使用 Django 查询集获取每个组的前 n 条记录

sad*_*dat 2 django django-orm

我有一个如下表所示的模型,

create table `mytable`
(
  `person` varchar(10),
  `groupname` int,
  `age` int
);
Run Code Online (Sandbox Code Playgroud)

我想从每组中选出 2 个最年长的人。原始的 SQL 问题和答案在这里StackOverflow,有效的解决方案之一是

SELECT
    person,
    groupname,
    age
FROM
(
    SELECT
        person,
        groupname,
        age,
        @rn := IF(@prev = groupname, @rn + 1, 1) AS rn,
        @prev := groupname
    FROM mytable
    JOIN (SELECT @prev := NULL, @rn := 0) AS vars
    ORDER BY groupname, age DESC, person
) AS T1
WHERE rn <= 2
Run Code Online (Sandbox Code Playgroud)

您也可以在此处检查 SQL 输出以及SQLFIDLE

我只是想知道如何在 Django 视图中将这个查询实现为查询集。

wia*_*erb 5

具有类似输出的另一个 SQL 将具有窗口函数,该函数使用特定组名称中的行号来注释每一行,然后您将在子句中过滤小于或等于 2 的行号HAVING

在撰写本文时,django不支持基于窗口函数结果的过滤,因此您需要在第一个查询中计算行并People在第二个查询中进行过滤。

以下代码基于类似的问题,但它实现了每个返回的行数限制group_name

from django.db.models import F, When, Window
from django.db.models.functions import RowNumber

person_ids = {
    pk
    for pk, row_no_in_group in Person.objects.annotate(
        row_no_in_group=Window(
            expression=RowNumber(), 
            partition_by=[F('group_name')],
            order_by=['group_name', F('age').desc(), 'person']
        )
    ).values_list('id', 'row_no_in_group')
    if row_no_in_group <= 2
}
filtered_persons = Person.objects.filter(id__in=person_ids)
Run Code Online (Sandbox Code Playgroud)

Person对于表的以下状态

>>> Person.objects.order_by('group_name', '-age', 'person').values_list('group_name', 'age', 'person')
<QuerySet [(1, 19, 'Brian'), (1, 17, 'Brett'), (1, 14, 'Teresa'), (1, 13, 'Sydney'), (2, 20, 'Daniel'), (2, 18, 'Maureen'), (2, 14, 'Vincent'), (2, 12, 'Carlos'), (2, 11, 'Kathleen'), (2, 11, 'Sandra')]>
Run Code Online (Sandbox Code Playgroud)

上面的查询返回

>>> filtered_persons.order_by('group_name', '-age', 'person').values_list('group_name', 'age', 'person')
<QuerySet [(1, 19, 'Brian'), (1, 17, 'Brett'), (2, 20, 'Daniel'), (2, 18, 'Maureen')]>
Run Code Online (Sandbox Code Playgroud)