将 DISTINCT 与多个条件聚合一起使用(注释)

Posted

技术标签:

【中文标题】将 DISTINCT 与多个条件聚合一起使用(注释)【英文标题】:Using DISTINCT with Multiple Condtional Aggregations (Annotate) 【发布时间】:2020-04-30 10:09:35 【问题描述】:

Python:2.7 版本:Django 1.11

你好,

我在 Django 查询中使用带有 COUNT DISTINCT 的条件聚合时遇到问题。 问题是当我加入一个单独的表时,我对 ID 的不同计数似乎不起作用。 我有一个查询,我在其中进行条件聚合以根据其他属性计算天数和总分钟数。

在下面的例子中,我们要同时查询两个东西:

每只狗的“记录的晴天数”计数。 我们遛每只狗的总时间

(请耐心等待我的例子。我尝试简化模型)

型号:

from django.db import models

class Dog(models.Model):
    name = models.CharField(max_length=255)

class DailyLog(models.Model):
    dog = models.ForeignKey(Dog, on_delete=models.CASCADE)
    is_sunny = models.BooleanField(default=False)

class WalkSession(models.Model):
    daily_log = models.ForeignKey(DailyLog, on_delete=models.CASCADE)
    minutes_walked = models.IntegerField()

通过迁移填充数据:

 d1 = Dog.objects.create(name="Fido")
 d2 = Dog.objects.create(name="Fido2")
 d3 = Dog.objects.create(name="Fido3")

 dl1 = DailyLog.objects.create(dog=d1, is_sunny=True)
 dl2 = DailyLog.objects.create(dog=d2, is_sunny=False)
 dl3 = DailyLog.objects.create(dog=d3, is_sunny=False)

 WalkSession.objects.create(daily_log=dl1, minutes_walked=100)
 WalkSession.objects.create(daily_log=dl1, minutes_walked=200)
 WalkSession.objects.create(daily_log=dl2, minutes_walked=50)
 WalkSession.objects.create(daily_log=dl3, minutes_walked=999)

Python 控制台:

简单检查总分钟数。

   DailyLog.objects.all().values('dog__name').annotate(total_minutes_walked=Sum('walksession__minutes_walked'))

Result: <QuerySet ['dog__name': 'Fido', 'total_minutes_walked': 300,
     'dog__name': 'Fido2', 'total_minutes_walked': 50,
     'dog__name': 'Fido3', 'total_minutes_walked': 999]>

简单检查记录的晴天数。

DailyLog.objects.all().values('dog__name').annotate(sunny_days_logged=Count(Case(When(is_sunny=True, then='id'), distinct=True)))

Result: <QuerySet ['dog__name': 'Fido', 'sunny_days_logged': 1,
     'dog__name': 'Fido2', 'sunny_days_logged': 0,
     'dog__name': 'Fido3', 'sunny_days_logged': 0]>

使用条件聚合连接 DailyLog 表和 WalkSession 的查询。

我们现在看到记录的晴天是“2”。我们预计这是“1”。

DailyLog.objects.all().values('dog__name').annotate(total_minutes_walked=Sum('walksession__minutes_walked'), sunny_days_logged=Count(Case(When(is_sunny=True, then='id'), distinct=True)))

Result: <QuerySet ['dog__name': 'Fido', 'total_minutes_walked': 300, 'sunny_days_logged': 2,
     'dog__name': 'Fido2', 'total_minutes_walked': 50, 'sunny_days_logged': 0,
     'dog__name': 'Fido3', 'total_minutes_walked': 999, 'sunny_days_logged': 0]>

我查看了生成的查询,似乎在我们使用 CASE WHEN 时删除了 DISTINCT 选项。

SELECT dogwalker_dog.name,
        SUM(dogwalker_walksession.minutes_walked) AS 'total_minutes_walked',
        COUNT(CASE
              WHEN dogwalker_dailylog.is_sunny = true THEN dogwalker_dailylog.id ELSE NULL END) AS 'sunny_days_logged'
    FROM dogwalker_dailylog
    INNER JOIN dogwalker_dog
    ON dogwalker_dailylog.dog_id = dogwalker_dog.id
    LEFT OUTER JOIN dogwalker_walksession
    ON dogwalker_dailylog.id = dogwalker_walksession.daily_log_id
GROUP BY dogwalker_dog.name
COUNT 中缺少 DISTINCT。 COUNT(DISTINCT 案例 WHEN dogwalker_dailylog.is_sunny = true THEN dogwalker_dailylog.id ELSE NULL END) AS 'sunny_days_logged'

文档确实说多个聚合可能会显示错误的结果。

参考:https://docs.djangoproject.com/en/1.11/topics/db/aggregation/#combining-multiple-aggregations 我试图使用 DISTINCT 参数来帮助解决这个问题。

为什么在使用 CASE WHEN 时会丢弃 DISTINCT? 最好将查询分开而不是尝试在一个查询中计算多项内容?

【问题讨论】:

【参考方案1】:

我在括号上的错误和缺少选项 output_field 选项。

下面的语句为每只狗提供了正确的晴天数。

DailyLog.objects.all().values('dog__name').annotate(total_minutes_walked=Sum('walksession__minutes_walked'), sunny_days_logged=Count(Case(When(is_sunny=True, then='id'), output_field=IntegerField()), distinct=True))

【讨论】:

以上是关于将 DISTINCT 与多个条件聚合一起使用(注释)的主要内容,如果未能解决你的问题,请参考以下文章

如何将案例条件与聚合一起使用?

想要将多个聚合函数与雪花枢轴列函数一起使用

mongoDB应用篇-mongo聚合查询

将 SUMIFS 与多个 AND OR 条件一起使用

将 SELECT DISTINCT ON 与 OrmLite 一起使用

将 DISTINCT 与 FIND_IN_SET 一起使用