Django ORM 多表操作(二)

x33g5p2x  于2022-03-05 转载在 Go  
字(9.6k)|赞(0)|评价(0)|浏览(318)


本文总结:多对多常见API如何使用,分组查询的多种情况、ORM如何开启事务,ORM中常见的字段和参数有哪些?在不同情况下创建自己需要的多对多关系表~什么?ORM创建表的类中还可以写Meta类?

Django ORM 多表操作(二)

多对多操作常用API

  • add
  • remove
  • clear
  • set

示例

# 当前生成的书籍对象
    book_obj=Book.objects.create(title="三国演义",price=200,publishDate="2012-11-12",publish_id=1)
    # 为书籍绑定的做作者对象
    hans=Author.objects.filter(name="hans").first() # 在Author表中主键为2的纪录
    tom=Author.objects.filter(name="tom").first() # 在Author表中主键为1的纪录

    # 绑定多对多关系,即向关系表book_authors中添加纪录
    book_obj.authors.add(hans,tom)    #  将某些特定的 model 对象添加到被关联对象集合中。   =======    book_obj.authors.add(*[])
book = Book.objects.filter(name='红楼梦').first()
    Hammer=Author.objects.filter(name='Hammer').first()
    hans=Author.objects.filter(name='hans').first()
    # 1 没有返回值,直接传对象
    book.authors.add(Hammer,hans)
    # 2 直接传作者id
    book.authors.add(1,3)
    # 3 直接传列表,会打散
    book.authors.add(*[1,2])
    
 # 解除多对多关系
    book = Book.objects.filter(name='红楼梦').first()
    # 1 传作者id
    book.authors.remove(1)
    # 2 传作者对象
    hans = Author.objects.filter(name='hans').first()
    book.authors.remove(hans)
    #3 传*列表
    book.authors.remove(*[1,2])
    #4 删除所有
    book.authors.clear()
    # 5 拿到与 这本书关联的所有作者,结果是queryset对象,作者列表
    ret=book.authors.all()
    # 6 queryset对象,又可以继续点(查询红楼梦这本书所有作者的名字)
    ret=book.authors.all().values('name')
    print(ret)
book_obj.authors.remove()      # 将某个特定的对象从被关联对象集合中去除。    ======   book_obj.authors.remove(*[])
book_obj.authors.clear()       #清空被关联对象集合
book_obj.authors.set()         #先清空再设置

分组查询

分组查询一般会用到聚合函数,所以使用前要先从 django.db.models 引入 Avg,Max,Min,Count,Sum(首字母大写)

from django.db.models import Avg,Max,Min,Count,Sum  #   引入函数

返回值:

  • 分组后,用 values 取值,则返回值是 QuerySet 数据类型里面为一个个字典;
  • 分组后,用 values_list 取值,则返回值是 QuerySet 数据类型里面为一个个元组

注意:MySQL 中的 limit 相当于 ORM 中的 QuerySet 数据类型的切片,annotate 里面放聚合函数

  • **values 或者 values_list 放在 annotate 前面:**values 或者 values_list 是声明以什么字段分组,annotate 执行分组。
  • values 或者 values_list 放在annotate后面: annotate 表示直接以当前表的pk执行分组,values 或者 values_list 表示查询哪些字段, 并且要将 annotate 里的聚合函数起别名,在 values 或者 values_list 里写其别名。

总结

  • 使用annotate()分组,括号内写聚合函数
  • values在前面,表示SQL中group by的字段
  • values在后面,表示取出 字段
  • filter在前面,表示SQL中where条件
  • filter在后面,表示SQL中having过滤

单表分组查询示例

格式:表名.objects.values('group by 的字段').annotate(聚合函数('统计的字段'))

# select count(id) from emp group by dep
    # 示例一:查询每一个部门的名称,以及平均薪水
    # select dep,Avg(salary) from app01_emp group by dep
    from django.db.models import Avg, Count, Max, Min
    ret=Emp.objects.values('dep').annotate(Avg('salary'))
    # 重新命名
    ret=Emp.objects.values('dep').annotate(avg_salary=Avg('salary'))
    print(ret)

    # 示例2 查询每个省份对应的员工数
    ret=Emp.objects.values('province').annotate(Count('id'))
    ret=Emp.objects.values('province').annotate(c=Count('id'))
    print(ret)
    # 补充知识点:
    ret=Emp.objects.all()
    # select * from emp
    ret=Emp.objects.values('name')
    # select name from emp
'''单表下,按照id进行分组是没有任何意义的'''
    ret=Emp.objects.all().annotate(Avg('salary'))
    print(ret)

多表分组查询练习

