在 Django 中使用带有 GROUP BY 子句的 COUNT(DISTINCT 字段)

Posted

技术标签:

【中文标题】在 Django 中使用带有 GROUP BY 子句的 COUNT(DISTINCT 字段)【英文标题】:Usage of a COUNT(DISTINCT field) with a GROUP BY clause in Django 【发布时间】:2013-08-29 09:18:30 【问题描述】:

问题

我想在 Django 中使用 COUNT(DISTINCT field)GROUP BY 子句。据我了解,COUNT(DISTINCT... 只能通过对查询集使用extra 来实现。

我的简化模型是:

class Site(models.Model):
    name = models.CharField(max_length=128, unique=True)

class Application(models.Model):
    name = models.CharField(max_length=64)
    version = models.CharField(max_length=13, db_index=True)

class User(models.Model):
    name = models.CharField(max_length=64) 
    site = models.ForeignKey(Site, db_index=True) 

class Device(models.Model):
    imei = models.CharField(max_length=16, unique=True)

    applications = models.ManyToManyField(Application, null=True, db_index=True, through='ApplicationUsage') 
    user = models.ForeignKey(User, null=True, db_index=True)

class ApplicationUsage(models.Model):
    activity = models.DateField(db_index=True)

    application = models.ForeignKey(Application)
    device = models.ForeignKey(Device)

我的目标是在给定一段时间内的应用程序活动的情况下,为每个站点创建一个包含不同设备计数的站点对象列表,例如

stats_site.name     deviceCount
ALBI                32
AMPLEPUIS           42
...

我试试这个代码:

qs = models.Site.objects.filter(user__device__applicationusage__activity__range=[startDay, endDay])\
                            .extra(select='deviceCount' : 'COUNT(DISTINCT `stats_device`.`id`)')\
                            .values('name', 'deviceCount')\

生成的SQL是:

SELECT (COUNT(DISTINCT stats_device.id)) AS deviceCount, stats_site.name
FROM stats_site
INNER JOIN stats_user ON (stats_site.id = stats_user.site_id)
INNER JOIN stats_device ON (stats_user.id = stats_device.user_id)
INNER JOIN stats_applicationusage ON (stats_device.id = stats_applicationusage.device_id)
WHERE stats_applicationusage.activity BETWEEN '2013-07-01' AND '2013-07-03'

而且结果显然是错误的,因为它缺少GROUP BY 子句,应该是GROUP BY stats_site.name

问题是:我不知道如何使用annotate 函数或其他方法添加正确的GROUP BY

解决方案

Count 函数上使用distinct=Trueannotate

qs = models.Site.objects.filter(habileouser__device__applicationusage__activity__range=[startDay, endDay])\
                            .annotate(deviceCount=Count('habileouser__device', distinct=True))\
                            .values('name', 'deviceCount')

【问题讨论】:

就个人而言,我会在一个查询中将它们全部获取,然后使用 python 进行计数,但是,您是否查看过聚合? docs.djangoproject.com/en/dev/topics/db/aggregation 是的,但是聚合只会为整个查询集提供一个值,所以它不起作用。对于使用 Python 进行计数,最终它可能是解决方案,但前提是我找不到仅使用 SQL 计数的更好方法 【参考方案1】:

查询集的annotate 方法将为查询集的每个元素计算一个聚合值,当在values 调用之后使用时,将对这些值的值进行聚合。我认为这应该可行:

qs = models.Site.objects.filter(user__device__applicationusage__activity__range=[startDay, endDay]).values('name').annotate(Count('user__device', distinct=True))

如果您指定了订单,则可能需要按此处所述将其删除: https://docs.djangoproject.com/en/dev/topics/db/aggregation/#interaction-with-default-ordering-or-order-by

【讨论】:

感谢您的回答。但是,您提出的解决方案会计算所有设备,甚至是重复的设备。关键是计算不同设备的数量。添加 distinct() 将不起作用,因为 distinct 也将应用于 select 子句中的所有字段,甚至是计数 根据这个答案,Count 聚合可以采用distinct=True 参数 - 我以前不知道这一点。 ***.com/questions/4048014/…

以上是关于在 Django 中使用带有 GROUP BY 子句的 COUNT(DISTINCT 字段)的主要内容,如果未能解决你的问题,请参考以下文章

mysql使用带有子查询的临时表,但不是group by和order by

带有注释的Django查询集,为啥将GROUP BY应用于所有字段?

如何在 Django 中通过 group by 获得额外的列?

LINQ Group By并将Group的子列表合并回唯一列表

如何在 django orm 中使用 Annotation 进行嵌套 Group By?

django的group_by