Django models 詳解之聚合查詢(xún)(aggregate)與分組查詢(xún)(annotate)

一、測(cè)試代碼及數(shù)據(jù)

models.py 代碼

from django.db import models


class Author(models.Model):
    name = models.CharField(max_length=100)
    age = models.IntegerField()


class Book(models.Model):
    name = models.CharField(max_length=300)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    authors = models.ManyToManyField(Author)
    pubdate = models.DateField()

測(cè)試數(shù)據(jù)

authors:

[
    {
        "id": 1,
        "name": "路人甲",
        "age": 10
    },
    {
        "id": 2,
        "name": "路人乙",
        "age": 18
    },
    {
        "id": 3,
        "name": "路人丙",
        "age": 28
    },
    {
        "id": 4,
        "name": "路人丁",
        "age": 50
    }
]

books:

[
    {
        "id": 1,
        "name": "人之初",
        "price": "38.80",
        "pubdate": "2020-12-01",
        "authors": [
            1
        ]
    },
    {
        "id": 2,
        "name": "性本善",
        "price": "28.40",
        "pubdate": "2020-06-01",
        "authors": [
            2
        ]
    },
    {
        "id": 3,
        "name": "性相近",
        "price": "15.20",
        "pubdate": "2019-10-01",
        "authors": [
            3
        ]
    },
    {
        "id": 4,
        "name": "習(xí)相遠(yuǎn)",
        "price": "35.20",
        "pubdate": "2019-07-01",
        "authors": [
            4
        ]
    },
    {
        "id": 5,
        "name": "茍不教",
        "price": "5.20",
        "pubdate": "2018-07-01",
        "authors": [
            1,
            3,
            4
        ]
    },
    {
        "id": 6,
        "name": "性乃遷",
        "price": "55.20",
        "pubdate": "2018-12-01",
        "authors": [
            2,
            3,
            4
        ]
    },
    {
        "id": 7,
        "name": "教之道",
        "price": "33.20",
        "pubdate": "2018-12-23",
        "authors": [
            2,
            3
        ]
    },
    {
        "id": 8,
        "name": "貴以專(zhuān)",
        "price": "27.20",
        "pubdate": "2017-12-23",
        "authors": [
            1,
            4
        ]
    }
]

二、常用聚合操作

獲取所有書(shū)籍的數(shù)量:

>>> Book.objects.count()
8

獲取由路人甲參與著作的所有書(shū)籍的數(shù)量:

>>> Book.objects.filter(authors__name__contains='路人甲').count()
3

獲取所有書(shū)籍的平均價(jià)格:

>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': Decimal('29.800000')}

獲取所有書(shū)籍中的最高價(jià)格:

>>> Book.objects.all().aggregate(Max('price'))
{'price__max': Decimal('55.20')}
涉及到一對(duì)多或多對(duì)多關(guān)系的聚合查詢(xún)

計(jì)算每一位作者各自參與寫(xiě)作了多少本書(shū):

>>> from django.db.models import Count
>>> authors=Author.objects.annotate(num_books=Count('book'))
>>> authors
<QuerySet [<Author: Author object (1)>, <Author: Author object (2)>, <Author: Author object (3)>, <Author: Author object (4)>]>
>>> authors[0].num_books
3
>>> authors.values_list('name', 'num_books')
<QuerySet [('路人甲', 3), ('路人乙', 3), ('路人丙', 4), ('路人丁', 4)]>

即作者包含路人甲的書(shū)籍有3本,以此類(lèi)推。

計(jì)算每一位作者各自參與寫(xiě)作的書(shū)籍?dāng)?shù)量,根據(jù)書(shū)籍出版年份是否在2020年以前分界:

>>> from django.db.models import Q
>>> before_2020 = Count('book', filter=Q(book__pubdate__lt='2020-01-01'))
>>> after_2020 = Count('book', filter=Q(book__pubdate__gt='2020-01-01'))
>>> authors = Author.objects.annotate(before_2020=before_2020).annotate(after_2020=after_2020)
>>> authors
<QuerySet [<Author: Author object (1)>, <Author: Author object (2)>, <Author: Author object (3)>, <Author: Author object (4)>]>
>>> authors[0].before_2020
2
>>> authors.values_list('name', 'before_2020', 'after_2020')
<QuerySet [('路人甲', 2, 1), ('路人乙', 2, 1), ('路人丙', 4, 0), ('路人丁', 4, 0)]>

即作者包含路人甲的書(shū)籍,2020年以前出版的有2本,2020年以后出版的有1本。以此類(lèi)推。

獲取每一位作者各自參與著作的書(shū)籍?dāng)?shù)量,將輸出結(jié)果按書(shū)籍?dāng)?shù)量由大到小的順序排序:

>>> authors = Author.objects.annotate(num_books=Count('book')).order_by('-num_books')
>>> authors
<QuerySet [<Author: Author object (3)>, <Author: Author object (4)>, <Author: Author object (1)>, <Author: Author object (2)>]>
>>> authors.values_list('name', 'num_books')
<QuerySet [('路人丙', 4), ('路人丁', 4), ('路人甲', 3), ('路人乙', 3)]>