'''多表分组查询'''
    # 查询每一个出版社出版的书籍个数
    ret=Book.objects.values('publish_id').annotate(Count('nid'))
    print(ret)
    # 查询每个出版社的名称以及出版社书的个数(先join在跨表分组)
    # 正向
    ret=Publish.objects.values('name').annotate(Count('book__name'))
    ret=Publish.objects.values('nid').annotate(c=Count('book__name')).values('name','c')
    print(ret)
    # 反向
    ret=Book.objects.values('publish__name').annotate(Count('name'))
    ret=Book.objects.values('publish__name').annotate(c=Count('name')).values('publish__name','c')
    print(ret)
    # 查询每个作者的名字,以及出版过书籍的最高价格
    ret=Author.objects.values('pk').annotate(c=Max('book__price')).values('name','c')
    print(ret)
    # 跨表查询的模型:每一个后表模型.objects.value('pk').annotate(聚合函数('关联表__统计字段')).values()

    # 查询每一个书籍的名称,以及对应的作者个数
    ret=Book.objects.values('pk').annotate(c=Count('authors__name')).values('name','c')
    print(ret)
    # 统计不止一个作者的图书
    ret=Book.objects.values('pk').annotate(c=Count('authors__name')).filter(c__gt=1).values('name','c')
    print(ret)

查询练习

1、统计每一本书作者个数

# 1、统计每一本书作者个数
    from django.db.models import Avg, Max, Min, Sum, Count, F, Q
    res = models.Book.objects.all().annotate(author_num = Count('authors')).values('title','author_num')
    print(res)

2、统计每一个出版社的最便宜的书

res = models.Publish.objects.all().annotate(min_price = Min('book__price')).values_list('name','min_price')
print(res)
  # <QuerySet [('北方出版社', Decimal('168.80')), ('东方出版社', Decimal('168.80')), ('南方出版社', Decimal('180.00')), ('西方出版社', None)]>
        
res1 = models.Publish.objects.all().annotate(min_price=Min('book__price')).values('name', 'min_price')
print(res)
    # <QuerySet [{'name': '北方出版社', 'min_price': Decimal('168.80')}, {'name': '东方出版社', 'min_price': Decimal('168.80')}, {'name': '南方出版社', 'min_price': Decimal('180.00')}, {'name': '西方出版社', 'min_price': None}]>

3、统计每一本包含‘水’的书籍的作者个数

res = models.Book.objects.filter(title__contains='水').annotate(author_num=Count('authors')).values('title','author_num')
    print(res)  # <QuerySet [{'title': '水浒传', 'author_num': 3}]>

4、统计不止一个作者的图书:(作者数量大于一)

res = models.Book.objects.all().annotate(author_num=Count('authors')).filter(author_num__gt=1).values('title','author_num')
    print(res)  # <QuerySet [{'title': '水浒传', 'author_num': 3}]>

5、统计不止一个写过一本书的作者:(书数量大于一)

# 4、统计不止一个写过一本书的作者:(书数量大于一)
    res = models.Author.objects.all().annotate(book_num = Count('book')).filter(book_num__gt=1).values('name','book_num')
    print(res) # <QuerySet [{'name': '李白', 'book_num': 2}]>

6、根据一本图书作者数量的多少对查询集 QuerySet进行排序:

res = models.Book.objects.all().annotate(author_num=Count('authors')).values_list('title','author_num').order_by('author_num')
print(res)  # 升序:<QuerySet [('三国演义', 0), ('西游记', 0), ('西游记', 1), ('水浒传', 3)]>

res = models.Book.objects.all().annotate(author_num=Count('authors')).values_list('title','author_num').order_by('author_num')
print(res)  # 降序:<QuerySet [('水浒传', 3), ('西游记', 1), ('三国演义', 0), ('西游记', 0)]>

7、查询各个作者出的书的总价格:

res = models.Author.objects.annotate(book_price=Sum('book__price')).values('name','book_price')
print(res)
# <QuerySet [{'name': '李白', 'book_price': Decimal('348.70')}, {'name': '杜甫', 'book_price': Decimal('179.90')}, {'name': '王羲之', 'book_price': Decimal('179.90')}]>

8、查询每个出版社的名称和书籍个数

res = models.Publish.objects.annotate(book_num=Count('book')).values('name','book_num')
print(res)
    # <QuerySet [{'name': '北方出版社', 'book_num': 2}, {'name': '南方出版社', 'book_num': 1}, {'name': '东方出版社', 'book_num': 1}, {'name': '西方出版社', 'book_num': 0}]>

ORM 事务

'''django开启事务'''
 导入模块:   from django.db import transaction

