Django query - "case when" с функцией агрегации

у меня есть следующая модель django (сопоставленная с таблицей 'A'):

class A(models.Model):
    name = models.CharField(max_length=64, null=False)
    value = models.IntegerField()
    ...

Я хочу выполнить следующий простой запрос сверху:

select avg(case 
        when (value > 0 and value <= 50) then 0 
        when (value > 50 and value < 70) then 50 
        else 100 end) 
from A
where ...

Я пытаюсь избежать raw SQL-как это можно реализовать с django (в приведенном выше примере я использую avg, но тот же вопрос также актуален для max, min, sum и т. д.)?

Я попытался использовать extra и aggregate:

extra(select={'avg_field': case_when_query})

и

aggregate(Avg('avg_field')), 

но агрегатная функция работает только с полями модели, поэтому дополнительное поле не может использоваться здесь. Как это можно сделать с Django?

Спасибо за помощь

3 ответов


насколько я знаю, (к сожалению) нет способа сделать то, что вы описали, не прибегая к raw SQL.

сказал, что там и способ вычисления среднего так, как вы описываете, если вы готовы денормализовать свои данные немного. Например, вы можете добавить новый столбец с именем average_field это автоматически устанавливается в соответствующее значение на save(). Вы можете либо переопределить save() или нажмите на сигнал, чтобы сделать это автоматически. Для например,

class A(models.Model):
    name = models.CharField(max_length=64, null=False)
    value = models.IntegerField()
    average_field = models.IntegerField(default = 0)

    def _get_average_field(self):
        # Trying to match the case statement's syntax.
        # You can also do 0 < self.value <= 50
        if self.value > 0 and self.value <= 50:
            return 0
        elif self.value > 50 and self.value < 70:
            return 50
        else:
            return 100

    def save(self, *args, **kwargs):
        if self.value:
            self.average_field = self._get_average_field()
        super(A, self).save(*args, **kwargs)

как только вы это сделаете, ваш запрос станет очень легким.

A.objects.filter(...).aggregate(avg = Avg('average_field'))

что можно сделать, что все равно позволит нам использовать Django queryset что-то вроде этого:

qs = A.objects.extra(select={"avg_field": 
                     "avg(case when...)"}).filter(...).values("avg_field")

использовать результат:

qs[0]["avg_field"]

и это позволит необходимую функциональность.


Django 1.8 будет поддерживать случай, когда выражения из коробки, см. https://docs.djangoproject.com/en/dev/ref/models/conditional-expressions/