聚合查询与分组查询

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")

posted @ 2018-07-22 17:14  哈哈大圣  阅读(1117)  评论(0编辑  收藏  举报