zoukankan      html  css  js  c++  java
  • Django模型系统——ORM中跨表、聚合、分组、F、Q

    核心知识点:

    1.明白表之间的关系

    2.根据关联字段确定正反向,选择一种方式

    在Django的ORM种,查询既可以通过查询的方向分为正向查询和反向查询,也可以通过不同的对象分为对象查询和Queryset查询。

    下面会一一进行讲解,首先给出表结构对应关系,以及表格中的数据:

    from django.db import models
    
    # Create your models here.
    
    class Book(models.Model):
        title = models.CharField(max_length=32)
        publish_date = models.DateField(auto_now_add=True)
        price = models.DecimalField(max_digits=5, decimal_places=2)
        # 创建外键,关联publish,一个出版社有多本书,书和出版社是多对一
        publisher = models.ForeignKey(to="Publisher")
        # 创建多对多关联author
        #一本书可能有多个作者,一个作者可能有多本书,因此双方是多对多的关系
        #因此会自动生成author_book表
        #定义关系在book或者author任意一张表中指定都可以
        #但是一般放在字段稍微少的表格中这样有利于数据查询
        author = models.ManyToManyField(to="Author")
    
        def __str__(self):
            return self.title
    
    
    # 出版社
    class Publisher(models.Model):
        name = models.CharField(max_length=32)
        city = models.CharField(max_length=32)
    
        def __str__(self):
            return self.name
    
    
    # 作者
    class Author(models.Model):
        name = models.CharField(max_length=32)
        age = models.IntegerField()
        phone = models.IntegerField()
        #作者和详情表是一对一关联
        detail = models.OneToOneField(to="AuthorDetail")
    
        def __str__(self):
            return self.name
    
    
    # 作者详情
    class AuthorDetail(models.Model):
        addr = models.CharField(max_length=64)
        email = models.EmailField()
    表结构对应关系
    mysql> show tables;
    +----------------------------+
    | Tables_in_books            |
    +----------------------------+
    | app01_author               |
    | app01_authordetail         |
    | app01_book                 |
    | app01_book_author          |
    | app01_publisher            |
    | auth_group                 |
    | auth_group_permissions     |
    | auth_permission            |
    | auth_user                  |
    | auth_user_groups           |
    | auth_user_user_permissions |
    | django_admin_log           |
    | django_content_type        |
    | django_migrations          |
    | django_session             |
    +----------------------------+
    15 rows in set (0.00 sec)
    
    mysql> select * from author;
    ERROR 1146 (42S02): Table 'books.author' doesn't exist
    mysql> select * from app01_author;
    +----+-----------+-----+--------+-----------+
    | id | name      | age | phone  | detail_id |
    +----+-----------+-----+--------+-----------+
    |  6 | 路遥      |  78 |     12 |         1 |
    |  7 | 黄仁宇    |  67 |  12345 |         2 |
    |  8 | 陈忠实    |  55 |    789 |         3 |
    |  9 | 刘天斯    |  35 |    567 |         4 |
    | 10 | 老男孩    |  40 |    456 |         5 |
    | 11 | 北岛      |  56 |    123 |         6 |
    | 12 | 付磊      |  35 |  56788 |         7 |
    | 13 | 张益军    |  38 | 234566 |         8 |
    +----+-----------+-----+--------+-----------+
    8 rows in set (0.00 sec)
    
    mysql> select * from app01_publisher;
    +----+-----------------------+--------+
    | id | name                  | city   |
    +----+-----------------------+--------+
    |  1 | 机械工业出版社        | 杭州   |
    |  2 | 电子工业出版社        | 北京   |
    |  3 | 长江文艺出版社        | 武汉   |
    |  4 | 人民出版社            | 北京   |
    |  5 | 岳麓书社出版社        | 扬州   |
    +----+-----------------------+--------+
    5 rows in set (0.00 sec)
    
    mysql> select * from app01_authordetail;
    +----+--------------+-------+
    | id | addr         | email |
    +----+--------------+-------+
    |  1 | 陕西平原     | 1@    |
    |  2 | 美国         | 2@    |
    |  3 | 重庆         | 3@    |
    |  4 | 江苏扬州     | 4@    |
    |  5 | 北京         | 5@    |
    |  6 | 湖北宜昌     | 6@    |
    |  7 | 江苏         | 7@    |
    |  8 | 安徽芜湖     | 8@    |
    +----+--------------+-------+
    8 rows in set (0.00 sec)
    
    mysql> select * from app01_book;
    +----+----------------------------+--------------+-------+--------------+
    | id | title                      | publish_date | price | publisher_id |
    +----+----------------------------+--------------+-------+--------------+
    |  1 | 平凡的世界                 | 2009-04-22   | 56.00 |            4 |
    |  2 | 跟老男孩学shell脚本        | 2012-02-16   | 78.00 |            2 |
    |  3 | 万历十五年                 | 2004-04-21   | 39.90 |            3 |
    |  4 | python自动化运维           | 2017-01-10   | 68.00 |            1 |
    |  5 | 中国大历史                 | 2014-01-14   | 45.00 |            5 |
    |  6 | 北岛诗集                   | 1999-10-10   | 29.90 |            3 |
    |  7 | Redis运维与开发            | 2019-10-01   | 68.00 |            1 |
    +----+----------------------------+--------------+-------+--------------+
    7 rows in set (0.00 sec)
    
    mysql> select * from app01_book_author;
    +----+---------+-----------+
    | id | book_id | author_id |
    +----+---------+-----------+
    |  1 |       1 |         6 |
    |  2 |       2 |        10 |
    |  3 |       3 |         7 |
    |  4 |       4 |         9 |
    |  5 |       5 |         7 |
    |  6 |       6 |        11 |
    |  7 |       7 |        12 |
    |  8 |       7 |        13 |
    +----+---------+-----------+
    8 rows in set (0.00 sec)
    表格中的相关数据

    下面使用表格展示关系:

    不管是基于对象查询,还是基于Queryset查询,都遵循正向查询字段,反向查询通过表名的规则。

    关于何时是正向查,何时是反查取决于关联字段定义在那个列表上,如果字段在本地,查询关联字段就是正向查,反之则是反向查询。

    关于格式上的用法:

    一、基本查询

    1.一对一(Author和Authordetail

    (1)正向查询:查询黄仁宇的所在地址

    可以看出,需要跨表,下面使用两种方式查询。

    首先通过对象正向查:

    models.Author.objects.filter(name="黄仁宇").first().detail.addr
    '美国'
    
    #首先作者查信息,从作者查,所以是正向查询
    #字段定义在谁那里,谁就是正的一方
    #首先找到黄仁宇这个对象
    models.Author.objects.filter(name="黄仁宇").first()
    #然后通过detail这个字段直接查询相关联的对象
    models.Author.objects.filter(name="黄仁宇").first().detail 

    通过Queryset对象正向查:

    #正向查询按找字段名
    models.Author.objects.filter(name="黄仁宇").values("detail__addr").first()
    {'detail__addr': '美国'}
    #这样也是可以得到相应的结果
    
    #为什么说是基于queryset对象正向查询了?
    models.Author.objects.filter(name="黄仁宇")
    <QuerySet [<Author: 黄仁宇>]>   #这是一个对象
    
    #当然返回的结果也是对象
    models.Author.objects.filter(name="黄仁宇").values("detail__addr")
    <QuerySet [{'detail__addr': '美国'}]>
    
    #values其实就是帮你做了一个循环,按照你的要求得到相应的字段值

    (2)反向查询:查询住在北京的作家的名称

    分析:有住址的是一张表,有作家姓名的是一张表,所以要跨表。

    基于对象查询:

    #反向查询按照表名(小写)
    models.AuthorDetail.objects.filter(addr="北京").first().author.name
    '老男孩'
    
    #先找到住在北京的对象
    models.AuthorDetail.objects.filter(addr="北京").first()
    
    #然后通过表名author直接查询,得到的也是对象

    基于Queryset查询:

    #反向查询通过表名,而且是基于Queryset
    models.AuthorDetail.objects.filter(addr="北京").values("author__name").first()
    {'author__name': '老男孩'}
    
    #Queryset对象返回的也是Queryset对象,values只是帮你循环取值,不会帮你转换数据类型

    2.多对一(Book与publisher)

    (1)正向查询:打印《万历十五年》这本书出版社的名字

    分析:正向查询依然按照字段名publisher

    基于对象查询:

    #找到书这个对象
    models.Book.objects.filter(title="万历十五年").first()
    <Book: 万历十五年>
    
    #找到与《万历十五年》这本书先关联的出版社的名字
    models.Book.objects.filter(title="万历十五年").first().publisher
    <Publisher: 长江文艺出版社>
    
    #找到对象之后直接获取相应的属性
    models.Book.objects.filter(title="万历十五年").first().publisher.name
    '长江文艺出版社'

    基于Queryset查询:

    #找到相应的Queryset
    models.Book.objects.filter(title="万历十五年").values("publisher__name")
    <QuerySet [{'publisher__name': '长江文艺出版社'}]>
    
    #得到相应的字段值
    models.Book.objects.filter(title="万历十五年").values("publisher__name").first()
    {'publisher__name': '长江文艺出版社'}
    
    #为什么只有Queryset才能使用__下划线,因为values是专门正对于Queryset的方法

    (2)反向查询

    反向查询通过小写表名__set

     基于对象查询:

    models.Publisher.objects.filter(name="长江文艺出版社").first().book_set.values()
    <QuerySet [{'id': 3, 'title': '万历十五年', 'publish_date': datetime.date(2004, 4, 21), 'price': Decimal('39.90'), 'publisher_id': 3}, {'id': 6, 'title': '北岛诗集', 'publish_date': datetime.date(1999, 10, 10), 'price': Decimal('29.90'), 'publisher_id': 3}]>

    基于Queryset对象查询:

    models.Publisher.objects.filter(name="长江文艺出版社").values("book__title")
    <QuerySet [{'book__title': '万历十五年'}, {'book__title': '北岛诗集'}]>

    3.多对多

    (1)正向查询:Redis运维与开发所有的作者的名字

    基于对象查询:

    #找到相对应的对象
    models.Book.objects.filter(title="Redis运维与开发")
    <QuerySet [<Book: Redis运维与开发>]>
    
    #找到对象相关联的作者
    models.Book.objects.filter(title="Redis运维与开发").first().author.values()
    <QuerySet [{'id': 12, 'name': '付磊', 'age': 35, 'phone': 56788, 'detail_id': 7}, {'id': 13, 'name': '张益军', 'age': 38, 'phone': 234566, 'detail_id': 8}]>
    
    #取出相应的字段值
    models.Book.objects.filter(title="Redis运维与开发").first().author.values("name",)
    <QuerySet [{'name': '付磊'}, {'name': '张益军'}]>

    基于Queryset查询

    #找到Queryset对象
    models.Book.objects.filter(title="Redis运维与开发") <QuerySet [<Book: Redis运维与开发>]>

    #直接取相应的字段 models.Book.objects.filter(title="Redis运维与开发").values("author__name") <QuerySet [{'author__name': '付磊'}, {'author__name': '张益军'}]>

    (2)反向查询:黄仁宇出的所有书的名字, author——》book,属于多对多,字段定义在book,属于反向查询

    基于对象查询:

    #获取黄仁宇这个任务对象
    models.Author.objects.filter(name="黄仁宇").first()
    <Author: 黄仁宇>
    
    #通过表名+set获取相关联的书籍名称
    models.Author.objects.filter(name="黄仁宇").first().book_set
    <django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager object at 0x0000022D330E73C8>
    
    #获取相应的属性值
    models.Author.objects.filter(name="黄仁宇").first().book_set.values("title")
    <QuerySet [{'title': '万历十五年'}, {'title': '中国大历史'}]>

    基于Queryset查询:

    #获取黄仁宇这个Queryset
    models.Author.objects.filter(name="黄仁宇")
    <QuerySet [<Author: 黄仁宇>]>
    
    #直接通过values特性获取相应的信息
    models.Author.objects.filter(name="黄仁宇").values("book__title")
    <QuerySet [{'book__title': '万历十五年'}, {'book__title': '中国大历史'}]>

     还有在过滤元素的时候,还可以通过__可以跨表进行参数设置:

    #书找书
    models.Book.objects.filter(author__name="黄仁宇").values("title")
    <QuerySet [{'title': '万历十五年'}, {'title': '中国大历史'}]>
    #可以看到与刚才基于对象和Queryset跨表结果一直
    
    #作者找书
    models.Author.objects.filter(name="黄仁宇").values("book__title")
    <QuerySet [{'book__title': '万历十五年'}, {'book__title': '中国大历史'}]>

    直接对着所需的对象,然后通过下换线过滤这种方法来的更加直白。

    拓展:查询“机械工业出版社”出版的书籍名和书籍的作者姓名

    #首先来查看需求
    #出版社一个表、书籍一个表以及作者一个表,也就是有三个表
    
    #正向查
    models.Book.objects.filter(publisher__name="机械工业出版社").values_list("title","author__name")
    <QuerySet [('python自动化运维', '刘天斯'), ('Redis运维与开发', '付磊'), ('Redis运维与开发', '张益军')]>
    #联合book--publisher,找到书名
    #联合book--author,再找到作者名,
    #与上一题相比,就实在显示的时候多了一个__跨表差
    
    
    #反向查
    models.Publisher.objects.get(name="机械工业出版社").book_set.all().values_list("title","author__name")
    <QuerySet [('python自动化运维', '刘天斯'), ('Redis运维与开发', '付磊'), ('Redis运维与开发', '张益军')]>
    #publisher—(正向)—》book—(反向)—》author
    
    
    #双下划线反向查
    models.Publisher.objects.filter(name="机械工业出版社").values_list("book__title","book__author__name")
    <QuerySet [('python自动化运维', '刘天斯'), ('Redis运维与开发', '付磊'), ('Redis运维与开发', '张益军')]>
    
    #最后显示的时候一次性跨了两张表

    二、聚合查询和分组查询

    1.聚合

    aggregate()QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。

    from django.db.models import Avg
    models.Book.objects.all().aggregate(Avg("price"))
    {'price__avg': 54.971429}

    如果你想要为聚合值指定一个名称,可以向聚合子句提供它。

    models.Book.objects.all().aggregate(average_price=Avg("price"))
    {'average_price': 54.971429}

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

    models.Book.objects.all().aggregate(Avg("price"),Max("price"),Min("price"))
    {'price__avg': 54.971429, 'price__max': Decimal('78.00'), 'price__min': Decimal('29.90')}

    2.分组

    为调用的QuerySet中每一个对象都生成一个独立的统计值

    示例1:统计每一本书的作者个数

    from django.db.models import Avg,Max,Min,Count
    book_list = models.Book.objects.all().annotate(author_num=Count("author"))
    for obj in book_list:
        print(obj.author_num)
        
    1
    1
    1
    1
    1
    1

    示例2:统计出每个出版社买的最便宜的书的价格

    publisher_list = models.Publisher.objects.annotate(min_price=Min("book__price"))
    for obj in publisher_list:
        print(obj.min_price)
        
    68.00
    78.00
    29.90
    56.00
    45.00

    方法二:

    models.Book.objects.values("publisher__name").annotate(min_price=Min("price"))
    <QuerySet [{'publisher__name': '机械工业出版社', 'min_price': Decimal('68.00')}, {'publisher__name': '电子工业出版社', 'min_price': Decimal('78.00')}, {'publisher__name': '长江文艺出版社', 'min_price': Decimal('29.90')}, {'publisher__name': '人民出版社', 'min_price': Decimal('56.00')}, {'publisher__name': '岳麓书社出版社', 'min_price': Decimal('45.00')}]>

    示例3:统计不止一个作者的图书

    models.Book.objects.annotate(author_num=Count("author")).filter(author_num__gt=1)
    <QuerySet [<Book: Redis运维与开发>]>

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

    models.Book.objects.annotate(author_num=Count("author")).order_by("author_num")
    <QuerySet [<Book: 平凡的世界>, <Book: 万历十五年>, <Book: 中国大历史>, <Book: 跟老男孩学shell脚本>, <Book: python自动化运维>, <Book: 北岛诗集>, <Book: Redis运维与开发>]>

    示例5:查询各个作者出的书的总价格

    models.Author.objects.annotate(sum_price=Sum("book__price")).values("name", "sum_price")
    <QuerySet [{'name': '路遥', 'sum_price': Decimal('56.00')}, {'name': '黄仁宇', 'sum_price': Decimal('84.90')}, {'name': '陈忠实', 'sum_price': None}, {'name': '刘天斯', 'sum_price': Decimal('68.00')}, {'name': '老男孩', 'sum_price': Decimal('78.00')}, {'name': '北岛', 'sum_price': Decimal('29.90')}, {'name': '付磊', 'sum_price': Decimal('68.00')}, {'name': '张益军', 'sum_price': Decimal('68.00')}]>

    三、F查询和Q查询

    1.F查询

    在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?

    Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。

    示例1:

    查询评论数大于收藏数的书籍

    from django.db.models import F
    models.Book.objects.filter(commnet_num__lt=F('keep_num'))

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

    models.Book.objects.filter(commnet_num__lt=F('keep_num')*2)

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

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

    引申:

    如果要修改char字段咋办?

    如:把所有书名后面加上(第一版)

    >>> from django.db.models.functions import Concat
    >>> from django.db.models import Value
    >>> models.Book.objects.all().update(title=Concat(F("title"), Value("("), Value("第一版"), Value(")")))

    2.Q查询

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

    示例1:

    查询作者名是黄仁宇或老男孩

    from django.db.models import Q
    models.Book.objects.filter(Q(author__name="黄仁宇")|Q(author__name="老男孩"))
    <QuerySet [<Book: 跟老男孩学shell脚本>, <Book: 万历十五年>, <Book: 中国大历史>]>

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

    示例2:

    查询作者名字是黄仁宇并且不是2014年出版的书的书名

    models.Book.objects.filter(Q(author__name="黄仁宇") & ~Q(publish_date__year=2014)).values_list("title")
    <QuerySet [('万历十五年',)]>

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

    示例3:

    查询出版年份是2017或2018,书名中带物语的所有书。

    models.Book.objects.filter(Q(publish_date__year=2004) | Q(publish_date__year=2014), title__icontains="")
    <QuerySet [<Book: 万历十五年>, <Book: 中国大历史>]>
  • 相关阅读:
    OO第三单元作业总结
    OO第二单元作业总结
    OO第一单元总结
    OO第四单元总结
    OO第三单元总结
    OO第二单元总结
    OO第一单元总结
    OO Unit4 总结
    OO Unit3 总结
    OO Unit2 总结
  • 原文地址:https://www.cnblogs.com/yangmingxianshen/p/8343854.html
Copyright © 2011-2022 走看看