Django - 按列分组,在不同的列上

时间:2016-10-02 17:20:53

标签: sql django postgresql django-models django-orm

这些是我的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

Queryset注释

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

返回

[{'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;

返回

    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;

返回正确结果

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

1 个答案:

答案 0 :(得分:1)

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

解决问题的原始评论:

  

首先必须说清楚:你确定你有吗?   上个月创建的已关闭或打开的机票请求?那会   是最明显的原因。但如果你已经加倍了   三重检查,然后我有一个类似的模糊记忆   问题在哪里,但不记得为什么。但我通过改变来修复它   Count()到Sum(Case(当(set__the__condition ='set_the_value')时,   then = 1),default = 0,output_field = models.IntegerField()))。