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()
posted @ 2022-03-06 21:48  谢俊杰  阅读(1036)  评论(0编辑  收藏  举报