Использование extra() для ValuesQuerySet в Django

Я пытаюсь рассчитать процент с двумя значениями, которые сами по себе агрегированы. SQL-запрос, который объясняет, что мне нужно, выглядит следующим образом:

SELECT (SUM(field_a) / SUM(field_b) * 100) AS percent
FROM myapp_mymodel 
GROUP BY id
ORDER BY id

Я попытался использовать следующее для создания QuerySet, но, к сожалению, он не содержит дополнительного поля:

MyModel.objects.values('id').annotate(
   sum_field_a=Sum('field_a'),
   sum_field_b=Sum('field_b')).extra(
      select={'percent': 'sum_field_a / sum_field_b * 100'})

Что меня раздражает, так это то, что, согласно документации Django, это похоже на путь:

Когда предложение values() используется для ограничения столбцов, которые возвращаются в наборе результатов […], вместо возврата аннотированного результата для каждого результата в исходном QuerySet исходные результаты группируются в соответствии с уникальными комбинациями указанных полей. в предложении values(). Затем для каждой уникальной группы предоставляется аннотация; аннотация вычисляется по всем членам группы.

Источник: http://docs.djangoproject.com/en/dev/topics/db/aggregation/#values

Если вы используете предложение values() после предложения extra(), любые поля, определенные аргументом select в extra(), должны быть явно включены в предложение values(). Однако, если предложение extra() используется после values(), поля, добавленные выбором, будут включены автоматически.

Источник: http://docs.djangoproject.com/en/dev/ref/models/querysets/#values


person jnns    schedule 04.03.2011    source источник


Ответы (3)


Агрегированные выражения легко разрешать такие выражения в функциях Aggregate ">начиная с Django 1.8 без проблемного метода "extra()".

qs = (
    MyModel.objects.values('id')
    .annotate(percent=Sum('field__a') / Sum('field__b') * 100)
    .order_by('id')
)
>>> print(str(qs.query))
SELECT id, ((SUM(field_a) / SUM(field_b)) * 100) AS percent
FROM app_mymodel GROUP BY id ORDER BY id ASC

(Упомянутая проблема № 15546 была вскоре закрыта замечанием в документации о том, что extra() после values() не будет работать - коммит a4a250a.)

person hynekcer    schedule 05.09.2017
comment
Мне еще предстоит проверить это, потому что с тех пор я больше не рассматривал эту проблему. Но поскольку совокупные выражения кажутся подходом к решению этой проблемы, я отмечаю этот ответ как решение моего вопроса. - person jnns; 06.09.2017
comment
Пробовал проверять SQL еще и группой по ForeignKey или набором запросов, начатым со стороны обратной связи. Простой пример для лучшего ответа. - person hynekcer; 06.09.2017

Как вы указали (#15546), в django может быть ошибка.

Но в качестве обходного пути вы можете возложить бремя фактических вычислений на python вместо базы данных SQL, выполнив что-то вроде этого:

[{'field_c': model['field_c'],
  'percent': m['sum_field_a'] * 100.0 / m['sum_field_b']}
 for model in MyModel.objects.values('field_c').annotate(
    sum_field_a=Sum('field_a'),
    sum_field_b=Sum('field_b')).order_by('field_c')]

Поскольку это решение заставляет вас перебирать все данные, в зависимости от того, что вы хотите сделать, оно может быть приемлемым или неприемлемым.

person Xixi    schedule 11.03.2011
comment
Спасибо. Я надеялся обойти это на питоне, но это может быть путь, пока не будет дальнейшего развития ошибки. - person jnns; 13.03.2011

Если вы используете предложение values() после предложения extra(), любые поля, определенные аргументом select в extra(), должны быть явно включены в предложение values().

Источник: http://docs.djangoproject.com/en/dev/ref/models/querysets/#values

поле «процент», добавленное в выбор, может быть явно добавлено в предложение значений, и оно должно быть добавлено в набор запросов.

MyModel.objects.annotate(
              sum_field_a=Sum('field_a'),
              sum_field_b=Sum('field_b')).extra(
              select={'percent': 'sum_field_a / sum_field_b * 100'}
         ).values('id', 'percent')
person Thomas    schedule 13.03.2011
comment
Результаты должны быть сгруппированы перед аннотацией (отсюда values() перед annotate()). Кроме того, я получаю сообщение Неизвестный столбец field_a при попытке выбрать поле, созданное с помощью аннотации. - person jnns; 16.03.2011
comment
отредактировали фрагмент, добавив «id» вместо field_c, попробуйте сейчас? - person Thomas; 20.03.2011