1. 程式人生 > Django入門教學 >20 Django 中 ORM 的聚合函式

20 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 語句和執行結果如下:

圖片描述

avg 聚合函式
  • 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 模組。