liuchengdage

注意事项:

  • 在同一个应用中重新跟新表时,建议删除该应用下migrations文件夹下对应的文件!
  • 跨表分组查询之后可以取到任意字段

1.聚合函数

表模型请参考多表操作
数据准备以及路径配置等等请参考 基于对象的跨表查询
其他配置请参考前面几小节的博客

app01/views.py


from django.shortcuts import render,HttpResponse
from app01.models import *

def query(request):
    """跨表查询
        1 基于对象查询
        2 基于双下划线查询
        3 聚合和分组查询
        4 F 与 Q 查询
    """
    # ================ 聚合查询 ================
    """
    aggregate聚合函数:是QuerySet 的一个终止子句,返回值是一个字典,不再是QuerySet
    * 它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。
    * 键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
    * 如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一写参数。

    """
    """查询所有书籍的平均价格     原生SQL
    select avg(price) from app01_book;
    """
    # 需要导入相关的聚合函数
    from django.db.models import Avg,Max,Min,Count

    book_price_avg = Book.objects.all().aggregate(Avg("price"))
    print(book_price_avg) # {\'price__avg\': 613.14}

    # 可以自己定义一个查询结果的名字,如下
    bpav = Book.objects.all().aggregate(avg__price=Avg("price"))
    print(bpav)

    # 可以同时查询多个聚合函数
    aggre = Book.objects.all().aggregate(avg__price=Avg("price"),
     max__price=Max(\'price\'), min_price=Min("price"), total_price=Count(\'price\'))
    print(aggre)

    # {\'avg__price\': 613.14, \'max__price\': Decimal(\'999.99\'), \'min_price\': Decimal(\'222.55\'), \'total_price\': 4}

    return HttpResponse("OK")

2.单表下的分组查询

准备工作(详细步骤略,如果需要请参照Django博客其他文章)

  • 终端Mysql新建一个数据库,配置setting.py数据库的名称
  • 在app01/models.py创建Emp表并进行表迁移(python manage.py makemigrations / python manage.py migrate)
  • 在Emp表插入数据。

app01/models.py

from django.db import models

class Emp(models.Model):
    id = models.AutoField(primary_key=True)
    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)

app01/views.py

from django.shortcuts import render,HttpResponse
from app01.models import * # 最好这样引入视图函数,避免models调用重名覆盖!
from django.db.models import Avg,Max,Min,Count


def query(request):
    """跨表查询
        1 基于对象查询
        2 基于双下划线查询
        3 聚合和分组查询
        4 F 与 Q 查询
    """
# ================== 分组查询 ===================
    #           annotate,返回值依然是queryset
    """
    annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。
    而 aggregate() 是聚合所有的对象??
    """

    # ======= 单表的分组查询
    """
    app01_emp:
    id  name  age  salary dep    province
    1   alex  12   2000   教学部  四川
    2   egon  22   3000   讲师部  上海
    3   peiqi 22   5000   讲师部  北京

    插入数据
    Emp.objects.create(name="alex", age=12, salary=2000, dep="教学部", province="四川")
    Emp.objects.create(name="egon", age=22, salary=3000, dep="讲师部", province="上海")
    Emp.objects.create(name="peiqi", age=22, salary=5000, dep="讲师部", province="北京")

    查询每一个部门名称以及对应的员工数 sql:
    select Count(id) from app01_emp group by dep;

    如何用ORM 语法进行分组查询:
        单表模型.objects.values("group by的字段").annotate(聚合函数("统计字段"))
        注意:在单表下按照主键进行group by 是没有任何意义的。

    """
    # 示例一:查询每一个部门的名称以及员工的平均薪水
    # select dep,Avg(salary) from app01_emp group by dep;
    # 可以为分组自定义名字avg_salary ;返回值是QuerySet
    res = Emp.objects.values("dep").annotate(avg_salary=Avg(\'salary\'))
    print(res)
    # <QuerySet [{\'dep\': \'教学部\', \'avg_salary\': 2000.0}, {\'dep\': \'讲师部\', \'avg_salary\': 4000.0}]>

    # 示例二:查询每一个省份的名称以及对应的员工数
    res = Emp.objects.values("province").annotate(count_number=Count("id"))
    print(res)
    # <QuerySet [{\'province\': \'四川\', \'count_number\': 1}, {\'province\': \'上海\', \'count_number\': 1}, {\'province\': \'北京\', \'count_number\': 1}]>


    # 补充知识点
    ret = Emp.objects.all()
    # select * from app01_emp;

    ret = Emp.objects.all().values(\'name\')
    # 等同于 Emp.objects.values(\'name\')
    # select name from app01_emp;


    return HttpResponse("OK")

3.多表下的分组查询

表模型请参考多表操作
数据准备以及路径配置等等请参考 基于对象的跨表查询
其他配置请参考前面几小节的博客

app01/views.py

from django.shortcuts import render,HttpResponse
from app01.models import * # 最好这样引入视图函数,避免models调用重名覆盖!
from django.db.models import Avg,Max,Min,Count
def query(request):
    """跨表查询
        1 基于对象查询
        2 基于双下划线查询
        3 聚合和分组查询
        4 F 与 Q 查询
    """
   # ================= 多表(跨表)分组查询 ================================
    """
    思路:先join然后group py
    跨表分组查询的实用模型
        1:被分组字段所在的基表模型.objects.values("pk").annotate(聚合函数(\'关联表__统计字段\')).value("取其他字段"...)
    其他玩法:被分组字段所在的基表模型.objects.annotate(聚合函数(\'关联表__统计字段\')).value("取其他字段"...)
                                  -----.all().-----
    """
    # 使用之前的orm_multi数据库中的数据

    # 单表:查询每一个出版社出版的书籍个数
    res = Book.objects.values("publish_id").annotate(Count(\'nid\'))
    print(res)

    # ===示例一:查询每一个出版社的名称以及出版社的书籍个数
    """原生SQL
    select     app01_publish.name,Count(app01_book.publish_id)
    from       app01_book
    inner join app01_publish
    on         app01_book.publish_id = app01_publish.nid
    group by   app01_publish.name;
    """
    # ORM实现跨表分组查询 (这里的values完成了select 和 join的功能)
    res = Book.objects.values("publish__name").annotate(Count("publish_id"))
    # 或者(好几没有写SQL,之前把Count搞错了,聚合函数是统计个数,不需被统计字段的值是一样的,因为它统计的是整个该对象下的该字段的个数)
    res = Publish.objects.values("name").annotate(c_count=Count("book__title"))
    print(res)
    # <QuerySet [{\'publish__name\': \'成都中医药大学出版社\', \'publish_id__count\': 2}, {\'publish__name\': \'电子科技大学出版社\', \'publish_id__count\': 2}]>

    """有个小问题: 跨表分组查询之后可以取到任意字段
    对于两个多对一关联表分组统计过后,如果想显示任意一张表的其他字段,也是可以的!()非严格模式,但是注意:这样操作
    对于一对多查询,显示与 分组条件 在同一张表的其他字段是有意义的
    但是如果显示 多关系 字段 与 聚合结果,使用跨表分组查询是多此一举的
    如下:这样是多此一举"""
    res = Publish.objects.values("nid").annotate(c_count=Count("book__title")).values("c_count","book__title")
    print(res)

    # ===示例二:查询每一个作者的名字以及出版过的书籍的最高价格
    """SQL
    select      app01_author.name,Max(app01_book.price)
    from        app01_book
    inner join  app01_book_authors
    on          app01_book.nid = app01_book_authors.book_id
    inner join  app01_author
    on          app01_author.nid = app01_book_authors.author_id
    group by    app01_author.nid;

    """
    # 方式一
    res = Book.objects.values("authors__nid").annotate(max_price=Max("price")).values("authors__name",\'max_price\')
    print(res)
    """
    <QuerySet [{\'authors__name\': \'alex\', \'max_price\': Decimal(\'999.99\')},
               {\'authors__name\': \'egon\', \'max_price\': Decimal(\'999.99\')},
               {\'authors__name\': \'peiqi\', \'max_price\': Decimal(\'888.88\')},
               {\'authors__name\': None, \'max_price\': Decimal(\'222.55\')}]>

    """
    # 方式二
    res = Author.objects.values("nid").annotate(max_price=Max("book__price")).values("name","max_price")
    print(res)
    """
    <QuerySet [ {\'name\': \'alex\', \'max_price\': Decimal(\'999.99\')},
                {\'name\': \'egon\', \'max_price\': Decimal(\'999.99\')},
                {\'name\': \'peiqi\', \'max_price\': Decimal(\'888.88\')},
                {\'name\': \'yuan\', \'max_price\': None}]>
    """

    # === 示例三:查询每个书籍的名称以及对应的作者个数
    res = Book.objects.values("title").annotate(count_author=Count("authors__nid"))
    print(res)
    """{\'name\': \'peiqi\', \'max_price\': Decimal(\'888.88\')}, {\'name\': \'yuan\', \'max_price\': None}]>
<QuerySet [  {\'title\': \'Python\', \'count_author\': 2},
             {\'title\': \'JavaScript\', \'count_author\': 0},
             {\'title\': \'Vue\', \'count_author\': 1},
             {\'title\': \'Java\', \'count_author\': 1}]>
    原生SQL:
        select app01_book.title,Count(app01_author.nid)
        from        app01_book
        inner join  app01_book_authors
        on          app01_book.nid = app01_book_authors.book_id
        inner join  app01_author
        on          app01_author.nid = app01_book_authors.author_id
        group by    app01_book.title
        ;
    """

    # ======跨表分组查询的另一种玩儿法,可以根据多个字段组合进行分组
    # 示例
    res = Publish.objects.annotate(c_count=Count("book__title")).values("c_count","book__title")

    # ===== 小练习
    # 统计每一本以Py开头的书籍的作者的个数。
    res = Book.objects.filter(title__startswith="Py").annotate(count_author=Count("authors__nid")).values("title",\'count_author\')
    print(res)
    # <QuerySet [{\'title\': \'Python\', \'count_author\': 2}]>

    # 统计不止一个作者的书籍名称
    res = Book.objects.values("pk").annotate(count_book=Count("authors__name")).filter(count_book__gt=1).values("title")
    print(res) # <QuerySet [{\'title\': \'Python\'}]>
    """原生SQL
    SELECT            `app01_book`.`title`
    FROM              `app01_book`
    LEFT OUTER JOIN   `app01_book_authors`
    ON               (`app01_book`.`nid` = `app01_book_authors`.`book_id`)
    LEFT OUTER JOIN   `app01_author`
    ON               (`app01_book_authors`.`author_id` = `app01_author`.`nid`)
    GROUP BY          `app01_book`.`nid`
    HAVING COUNT     (`app01_author`.`name`) > 1
    ORDER BY NULL
    LIMIT 21;
    """

    return HttpResponse("OK")

4.F查询与Q查询

F查询

  • Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
  • Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。
  • 修改操作也可以使用F函数,比如将每一本书的价格提高1元:

Q查询

  • Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象
  • Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询
  • 查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。

app01/views.py

from django.shortcuts import render,HttpResponse
from app01.models import * # 最好这样引入视图函数,避免models调用重名覆盖!
from django.db.models import Avg,Max,Min,Count
def query(request):
    # === F查询与Q查询 ===
    # 为Book表出插入两个字段comment_num  read_num (最好在终端中添加)

    # 场景:比如要查询一个字段的值大于另一个字段值,查询Book 表comment_num > read_num 记录
    # 荒唐的写法:Book.objects.filter(comment_num__gt=read_num) 不支持!

    # F查询 将字段当成一个变量,可以查询
    from django.db.models import F
    res = Book.objects.filter(comment_num__gt=F("read_num")).values("title",\'comment_num\',\'read_num\')
    print(res)

    # 场景:把所有的书籍价格都提升1块钱
    Book.objects.all().update(price=F("price") + 1)


    # Q查询 将字段当成一个条件,可以嵌套
    from django.db.models import Q
    # 查询 title为Python 且 publish_id 为5 的nid
    res = Book.objects.filter(title="Python", publish_id=5).values(\'nid\')
    res = Book.objects.filter(Q(title="Python") & Q(publish_id=5)).values(\'nid\')

    # 查询 title为Python 或者 publish_id 为5 的nid
    res = Book.objects.filter(Q(title="Python") | Q(publish_id=5)).values(\'nid\')

    # 查询 title不等于 Python的书籍 。~ 非操作
    res = Book.objects.filter(~Q(title="Python")).values(\'title\')
    print(res)

    # 如果想过滤多个条件,要吧Q语句放在最前面
    res = Book.objects.filter(~Q(title="Python"),price__gt=200).values(\'title\')


    return HttpResponse("OK")

分类:

技术点:

相关文章: