Django - 按列分组以及在不同列上的位置

Django - Group by column and where on a different column

这些是我的 django 模型(简化):

class Status(models.Model):
    status = models.CharField(max_length=20)

class Project(models.Model):
    client = models.ForiegnKey(Client)

class TicketRequest(models.Model):
    status = models.ForiegnKey(Status, related_name='ticket_requests')
    project = models.ForiegnKey(Project)
    created = models.DateTimeField()

要求的结果

status.value  |  client.id  |  count
--------------+-------------+---------
is_assigned   |           4 |      2
is_closed     |           4 |      66
is_open       |           4 |      7

查询集注释

Status.objects.filter(
    ticket_requests__project__client_id=4
).values('value').annotate(
    count=Count('ticket_requests__project__client')
)

returns

[{'count': 2, 'value': u'is_assigned'}, {'count': 66, 'value': u'is_closed'}, {'count': 7, 'value': u'is_open'}]

这正是所需要的。

但我需要使用今天、本周和月份等过滤器在 TicketRequest.created 上过滤查询集。

由于这些过滤器需要大量重复使用,我创建了一个方便的助手:

def qs_time_range(qs, time_range, field_name):
    now = timezone.now()

    if time_range == 'month':
        past = now - timedelta(days=30)

    return qs.filter(
        **{field_name + '__date__range': [past, now]}
    )

问题

当我使用助手过滤工单请求时,结果不是我所期望的。

data = filters.qs_time_range(
    Status.objects.filter(ticket_requests__project__client_id=4), 
    'month', 
    'ticket_requests__created'
).values('value').annotate(
    count=Count('ticket_requests__project__client')
)

结果

[{'count': 27, 'value': u'is_assigned'}]

结果应该是这样:

[{'count': 3, 'value': u'is_assigned'}, {'count': 3, 'value': u'is_closed'}, {'count': 3, 'value': u'is_open'}]

问题:过滤是否弄乱了 SQL?我应该尝试什么?


附加信息 - 原始 SQL

data.query 揭示了这个 SQL(为 Postgres 修改)

SELECT "tickets_status"."value",
       COUNT("projects_project"."client_id") AS "count"
FROM "tickets_status"
INNER JOIN "tickets_ticketrequest" ON ("tickets_status"."id" = "tickets_ticketrequest"."status_id")
INNER JOIN "projects_project" ON ("tickets_ticketrequest"."project_id" = "projects_project"."id")
INNER JOIN "tickets_ticketrequest" T5 ON ("tickets_status"."id" = T5."status_id")
WHERE ("projects_project"."client_id" = 4
       AND T5."created"::date BETWEEN '2016-09-02' AND '2016-10-02')
GROUP BY "tickets_status"."value",
         "tickets_status"."name"
ORDER BY "tickets_status"."name" ASC;

哪个returns

    value    | count 
-------------+-------
 is_assigned |    27
(1 row)

但这是我想要生成的 SQL:

SELECT "tickets_status"."value",
       COUNT("projects_project"."client_id") AS "count"
FROM "tickets_status"
INNER JOIN "tickets_ticketrequest" ON ("tickets_status"."id" = "tickets_ticketrequest"."status_id")
INNER JOIN "projects_project" ON ("tickets_ticketrequest"."project_id" = "projects_project"."id")
WHERE ("projects_project"."client_id" = 4
        AND "tickets_ticketrequest"."created"::date BETWEEN '2016-09-02' AND '2016-10-02')
GROUP BY "tickets_status"."value",
         "tickets_status"."name"
ORDER BY "tickets_status"."name" ASC;

哪个returns 正确的结果

    value    | count 
-------------+-------
 is_assigned |     3
(3 rows)

很高兴听到你的东西开始工作了,但这个答案是这个魔法背后的推理的 1 个解释。我记得这行得通,但如果有人想出为什么行得通的解释,那才是真正正确的答案。但是,是的,如果 django.db.models.Count 没有按预期工作,您可以将其替换为 Sum(Case(When(field='value'), then=1), default=0, output_field=models.IntegerField()) (在 Django >= 1.9 中)的组合。

原来解决问题的评论:

First of all have to state the obvious: are you sure you have any closed or open ticket requests created in the last month? That would be the most obvious cause. But if you already double- and triple-checked that, then I have a vague memory of having a similar problem somewhere, but can't remember why. But I fixed it by changing Count() to Sum(Case(When(set__the__condition='set_the_value'), then=1), default=0, output_field=models.IntegerField())).