ORM跨表查询

发布时间 2023-12-05 15:20:57作者: 橙子先生呀

 

ORM跨表查询、join查询、聚合查询、分组查询、FQ查询

跨表查询分为两类: 基于对象查询(子查询) 基于双下划线查询(join查询)

1、基于对象的跨表查询(sql语句:子查询)

子查询: 基于一个查询结果作为另一个查询的条件

1.1 一对多

"""
正向查询:多找一,按字段
反向查询:一找多,按表名称小写_set,其中set表示集合的意思
"""
正向查询(按字段)
# 查询西游记出版社的名字和邮箱
book = Book.objects.get(title='西游记')
print(book.publish.name)
print(book.publish.email)

 


本质上翻译两条sql如下:

(0.000) SELECT "book_book"."id", "book_book"."title", "book_book"."pub_date", "book_book"."price", "book_book"."publish_id" FROM "book_book" WHERE "book_book"."title" = '西游记' LIMIT 21; args=('西游记',)
# 小橘子出版社
(0.000) SELECT "book_publish"."id", "book_publish"."name", "book_publish"."city", "book_publish"."email" FROM "book_publish" WHERE "book_publish"."id" = 3 LIMIT 21; args=(3,)
# 222@666.com



反向查询(按表名:book_set,按 表名称小写_set, set是集合的意思,返回queryset集合)
# 查询小橘子出版社的所有书籍
pub = Publish.objects.get(name='小橘子出版社')
print(pub.book_set.all())    # 与这个出版社关联的所有书籍,即返回一个queryset
# <QuerySet [<Book: 西游记>, <Book: 赳赳老秦>]>

print(pub.book_set.values('title', 'price')) 
# <QuerySet [{'title': '西游记', 'price': Decimal('199.00')}, {'title': '赳赳老秦', 'price': Decimal('110.00')}]>

 

1.2 多对多
"""
正向查询:按字段
反向查询:按表名称小写_set,其中set表示集合的意思
"""
正向查询(按字段)
# 查询西游记所有作者的名字
book = Book.objects.get(title='西游记')
ret = book.authors.all().values('name')
print(ret)   # <QuerySet [{'name': '强子'}, {'name': '乖乖快回家'}]>

反向查询(按表名:book_set,按 表名称小写_set, set是集合的意思,返回queryset集合)
# 查询 强子 所有出版过的书籍名称
author_obj = Author.objects.get(name='强子')
ret = author_obj.book_set.all()
print(ret)   # <QuerySet [<Book: 西游记>, <Book: 三国志>]>


1.3 一对一
"""
正向查询:按字段,返回model对象,属性取值
反向查询:按表名称小写,不用加_set;一对一查询,仅返回一个对象;返回model对象,属性取值
"""
正向查询(按字段)
# 查询强子的手机号
author_obj = Author.objects.get(name='强子')
ret = author_obj.author_detail.telephone
print(ret)   # 111

反向查询(注意返回一个model对象,所以不用加 _set !!!,查到之后对象的属性取值)
# 查询手机号为111的作者名字
tel_obj = AuthorDetail.objects.get(telephone='111')
ret = tel_obj.author.name
print(ret)   # 强子

 

 

1.4 related_name 覆写 FOO_set

可以通过在 ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Book model 中做一下更改

publish = ForeignKey(Book, related_name='bookList')

接下来如下骚操作:

# 查询 人民出版社出版过的所有书籍
 
publish=Publish.objects.get(name="人民出版社")
book_list=publish.bookList.all()  # 与人民出版社关联的所有书籍对象集合

 

2、基于双划线的跨表查询(sql:join语句)

join查询:按照哪两个字段拼表,两张表拼成一张大表,一定程度下会影响查询效率

2.0 join简介

Publish表
id      name             email         addr
 1   北京出版社          123@qq.com      bj
 2   南京出版社          223@qq.com      nj

Book表
id  title    price   pub_date      publish_id       
1   西游记    123    2012-12-12         1                   
2   三国演义  234    2012-12-12         1                  
3   三体      45     2012-3-12          1      
4   水壶      45     2012-3-12          2

