Django: аннотировать сумму на переменное количество столбцов - PullRequest
0 голосов
/ 03 ноября 2018

Итак, я прочитал столбцы аннотирования и использовал функции F (), и увидел этот пост о том, как суммировать несколько столбцов. Однако я работаю с БД в стиле EAV, поэтому мне дан список переменных столбцов. Рассмотрим пример:

class TestModel(models.Model):
    column_a = models.FloatField()
    column_b = models.FloatField()
    column_c = models.FloatField()
    column_d = models.FloatField()

ПОПЫТКА 1:

columns = {'column_a', 'column_c', 'column_d'}
queryset = DummyModel.objects.annotate(total=Sum([F(column_name) for column_name in columns]))

Однако print(queryset.query) выдает ошибку django.core.exceptions.FieldError: Cannot resolve expression type, unknown output_field

ПОПЫТКА 2:

queryset = DummyModel.objects.annotate(total=ExpressionWrapper(Sum([F(column_name) for column_name in columns]), output_field=FloatField())) 

Это не приводит к ошибке компиляции, но SQL-запрос дает:

SELECT "test_model"."column_a", "test_model"."column_c", "test_model"."column_d", SUM([]) AS "total" FROM "test_model"

Который пуст. У кого-нибудь есть идеи, как это решить? Любая помощь с благодарностью!

1 Ответ

0 голосов
/ 03 ноября 2018

Чтобы понять это, нужно сначала изобразить таблицу:

column_a | column b | column_c
---------+----------+----------
1        | 2        | 3
4        | 5        | 6
7        | 8        | 9

Sum - «вертикальная» операция; то есть, если бы мы хотели сумму column_a, мы могли бы сделать

>>> DummyModel.objects.aggregate(total=Sum('column_a'))
{'total': 12}

Как видите, это возвращает 1 + 4 + 7 == 12 - так что вы можете понять, почему я называю это "вертикальной" суммой. Обратите внимание, что мы используем aggregate вместо annotate: aggregate для вертикальных операторов.

Если вместо этого нам нужна «горизонтальная» сумма - сумма по строке - мы бы использовали F() и +. Таким образом, чтобы получить column_a + column_b в каждом ряду, мы использовали бы

>>> DummyModel.objects.annotate(total=F('column_a') + F('column_b')).values('total')
<QuerySet [{'total': 3}, {'total': 9}, {'total': 15}]>

Надеюсь, вы понимаете, почему я называю это "горизонтальной" суммой: мы получаем сумму a и b "по горизонтали" в каждом ряду. И теперь обратите внимание, что мы используем annotate, который предназначен для горизонтальных операций.

Если имена столбцов заранее не известны, вам нужно запутаться и использовать functools.reduce и operator.add для построения выражения:

>>> from functools import reduce
>>> from operator import add

>>> cols = ['column_b', 'column_c']
>>> expr = reduce(add, (F(col) for col in cols))
>>> DummyModel.objects.annotate(total=expr).values('total')
<QuerySet [{'total': 5}, {'total': 11}, {'total': 17}]>

Если нам нужна и горизонтальная и вертикальная сумма - т.е. сумма column_a плюс сумма column_b - нам нужно использовать Sum и F():

>>> DummyModel.objects.aggregate(total=Sum(F('column_a') + F('column_b')))
{'total': 27}

Обратите внимание: aggregate, а не аннотировать, так как мы в конечном итоге идем с вертикальной операцией; Sum строк. Да, сначала выполняется горизонтальная операция, но, поскольку в конечном итоге мы Sum, нам нужно aggregate.

Итак, чтобы обернуть вещи, если поля являются переменными, нам нужно объединить aggregate, Sum и reduce хитрость сверху:

>>> cols = ['column_b', 'column_c']
>>> expr = reduce(add, (F(col) for col in cols))
>>> DummyModel.objects.aggregate(total=Sum(expr))
{'total': 33}

Надеюсь, это поможет!

...