Использование DISTINCT с несколькими условными агрегатами (Annotate) - PullRequest
2 голосов
/ 13 января 2020

Python: 2.7 Версия: Django 1.11

Здравствуйте,

У меня проблемы с использованием условного агрегирования с COUNT DISTINCT в моем запросе Django. Проблема в том, что, когда я присоединяюсь к отдельной таблице, мой подсчет идентификаторов не работает. У меня есть запрос, в котором я делаю условное агрегирование для подсчета дней и суммы минут на основе других атрибутов.

В приведенном ниже примере мы хотим запросить две вещи одновременно:

  • Количество «зарегистрированных солнечных дней» для каждой собаки.
  • Общее количество времени, которое мы гуляли с каждой собакой

(Пожалуйста, потерпите меня на примере Я пытался упростить модели)

Модели:

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}]>

Я посмотрел на сгенерированные запросы, и кажется, что опция DISTINCT пропускается при использовании CASE WHEN.

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
  • DISTINCT отсутствует в COUNT.
  • COUNT ( DISTINCT СЛУЧАЙ, КОГДА dogwalker_dailylog.is_sunny = true, ТОГДА dogwalker_dailylog.id, Иначе NULL END) AS 'sunny_days_logged'
  • Почему DISTINCT сбрасывается при использовании CASE WHEN?
  • Было бы лучше разделить запросы, а не пытаться вычислить несколько вещей в одном запросе?

1 Ответ

1 голос
/ 16 января 2020

Моя ошибка в скобках и пропущенная опция output_field option.

Приведенное ниже утверждение дает правильное количество солнечных дней для каждой собаки.

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))
...