# 示例
from django.db import transaction
    
    try:
        '''执行的SQL语句'''
        with transaction.Atomic:
            # sql1
            # sql2
            # sqln···
            pass
            # 写在这个里面的都是属于同一个事务
        
    except Exception as e:
        print(e)
        transaction.rollback()  # 回滚

ORM 常见字段和参数

ORM常见字段

AutoField:int自增列,必须填入参数 primary_key=True。当model中如果没有自增列,则自动会创建一个列名为id的列。

IntegerField:一个整数类型,范围在 -2147483648 to 2147483647。

CharField:字符类型,必须提供max_length参数, max_length表示字符长度。

DateField:日期字段,日期格式 YYYY-MM-DD,相当于Python中的datetime.date()实例。

DateTimeField:日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例

ps:DateField和DateTimeField可以添加auto_now_addauto_now参数,auto_now_add表示如果配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库,auto_now表示如果配置上auto_now=True,每次更新数据记录的时候会更新该字段

ORM 常见参数

null:用于表示某个字段可以为空

unique:如果设置为unique=True 则该字段在此表中必须是唯一的

db_index:如果db_index=True 则代表着为此字段设置索引

default:为该字段设置默认值

choices参数

针对可选,可列举的可能性,我们可以使用choices参数,比如性别,爱好,学历等

'''定义表字段的时候添加'''
gender_choices = (
        (1, '男'),
        (2, '女'),
        (3, '其他'),
    )
gender = models.IntegerField(choices=gender_choices)

# 这样我们在插入数据的时候就可以以该数值插入,1代表男,2二代表女,3代表其他,那么查询的时候输出的还是对应的字符串(男,女,其他)

注意:对于choices参数我们该如何选择数据类型?比如上面的性别,那么就看提前设定的小元组内第一个元素是什么类型的参数,下面定义字段就采用什么参数

# 字符串的情况
score_choices = (
        ('A', '优秀'),
        ('B', '良好'),
        ('C', '及格'),
        ('D', '不及格'),
    )
score = models.CharField(max_length=8)

读取存入的数据

res = models.User.objects.filter(pk=4).first()
    print(res)
    print(res.gender)
    print(res.get_gender_display())  # 固定语法:get_字段名_display()
'''
固定语法结构取值:get_字段名_display()
如果查询出来的数据不再choices范围内,会显示原始数据,比如gender只定义了1-3,那么读取4的时候返回的就是数值4
'''

多对多关联关系的三种方式

方式一:自己创建第三张表

  • 扩展性高的优点,以及如果第三张表中需要插入其他数据,不止于两个表的对应id值得时候,也可以采用该方式
class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="书名")

class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")

# 自己创建第三张表,分别通过外键关联书和作者
class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")

    class Meta:
        unique_together = ("author", "book")

方式二:通过ManyToManyFeild自动创建第三张表

class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="书名")

# 通过ORM自带的ManyToManyField自动创建第三张表
class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")
    books = models.ManyToManyField(to="Book", related_name="authors")

方式三:设置ManyTomanyField并指定自行创建的第三张表

class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="书名")

# 自己创建第三张表,并通过ManyToManyField指定关联
class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")
    books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
    # through_fields接受一个2元组('field1','field2'):
    # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。

class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")

    class Meta:
        unique_together = ("author", "book")

注意

  • 当我们需要在第三张关系表中存储额外的字段时,就要使用第三种方式。
  • 但是当我们使用第三种方式创建多对多关联关系时,就无法使用set、add、remove、clear方法来管理多对多的关系了,需要通过第三张表的model来管理多对多关系。

元信息

ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:

db_table

ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名

index_together
联合索引

unique_together

联合唯一索引

ordering

指定默认按什么字段排序,只有设置了该属性,我们查询到的结果才可以被reverse()

class UserInfo(models.Model):
        nid = models.AutoField(primary_key=True)
        username = models.CharField(max_length=32)

        class Meta:
            # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
            db_table = "table_name"

            # 联合索引:多个字段建索引
            index_together = [
                ("pub_date", "deadline"),
            ]

            # 联合唯一索引:当两个字段同时一样的时候就不能存入
            unique_together = (("driver", "restaurant"),)
            
            ordering = ('name',)  # ordering设置默认按什么排序

原生sql

如果想在django中使用原生sql,那么raw方法可以实现

from app01 import models
	res=models.Author.objects.raw('select * from app01_author where nid>1')
	for author in res:
		print(author.name)
		
'''上面看起来是使用了作者表,来查了数据,但是执行原生sql,跟对象类型无关了,查出什么字段,可以直接使用该字段'''
	res = models.Author.objects.raw('select * from app01_book where nid>1')
    for book in res:
        print(book.price)
	# 这样看起来是使用了作者表,但是可以查书的相关数据

相关文章