join之后的大表 Book
id  title    price   pub_date      publish_id     Publish.id   Publish.name          email         addr 
1   西游记    123    2012-12-12        1                1      北京出版社          123@qq.com    bj   
2   三国演义  234    2012-12-12        1                1      北京出版社          123@qq.com    bj    
3   三体      45     2012-3-12         1                1      北京出版社          123@qq.com    bj  
4   水壶      45     2012-3-12         2                2      南京出版社          223@qq.com    nj

正跨:关联字段publish 所在 表Book 进行查询 其所关联的表Publish的记录

反跨:关联表Publish 查询 其关联字段publish所在的表Book的记录

 

2.1 一对多

"""
   正向跨表(由关联字段所在表查询其关联的表):按关联字段__查询字段
   反向跨表:按表名称小写__查询字段
   以谁为基表 没所谓
"""
正跨查询,按字段;格式:外键字段__跨表字段;返回一个集合列表 [{},{}]
ret = Book.objects.filter(title='西游记').values('publish__name', 'publish__email')
print(ret)   # <QuerySet [{'publish__name': '小橘子出版社', 'publish__email': '222@666.com'}]>

ret = Book.objects.filter(publish__name='小橘子出版社').values('title')
print(ret)   # <QuerySet [{'title': '西游记'}, {'title': '赳赳老秦'}]>



反跨查询,按表名称;格式:小写表名称__跨表字段;返回一个集合列表 [{},{}]
ret = Publish.objects.filter(book__title='西游记').values('name', 'email')
print(ret)   # <QuerySet [{'name': '小橘子出版社', 'email': '222@666.com'}]>

ret = Publish.objects.filter(name='小橘子出版社').values('book__title')
print(ret)   # <QuerySet [{'book__title': '西游记'}, {'book__title': '赳赳老秦'}]>

 

 

2.2 多对多

关于多对多的join,sql中是三张表(Book、Author、book_authors第三张关联id表)拼接而成一张大表

  • 正跨查询
"""
   正向跨表(由关联表字段所在表查询其关联的表):按关联表字段__查询字段
   反向跨表:按表名称小写__查询字段
   以谁为基表 没所谓
"""
 
# 查询西游记所有作者的名字

ret = Book.objects.filter(title='西游记').values('authors__name')
print(ret)     # <QuerySet [{'authors__name': '强子'}, {'authors__name': '乖乖快回家'}]>

ret = Author.objects.filter(book__title='西游记').values('name')
print(ret)     # <QuerySet [{'name': '强子'}, {'name': '乖乖快回家'}]>

 

 

反跨查询
# 查询 强子 所有出版过的书籍名称
ret = Author.objects.filter(name='强子').values('book__title')
print(ret)     # <QuerySet [{'book__title': '西游记'}, {'book__title': '三国志'}]>

ret = Book.objects.filter(authors__name='强子').values('title')
print(ret)  # <QuerySet [{'title': '西游记'}, {'title': '三国志'}]>

 

 

2.3 一对一

"""
   正向跨表(由关联字段所在表查询其关联的表):按关联字段__查询字段
   反向跨表:按表名称小写__查询字段
   以谁为基表 没所谓
"""
正跨查询
# 查询强子的手机号
ret = Author.objects.filter(name='强子').values('author_detail__telephone')
print(ret)   # <QuerySet [{'author_detail__telephone': 111}]>

ret = AuthorDetail.objects.filter(telephone='111').values('author__name')
print(ret)  # <QuerySet [{'author__name': '强子'}]>

反跨查询
# 查询手机号为111的作者名字
ret = AuthorDetail.objects.filter(author__name='强子').values('telephone')
print(ret)   # <QuerySet [{'telephone': 111}]>

ret = Author.objects.filter(author_detail__telephone='111').values('name')
print(ret)  # <QuerySet [{'name': '强子'}]>

 

 

