UPDATE
Благодаря опубликованному ответу, я нашел гораздо более простой способ сформулировать проблему. Оригинальный вопрос можно увидеть в истории изменений.
Проблема
Я пытаюсь перевести SQL-запрос в Django, но получаю ошибку, которую я не понимаю.
Вот модель Django у меня:
class Title(models.Model):
title_id = models.CharField(primary_key=True, max_length=12)
title = models.CharField(max_length=80)
publisher = models.CharField(max_length=100)
price = models.DecimalField(decimal_places=2, blank=True, null=True)
У меня есть следующие данные:
publisher title_id price title
--------------------------- ---------- ------- -----------------------------------
New Age Books PS2106 7 Life Without Fear
New Age Books PS2091 10.95 Is Anger the Enemy?
New Age Books BU2075 2.99 You Can Combat Computer Stress!
New Age Books TC7777 14.99 Sushi, Anyone?
Binnet & Hardley MC3021 2.99 The Gourmet Microwave
Binnet & Hardley MC2222 19.99 Silicon Valley Gastronomic Treats
Algodata Infosystems PC1035 22.95 But Is It User Friendly?
Algodata Infosystems BU1032 19.99 The Busy Executive Database Guide
Algodata Infosystems PC8888 20 Secrets of Silicon Valley
Вот что я хочу сделать: введите аннотированное поле dbl_price
, которое в два раза больше цены, затем сгруппируйте полученный запрос на publisher
и для каждого издателя вычислите общее количество всех значений dbl_price
для всех названия, опубликованные этим издателем.
Запрос SQL, который делает это, выглядит следующим образом:
SELECT SUM(dbl_price) AS total_dbl_price, publisher
FROM (
SELECT price * 2 AS dbl_price, publisher
FROM title
) AS A
GROUP BY publisher
Желаемый результат:
publisher tot_dbl_prices
--------------------------- --------------
Algodata Infosystems 125.88
Binnet & Hardley 45.96
New Age Books 71.86
Запрос Django
Запрос будет выглядеть так:
Title.objects
.annotate(dbl_price=2*F('price'))
.values('publisher')
.annotate(tot_dbl_prices=Sum('dbl_price'))
но дает ошибку:
KeyError: 'dbl_price'.
который указывает, что он не может найти поле dbl_price
в запросе.
Причина ошибки
Вот почему эта ошибка происходит: в документации говорится
Вы также должны заметить, что в среднем в списке возвращаемых значений. Это необходимо из-за упорядочивания предложений values () и annotate().
Если предложение values () предшествует предложению annotate(), любые аннотации будет автоматически добавлен в результирующий набор. Однако, если values () применяется после предложения annotate(), вам нужно явно указать столбец агрегата.
Итак, dbl_price
не удалось найти в агрегации, потому что он был создан предыдущим annotate
, но не был включен в values()
.
Однако я не могу включить его в values
, потому что я хочу использовать values
(за которым следует другой annotate
) в качестве группирующего устройства, поскольку
Если предложение values () предшествует аннотации(), аннотация будет вычисляться с использованием группировки, описываемой предложением values ().
который является основой того, как Django реализует SQL GROUP BY
. Это означает, что я не могу включить dbl_price
внутри values()
, потому что тогда группировка будет основана на уникальных комбинациях обоих полей publisher
и dbl_price
, тогда как мне нужно группировать только publisher
.
Таким образом, следующий запрос, который отличается от вышеприведенного в том, что я агрегировал над полем модели price
вместо аннотированного поля dbl_price
, фактически работает:
Title.objects
.annotate(dbl_price=2*F('price'))
.values('publisher')
.annotate(sum_of_prices=Count('price'))
потому что поле price
находится в модели, а не является аннотированным полем, поэтому нам не нужно включать его в values
, чтобы сохранить его в наборе запросов.
Вопрос
Итак, вот оно: мне нужно включить аннотированное свойство в values
, чтобы сохранить его в наборе запросов, но я не могу этого сделать, потому что values
также используется для группировки (что будет неправильно с дополнительное поле). Проблема в основном связана с двумя очень разными способами, в которых values
используется в Django, в зависимости от контекста (следует ли за values
следовать annotate
) - который является (1) извлечением значения (SQL plain SELECT
) и (2) группировка + агрегация по группам (SQL GROUP BY
) - и в этом случае эти два способа конфликтуют.
Мой вопрос: есть ли способ решить эту проблему (без таких вещей, как возврат к необработанному sql)?
Обратите внимание: конкретный пример может быть решен путем перемещения всех операторов annotate
после values
, что было отмечено несколькими ответами. Однако меня больше интересуют решения (или обсуждения), которые сохраняли бы инструкции annotate
до values()
по трем причинам: 1. Существуют также более сложные примеры, где предлагаемое обходное решение не будет работать. 2. Я могу представить ситуации, когда аннотированный запрос был передан другой функции, которая фактически выполняет GROUP BY, так что единственное, что мы знаем, это набор имен аннотированных полей и их типов. 3. Ситуация кажется довольно простой, и это меня удивило бы, если бы это столкновение двух разных видов использования values()
не было замечено и обсуждалось ранее.