Django 中 ORM 的聚合函數
在介紹 Django 中 ORM 模型的聚合函數之前,我們先要了解下 MySQL 中常用的聚合函數。首先同樣是準備數據,使用我i們之前在第 18 小節中完成的插入 100 條數據的代碼,重新執行一次:
(django-manual) [root@server test]# python insert_records.py
批量插入完成
此時,連同上次操作剩余的兩條會員記錄,數據庫中總共有 102 條數據:
1. MySQL 中的聚合操作
聚合函數(aggregation function)又稱為組函數。默認情況下聚合函數會對當前所在表當做一個組進行統計。MySQL5.7 中支持的聚合函數如下:
在這些聚合函數中,我們比較常用的有 AVG、COUNT、MAX、MIN、SUM 等。下面重點介紹這幾個聚合函數:
-
AVG():使用格式如下,函數返回expr的平均值,DISTINCT 則用于返回 expr 的不同值的平均值。如果沒有匹配的行,AVG() 返回 NULL。
AVG([DISTINCT] expr)
例如,我們使用 AVG() 函數計算每個職業的會員的平均年齡,其 SQL 語句和執行結果如下:
-
COUNT():使用格式如下,返回 SELECT 語句檢索的行中 expr 的非NULL值的計數。返回結果是 BIGINT 值。如果沒有匹配的行,count()返回0。
COUNT(expr)
注意: 我們常用的 COUNT(*),其返回取回的記錄數,無論它們是否包含 NULL 值。
例如,這里我們計算出每個職業的會員數,其 SQL 語句和執行結果如下:
count 函數 -
COUNT(DISTINCT …):使用格式如下,該函數返回不相同且非 NULL 的 expr 值的行數。如果沒有匹配的行,則 COUNT(DISTINCT) 返回0。
COUNT(DISTINCT expr,[expr...])
-
GROUP_CONCAT():使用格式如下,這個函數把來自同一個組的某一列(或者多列)的數據連接起來成為一個字符串。如果沒有非 NULL 值,返回 NULL;
GROUP_CONCAT([DISTINCT] expr [,expr ...] [ORDER BY {unsigned_integer | col_name | expr} [ASC | DESC] [,col_name ...]] [SEPARATOR str_val])
例如,這里我們將每個職業的會員的年齡連接到一起,其 SQL 語句和執行結果如下:
group_concat函數 -
SUM() / MAX() / MIN():這幾個聚合函數和 AVG() 函數用法幾乎一致,計算某列的和/最大值/最小值,也可以使用
GROUP BY
分組計算:SELECT occupation, SUM(age) FROM member WHERE 1=1 GROUP BY occupation; SELECT occupation, MAX(age) FROM member WHERE 1=1 GROUP BY occupation; SELECT occupation, MIN(age) FROM member WHERE 1=1 GROUP BY occupation;
2. Django 內嵌 ORM 模型的聚合操作
在 Django 中聚合函數是通過 aggregate 方法實現的,aggregate 方法返回的結果是一個字典。其支持的聚合函數如下:
# 源碼位置 django/db/models/aggregates.py
...
__all__ = [
'Aggregate', 'Avg', 'Count', 'Max', 'Min', 'StdDev', 'Sum', 'Variance',
]
...
注意:第一個是基類,從 Avg 開始,是支持的聚合方法,每個聚合方法的處理對應著一個類,而這些類分別繼承自 Aggregate 類。
aggregate 方法的使用也非常簡單,只需要在該方法內添加需要執行的聚合函數即可,同時我們還可以打印出聚合函數執行的 SQL 語句,具體操作如下:
(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from hello_app.models import Member
>>> from django.db.models import Avg, Max, Min, Sum
>>> from django.db import connection
>>> Member.objects.all().aggregate(avg_age=Avg('age'),sum_age=Sum('age'), max_age=Max('age'), min_age=Min('age'))
{'avg_age': 29.392156862745097, 'sum_age': 2998.0, 'max_age': '40', 'min_age': '20'}
>>> print(connection.queries[-1]['sql'])
SELECT AVG(`member`.`age`) AS `avg_age`, SUM(`member`.`age`) AS `sum_age`, MAX(`member`.`age`) AS `max_age`, MIN(`member`.`age`) AS `min_age` FROM `member`
注意:connection.queries
中保存的是最近執行的 SQL 語句,我們在執行完 Django 的 ORM 操作后,可以取出最后一次執行的 SQL 語句進行查看。此外,對于聚合的函數,如果我們不知道屬性名,則會有默認值:字段__聚合函數名
。
>>> from django.db.models import Count
>>> Member.objects.all().aggregate(Count('age', distinct=True))
{'age__count': 21}
>>> print(connection.queries[-1]['sql'])
SELECT COUNT(DISTINCT `member`.`age`) AS `age__count` FROM `member`
相比前面在 MySQL 中執行聚合函數,我們這里缺少一個 GROUP BY
功能。如果想要對數據庫中的記錄先分組然后再進行某些聚合操作或排序時,需要使用 annotate 方法來實現。與 aggregate 方法不同的是,annotate 方法返回結果的不僅僅是含有統計結果的一個字典,而是包含有新增統計字段的查詢集 (QuerySet)。下面是實現分組聚合的實例操作:
>>> from django.db.models import Count, Avg, Sum, Max, Min
>>> Member.objects.values('occupation').annotate(count=Count('age')).order_by('-count')
<QuerySet [{'occupation': 'security', 'count': 15}, {'occupation': 'ui', 'count': 15}, {'occupation': 'product', 'count': 14}, {'occupation': 'leader', 'count': 14}, {'occupation': 'ops', 'count': 14}, {'occupation': 'web', 'count': 12}, {'occupation': 'teacher', 'count': 8}, {'occupation': 'server', 'count': 8}, {'occupation': 'java', 'count': 1}, {'occupation': 'c/c++', 'count': 1}]>
>>> print(connection.queries[-1]['sql'])
SELECT `member`.`occupation`, COUNT(`member`.`age`) AS `count` FROM `member` GROUP BY `member`.`occupation` ORDER BY `count` DESC LIMIT 21
注意:上面的操作有如下說明:
- annotate 方法前面的 values 中出現的字段正是需要 GROUP BY 的字段。values 方法中出現多個值,即對多個字段進行 GROUP BY;
- annotate 方法的結果是一個查詢集 (QuerySet),這樣我們可以繼續在后面盜用 filter()、order_by() 等方法進行進一步過濾結果;
- order_by 方法是對前面的 QuerySet 按某些字段排序,類似于 SQL 中的 ORDER BY 操作。排序字段前面加上 “-” 表示按倒序順序,類似于 DESC 操作
3. 小結
本小節中,我們介紹了 MySQL 中的常用的聚合操作,然后在介紹在 Django 中 ORM 模型對應的聚合函數。關于Django 的內置 ORM 模型的介紹到這里就結束了。接下來將介紹 Django 給我們提供的一個完整的后臺管理系統功能-Admin 模塊。