2.4 related_name 覆写 FOO_set

反向查询时,如果定义了related_name ,则用related_name替换表名,例如:

# 练习: 查询人民出版社出版过的所有书籍的名字与价格(一对多)

# 反向查询 不再按表名:book,而是 related_name:bookList


queryResult=Publish.objects
           .filter(name="人民出版社")
           .values_list("bookList__title","bookList__price")

 

3、 跨表查询进阶

3.1 多个跨表嵌套

正向跨表
# 跨表查询小橘子出版社出版过的所有书籍名字以及作者的姓名
ret = Book.objects.filter(publish__name='小橘子出版社').values('title', 'authors__name')
print(ret)
# <QuerySet [{'title': '西游记', 'authors__name': '强子'}, {'title': '西游记', 'authors__name': '乖乖快回家'}, {'title': '赳赳老秦', 'authors__name': '大猩猩'}, {'title': '赳赳老秦', 'authors__name': '马大哈'}]>
反向跨表
# 跨表查询小橘子出版社出版过的所有书籍名字以及作者的姓名
ret = Publish.objects.filter(name='小橘子出版社').values('book__title', 'book__authors__name')
print(ret)
# <QuerySet [{'book__title': '西游记', 'book__authors__name': '强子'}, {'book__title': '西游记', 'book__authors__name': '乖乖快回家'}, {'book__title': '赳赳老秦', 'book__authors__name': '大猩猩'}, {'book__title': '赳赳老秦', 'book__authors__name': '马大哈'}]>

 

3.2 连续跨表查询

查询手机号以111开头的作者出版过的所有书籍名称以及出版社名称
# 方式1:
query_ret = Book.objects.filter(authors__author_detail__telephone__regex=r'^111').\
    values_list('title',  'publish__name')
print(query_ret)  # <QuerySet [('西游记', '小橘子出版社'), ('三国志', '小苹果出版社')]>

# 方式2:
ret = Author.objects\
    .filter(author_detail__telephone__regex=r'^111')\
    .values('book__title', 'book__publish__name')
print(ret)
# <QuerySet [{'book__title': '西游记', 'book__publish__name': '小橘子出版社'}, {'book__title': '三国志', 'book__publish__name': '小苹果出版社'}]>

 

 

4、F查询 与 Q查询

4.0 Book表模型

 

class Book(models.Model):
    # id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    pub_date = models.DateTimeField()
    price = models.DecimalField(max_digits=9, decimal_places=2)  # 9999999.99

    keep_nums = models.IntegerField(default=0)      # 收藏数
    comment_nums = models.IntegerField(default=0)   # 评论数

    # 与Publish建立一对多的关系,外键字段ForeignKey会生成publisher_id建立在多的一方
    # publish = models.ForeignKey(to="Publish", to_field="pk", on_delete=models.CASCADE)  # 级联删除
    publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)  # 级联删除

    # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表book_authors实现多对多的关系
    authors = models.ManyToManyField(to='Author')

    def __str__(self):
        return self.title

 

 

 

4.1 F 查询

表内的两个字段的值如何进行比较??且看Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。

F(‘字段名’) 表示字段含义并非命名空间内变量的意思

from django.db.models import F

 # 查询 评论数 大于 收藏数 的书籍
 books = Book.objects.filter(comment_nums__gt=F('keep_nums'))
 print(books)  # <QuerySet [<Book: 赳赳老秦>]>

Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作

# 查询评论数大于收藏数2倍的书籍
Book.objects.filter(comment_nums__gt=F('keep_nums')*2)

修改操作也可以使用F函数,比如将每一本书的价格提高30元:

Book.objects.all().update(price=F("price")+30) 

 

 

4.2 Q 查询

filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象。

Q(字段条件) 进行 与或非 的 多条件过滤

与:  &
或:  |
非:  ~
from django.db.models import Q

Q(title__startswith='Py')



Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。

