django group by
概要
如何用 django 自帶的 ORM 框架來完成 SQL 中的 group by 操作呢?
環境介紹
foo 這個應用定義了如下模型。
class PersonModel(models.Model): name = models.CharField('姓名', max_length=64) age = models.PositiveIntegerField('年齡') def __str__(self): return f"{self.name} - {self.age}"
數據庫層面看到的表結構如下。
CREATE TABLE `foo_personmodel` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(64) COLLATE utf8mb4_general_ci NOT NULL, `age` int(10) unsigned NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB
表中的數據如下。
mysql> select * from foo_personmodel; +----+-------+-----+ | id | name | age | +----+-------+-----+ | 1 | tim | 16 | | 2 | jerry | 27 | | 3 | marry | 18 | | 4 | tim | 20 | | 5 | alis | 17 | +----+-------+-----+ 5 rows in set (0.00 sec)
全表聚合
一個常見的需求,查詢一下表中有多少行數據。
SQL 寫法。
mysql> select count(id) as counts from foo_personmodel; +--------+ | counts | +--------+ | 5 | +--------+ 1 row in set (0.00 sec)
ORM 實現。
from django.db.models import Count from apps.foo.models import PersonModel PersonModel.objects.aggregate(counts = Count(id)) {'counts': 5}
注意 aggregate 返回的不再是 queryset 而是一個字典。
values 方法
要查詢出每一行 name 列的取值。
SQL 寫法。
mysql> select name from foo_personmodel; +-------+ | name | +-------+ | tim | | jerry | | marry | | tim | | alis | +-------+ 5 rows in set (0.00 sec)
ORM 實現。
PersonModel.objects.values("name") <QuerySet [{'name': 'tim'}, {'name': 'jerry'}, {'name': 'marry'}, {'name': 'tim'}, {'name': 'alis'}]>
django 會返回一個 queryset 並且不會去重。
分組聚合
查詢同一個名字在表中出現了多少次。
SQL 寫法。
mysql> select name,count(id) as counts from foo_personmodel group by name; +-------+--------+ | name | counts | +-------+--------+ | tim | 2 | | jerry | 1 | | marry | 1 | | alis | 1 | +-------+--------+ 4 rows in set (0.00 sec)
ORM 寫法。
PersonModel.objects.values("name").annotate(counts=Count(id)) <QuerySet [{'name': 'tim', 'counts': 2}, {'name': 'jerry', 'counts': 1}, {'name': 'marry', 'counts': 1}, {'name': 'alis', 'counts': 1}]>
神奇的事情發生了,當 values 和 annotate 一起使用的時候,values 就承擔起了 group by 的角色。並且自動去掉了重項!
結論
django 中並沒有為 group by 設置單獨的方法,而是通過 values + annotate 的組合來實現的。