三、aggregate

在聚合查詢(xún)中,Django 支持通過(guò) aggregate() 方法從整個(gè) QuerySet 中計(jì)算出一個(gè)匯總數(shù)據(jù)。如獲取所有書(shū)籍的平均價(jià)格:

>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': Decimal('29.800000')}

上述語(yǔ)句中的 all() 可以省略。aggregate() 的參數(shù)表示我們想要做聚合計(jì)算的那一列數(shù)據(jù),其中的 'price' 即表示 Book 模型的 price 字段。

aggregate() 對(duì)于 QuerySet 來(lái)說(shuō)是一種終止語(yǔ)句,會(huì)返回字典形式的鍵值對(duì)作為計(jì)算結(jié)果。其中的鍵會(huì)根據(jù)聚合的字段自動(dòng)生成,也可以手動(dòng)指定:

>>> Book.objects.all().aggregate(average_price=Avg('price'))
{'average_price': Decimal('29.800000')}

如果想要同時(shí)完成多個(gè)聚合查詢(xún)操作,可以為 aggregate() 添加多個(gè)參數(shù):

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

四、annotate

借助 annotate() 方法,Django 可以從 QuerySet 的每一個(gè)對(duì)象中計(jì)算出對(duì)應(yīng)的獨(dú)立的匯總數(shù)據(jù)。比如想獲得 Book 模型中每一本書(shū)的作者的數(shù)量:

>>> from django.db.models import Count
>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q
<QuerySet [<Book: Book object (1)>, <Book: Book object (2)>, <Book: Book object (3)>, <Book: Book object (4)>, <Book: Book object (5)>, <Book: Book object (6)>, <Book: Book object (7)>, <Book: Book object (8)>]>
>>> q[0].num_authors
1
>>> q.values_list('name', 'num_authors')
<QuerySet [('人之初', 1), ('性本善', 1), ('性相近', 1), ('習(xí)相遠(yuǎn)', 1), ('茍不教', 3), ('性乃遷', 3), ('教之道', 2), ('貴以專(zhuān)', 2)]>

不同于 aggregate(),annotate() 對(duì)于 QuerySet 來(lái)說(shuō)并不是終止語(yǔ)句,annotate() 方法的輸出結(jié)果仍是 QuerySet 對(duì)象。該對(duì)象可以繼續(xù)執(zhí)行被 QuerySet 支持的任意操作,如 filter()order_by() 等,甚至另一個(gè) annotate()

五、join & aggregate

某些情況下,你想要聚合的字段并不屬于當(dāng)前正在查詢(xún)的模型,而是屬于關(guān)聯(lián)于當(dāng)前模型的另一個(gè)模型。在對(duì)這些字段進(jìn)行聚合查詢(xún)時(shí),Django 允許使用與 filter() 中相同的用于指定關(guān)聯(lián)字段的雙下劃線語(yǔ)法。

比如想要獲取每一位作者所著書(shū)籍的價(jià)格區(qū)間:

>>> from django.db.models import Max, Min
>>> authors = Author.objects.annotate(min_price=Min('book__price'), max_price=Max('book__price'))
>>> authors.values_list('name', 'min_price', 'max_price')
<QuerySet [('路人甲', Decimal('5.20'), Decimal('38.80')), ('路人乙', Decimal('28.40'), Decimal('55.20')), ('路人丙', Decimal('5.20'), Decimal('55.20')), ('路人丁', Decimal('5.20'), Decimal('55.20'))]>

即作者為路人甲的書(shū)籍中,最低的價(jià)格為 5.20,最高的價(jià)格為 38.80。

六、filter() 或 order_by() 應(yīng)用到 annotate()

如查找所有多人合著(作者數(shù)量大于 1)的書(shū)籍列表:

>>> books = Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
>>> books
<QuerySet [<Book: Book object (5)>, <Book: Book object (6)>, <Book: Book object (7)>, <Book: Book object (8)>]>
>>> books.values_list('name', 'num_authors')
<QuerySet [('茍不教', 3), ('性乃遷', 3), ('教之道', 2), ('貴以專(zhuān)', 2)]>

根據(jù)作者數(shù)量對(duì)全部書(shū)籍進(jìn)行排序:

>>> books = Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
>>> books
<QuerySet [<Book: Book object (2)>, <Book: Book object (4)>, <Book: Book object (1)>, <Book: Book object (3)>, <Book: Book object (8)>, <Book: Book object (7)>, <Book: Book object (5)>, <Book: Book object (6)>]>
>>> books.values_list('name', 'num_authors')
<QuerySet [('性本善', 1), ('習(xí)相遠(yuǎn)', 1), ('人之初', 1), ('性相近', 1), ('教之道', 2), ('貴以專(zhuān)', 2), ('茍不教', 3), ('性乃遷', 3)]>

參考資料

Django 官方文檔 —— Aggregation

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容