# 查询 价格 大于 100 或  评论数 大于 3000
books = Book.objects.filter(Q(price__gt=100)|Q(comment_nums__gt=3000))
print(books.query)
print(books)  # <QuerySet [<Book: 三国志>, <Book: 西游记>, <Book: 赳赳老秦>]>

等同于下面的SQL WHERE 子句:

WHERE price > 100 OR comment_nums =3000

可以组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用 ~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询

bookList=Book.objects.filter(Q(authors__name="yuan") & ~Q(pub_date__year=2020)).values_list("title")

查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:

bookList=Book.objects.filter(Q(pub_date__year=2016) | Q(pub_date__year=2017),
                              title__icontains="python"
                             )

 

5、聚合与分组查

5.1 聚合查询

aggregate(*args, **kwargs)

# 计算所有图书的平均价格
    >>> from django.db.models import Avg
    >>> Book.objects.all().aggregate(Avg('price'))
    {'price__avg': 34.35}

aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。

>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}

如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:

>>> from django.db.models import Avg, Max, Min
>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

 

5.2 分组查询

5.2.1 分组介绍
###################################--单表分组查询--#######################################################

查询每一个部门名称以及对应的员工数

emp:

id  name age   salary    dep
1   alex  12   2000     销售部
2   egon  22   3000     人事部
3   wen   22   5000     人事部


sql语句:
select dep,Count(*) from emp group by dep;

ORM:
emp.objects.values("dep").annotate(c=Count("id")

###################################--多表分组查询--###########################


多表分组查询:

查询每一个部门名称以及对应的员工数


emp:

id  name age   salary   dep_id
1   alex  12   2000       1
2   egon  22   3000       2
3   wen   22   5000       2


dep

id   name 
1    销售部
2    人事部



emp-dep:

id  name age   salary   dep_id   id   name 
1   alex  12   2000       1      1    销售部
2   egon  22   3000       2      2    人事部
3   wen   22   5000       2      2    人事部

 




sql语句:
select dep.name,Count(*) from emp left join dep on emp.dep_id=dep.id group by dep.id

ORM:
dep.objetcs.values("id").annotate(c=Count("emp")).values("name","c")

 

5.2.2 分组 类 实现
class Emp(models.Model):
    name=models.CharField(max_length=32)
    age=models.IntegerField()
    salary=models.DecimalField(max_digits=8,decimal_places=2)
    dep=models.CharField(max_length=32)
    province=models.CharField(max_length=32)

 

 

annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。

总结 :跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。

5.2.3 查询练习
(1) 统计每一个出版社的最便宜的书
publishList=Publish.objects.annotate(MinPrice=Min("book__price"))
for publish_obj in publishList:
    print(publish_obj.name,publish_obj.MinPrice)

annotate的返回值是querySet,如果不想遍历对象,可以用上valuelist:
queryResult= Publish.objects
            .annotate(MinPrice=Min("book__price"))
            .values_list("name","MinPrice")
print(queryResult)

'''

SELECT "app01_publish"."name", MIN("app01_book"."price")  AS "MinPrice" FROM "app01_publish" 
LEFT  JOIN "app01_book" ON ("app01_publish"."nid" = "app01_book"."publish_id") 
GROUP BY "app01_publish"."nid", "app01_publish"."name", "app01_publish"."city", "app01_publish"."email" 

'''

(2) 练习:统计每一本书的作者个数
ret=Book.objects.annotate(authorsNum=Count('authors__name'))

1
(3) 统计每一本以py开头的书籍的作者个数
 queryResult=Book.objects
           .filter(title__startswith="Py")
           .annotate(num_authors=Count('authors'))

(4) 统计不止一个作者的图书
queryResult=Book.objects
          .annotate(num_authors=Count('authors'))
          .filter(num_authors__gt=1)

(5) 根据一本图书作者数量的多少对查询集 QuerySet进行排序
Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')

(6) 查询各个作者出的书的总价格
#   按author表的所有字段 group by
    queryResult=Author.objects
              .annotate(SumPrice=Sum("book__price"))
              .values_list("name","SumPrice")
    print(queryResult)