Django 多表查询:外键,联表,聚合,分组
多表操作前期准备
Models.py
from django.db import models
# Create your models here.
# 书籍
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8, decimal_places=2)
# 书籍与出版社一对多关系
publish = models.ForeignKey('Publish')
# 书籍与作者多对多关系
authors = models.ManyToManyField('Author')
# 出版社
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
email = models.EmailField()
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.BigIntegerField()
# 作者与作者表一对一关系
author_detail = models.OneToOneField('AuthorDetail')
class AuthorDetail(models.Model):
phone = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
外键的增删改查
一对多
增
# 第一种:直接书写实际字段,id
res = models.Book.objects.create(title='三国演义',price=123.23,publish_id=1)
# 第二种:虚拟字段,对象,
# 查询到出版社对象
publish_obj = models.Publish.objects.filter(pk=2)
print(publish_obj)
# 内部会自动找到当前数据对象的主键值并添加
models.Book.objects.create(title='红楼梦',price=666.23,publish=publish_obj)
删
# 删除Publish表中主键字段为1的记录。默认级联更新级联删除。
models.Publish.objects.filter(pk=1).delete()
修改
# 修改
# 第一种方式:将Book主键字段为1的publish_id修改成外键字段1
models.Book.objects.filter(pk=1).update(publish_id=1)
# 第二种方式:先获取一个主键字段为1的数据对象,在Book筛选出主键字段为1数据,将他的外键字段更新为对象的第一个元素即为id字段
publish_obj = models.Book.objects.filter(pk=1).first()
models.Book.objects.filter(pk=1).update(publish=publish_obj)
多对多
多对多的增删改查,就是在操作第三张表。
增>>>add()
给第三张关系表添加关系,括号内既可以传数字也可以传对象,并且都支持多个。
# 过滤出书籍主键为1的。
book_obj = models.Book.objects.filter(pk=2).first()
book_obj = models.Book.objects.filter(pk=5).first()
# Book类中有authors属性,那么产生的对象都会具有authors属性
print(book_obj.authors)
# 书籍id为1的书籍绑定主键为1的作者
book_obj.authors.add(1)
# add方法还可以添加多个,将书籍主键为1的书籍绑定主键为2,3的作者
book_obj.authors.add(2,3)
# 同样支持对象
book_obj = models.Book.objects.filter(pk=2).first()
author_obj = models.Author.objects.filter(pk=1).first()
book_obj.authors.add(author_obj)
# 支持添加多个
book_obj = models.Book.objects.filter(pk=3).first()
author_obj1 = models.Author.objects.filter(pk=1).first()
author_obj2 = models.Author.objects.filter(pk=2).first()
author_obj3 = models.Author.objects.filter(pk=3).first()
book_obj.authors.add(author_obj1,author_obj2,author_obj3)
删>>>remove()
remove:括号内既可以传数字也可以传对象,并且都支持多个。
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.remove(1,2)
# 对象的方式
book_obj = models.Book.objects.filter(pk=5).first()
author_obj = models.Author.objects.filter(pk=3).first()
book_obj.authors.remove(author_obj)
修改>>>set()
括号内必须传一个可迭代对象,该对象内既可以是数字也可以是对象,并且都支持多个。
先删除后新增。
# 将主键为1的书籍绑定关系修改为2,3,并且set括号内只能是可迭代对象
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.set([2,3])
# 对象方法
book_obj = models.Book.objects.filter(pk=2).first()
author_obj = models.Author.objects.filter(pk=3).first()
book_obj.authors.set([author_obj])
清空>>>clear()
括号内不用加任何参数,清除所有的绑定关系
# 在第三张关系表中清空书籍主键字段为1的书籍与作者的绑定关系
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.clear()
正反向的概念
正向:从具有外键字段的表查询无外键字段的表为正向
外键字段在我手上,我查你是正向
book >>> 外键字段在书(正向) >>>publish
反向:从没有外键字段的表查询具有外键的表为反向
外键字段不再手上,我查你就是反向
publish >>> 外键字段在书(反向) >>> book
口诀:正向按查询按字段(外键字段),反向查询按表名(_set.all())
多表查询
子查询>>>ORM(基于对象的跨表查询)
正向例题:
查询书籍主键为1的出版社名称
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.publish
print(res) # 对象: 东方出版社
print(res.name) # 东方出版社
print(res.addr) # 东方
查询书籍主键为1的作者
# 外键关系在书籍,正向查询,正向查询按字段。
book_obj = models.Book.objects.filter(pk=1).first()
# 拿到连个作者对象,列表套两个对象
res = book_obj.authors.all()
print(res)
查询作者junjie的电话号码
author_id = models.Author.objects.filter(name='junjie').first()
res = author_id.author_detail
print(res)
print(res.phone)
总结: 什么时候需要.all()
? 当结果可能有多个的时候后需要加.all()
,如果是一个则直接拿到数据对象
反向例题:
查询出版社是东方出版社出版的书
publish_book = models.Publish.objects.filter(name='东方出版社').first()
# 出版社查书 反向
# res = publish_book.book_set # app01.Book.None,输出此现象只需要加all()即可
res = publish_book.book_set.all()
print(res)
# <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>]>
查询作者是junjie写过的书
author_book = models.Author.objects.filter(name='junjie').first()
res = author_book.book_set.all()
print(res)
查询查询手机号是110的作者姓名
author_obj = models.AuthorDetail.objects.filter(phone=110).first()
res = author_obj.author
print(res.name)
总结:反向查询是,查询结果多个必须加_set.all()
,查询结果只有一个时不需要加。
联表查询>>>ORM(基于双下划线的跨表查询)
正向查询:
查询junjie的手机号和作者姓名
# 筛选出作者表中名字为junjie的数据,values中'author_detail'就代表这已经在这张关系表,__phone为字段,values是取表中字段的值
res = models.Author.objects.filter(name='junjie').values('author_detail__phone','name')
print(res)
# 输出:<QuerySet [{'author_detail__phone': '999', 'name': 'junjie'}]>
查询junjie的手机号
res = models.Author.objects.filter(name='junjie').values('author_detail__phone')
print(res)
# 输出:<QuerySet [{'author_detail__phone': '999'}]>
查询书籍主键为1的出版社名称和书的名称
book_obj = models.Book.objects.filter(pk=1).values('title','publish__name')
print(book_obj)
# 输出:<QuerySet [{'title': '红楼梦', 'publish__name': '东方出版社'}]>
查询书籍主键为1的作者姓名
res = models.Book.objects.filter(pk=1).values('authors__name')
print(res)
# 输出:<QuerySet [{'authors__name': 'junjie'}, {'authors__name': 'jason'}]>
反向查询:
查询junjie的手机号和作者姓名,不使用models.Author
的情况如何操作?
# 1. 拿姓名是junjie的作者详情
# res = models.AuthorDetail.objects.filter(author__name='junjie')
# 此时在AuthorDetail表,可以直接拿phone,在AuthorDetail拿作者名字为反向,使用小写表名
res = models.AuthorDetail.objects.filter(author__name='junjie').values('author__name','phone')
print(res)
# 输出:<QuerySet [{'author__name': 'junjie', 'phone': '999'}]>
查询书籍主键字段为1的主板社名称和书的名称
# 先拿书籍主键为1的出版社
res = models.Publish.objects.filter(book__id=1).values('name','book__title')
print(res)
查询书籍主键为1的作者姓名
res = models.Author.objects.filter(book__id=1).values('name')
print(res)
查询书籍主键是1的作者的手机号
res = models.Author.objects.filter(book__pk=1).values('author_detail__phone')
或者
res = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
print(res)
# 输出:结果一致
总结:只要掌握正方向概念,以及双下划线,可以无限制跨表。
聚合查询>>>aggregate
聚合查询,通常情况下配合分组一起使用,如果不想使用分组,需要添加 aggregate
只要跟数据库相关的模块,基本都在django.db.models中
如果上述没有应该在django.db中
from django.test import TestCase
# Create your tests here.
import os
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django01.settings")
from django.db.models import Max, Min, Sum, Avg, Count
import django
django.setup()
from app01 import models
# 聚合函数
res = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Avg('price'), Count('pk'))
print(res)
# 输出:{'price__max': Decimal('899.23'), 'price__min': Decimal('111.23'), 'price__sum': Decimal('2688.15'), 'price__avg': 537.63, 'pk__count': 5}
分组查询
配合聚合函数使用,models后面点什么就是按什么分组,并且ORM会内部简化,下面举例说明
只要ORM语句得出的结果还是一个queryset对象,就可以无限制的点queryset对象封装的方法。
统计每一本书的作者个数
# 按照书分组
# res = models.Book.objects.annotate() #
# 按照书分组,取一个别名即字段:author_num,用于存储统计出来的每本书对应作者个数,计数引用Count聚合函数,外键字段在Book表,正向查询按字段,字段后跟随可以查询作者个数的字段:id or pk
# 但是ORM内部简化,只需要写Count('author')
res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('author_num') # models后面点什么就是按什么分组
print(res)
# 输出:<QuerySet [{'author_num': 2}, {'author_num': 2}, {'author_num': 1}, {'author_num': 2}, {'author_num': 0}]>
统计每个出版社卖的最便宜的书的价格
# 从出版社查询书籍为反向
res = models.Publish.objects.annotate(book_price=Min('book__price')).values('book_price')
print(res)
# 输出:<QuerySet [{'book_price': Decimal('111.23')}, {'book_price': Decimal('888.23')}]>
统计不止一个作者的图书
res = models.Book.objects.annotate(book_num=Count('authors__pk')).filter(book_num__gt=1).values('title','book_num')
print(res)
# 输出:<QuerySet [{'title': '红楼梦', 'book_num': 2}, {'title': '三国演义', 'book_num': 2}, {'title': '中庸', 'book_num': 2}]>
查询每个作者出的书的总价格
res = models.Author.objects.annotate(author_price=Sum('book__price')).values('name','author_price')
print(res)
# 输出:<QuerySet [{'name': 'junjie', 'author_price': Decimal('2576.92')}, {'name': 'jason', 'author_price': Decimal('1677.69')}, {'name': 'egon', 'author_price': None}]>
补充:如何按照指定的字段分组如何处理?
# 如果annotate前面出现vaules,会按照values括号内指定字段分组,如果没有values,按照models括号内指定的表分组
res = models.Book.objects.values('price').annotate()