[toc]
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 直接传作者idbook.authors.add(1,3)# 3 直接传列表,会打散book.authors.add(*[1,2])# 解除多对多关系book = Book.objects.filter(name=\'红楼梦\').first()# 1 传作者idbook.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 depfrom django.db.models import Avg, Count, Max, Minret=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 empret=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, Qres = 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 transactiontry:\'\'\'执行的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_add
和
auto_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 modelsres=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)# 这样看起来是使用了作者表,但是可以查书的相关数据