ProgrammingError при агрегировании по аннотированному и сгруппированному запросу Django ORM
Я пытаюсь построить запрос, чтобы получить "среднее, максимальное и минимальное количество предметов, приобретенных на пользователя".
Источник данных - это простая таблица записей продаж:
class SalesRecord(models.Model):
id = models.IntegerField(primary_key=True)
user_id = models.IntegerField()
product_code = models.CharField()
price = models.IntegerField()
created_at = models.DateTimeField()
Новая запись вставляется в эту таблицу для каждого предмета, приобретенного пользователем.
[Заметка]: user_id
не является внешним ключом для таблицы в той же базе данных, потому что эта внутренняя система не управляет пользовательской информацией. Значение предоставляется интерфейсной частью продукта.
Вот моя попытка построить запрос:
q = SalesRecord.objects.all()
q = q.values('user_id').annotate( # group by user and count the # of records
count=Count('id'), # (= # of items)
).order_by()
result = q.aggregate(Max('count'), Min('count'), Avg('count'))
Когда я пытаюсь выполнить код, ProgrammingError
поднимается в последней строке:
(1064, "У вас есть ошибка в вашем синтаксисе SQL; обратитесь к руководству, соответствующему вашей версии сервера MySQL, чтобы узнать правильный синтаксис для использования рядом с" ОТ (ВЫБРАТЬ)
sales_records
,user_id
КАКuser_id
, COUNT (sales_records
.` 'в строке 1")
Экран ошибок Django показывает, что SQL
SELECT FROM
(SELECT
`sales_records`.`user_id` AS `user_id`,
COUNT(`sales_records`.`id`) AS `count`
FROM `sales_records`
WHERE (`sales_records`.`created_at` >= %s AND `sales_records`.`created_at` <= %s )
GROUP BY `sales_records`.`user_id` ORDER BY NULL) subquery
Он ничего не выбирает! Может кто-нибудь, пожалуйста, покажите мне правильный способ сделать это?
Взлом Джанго
Я обнаружил, что очистка кеша выбранных полей в django.db.models.sql.query.BaseQuery.get_aggregation()
кажется, чтобы решить проблему. Хотя я не совсем уверен, что это исправление или обходной путь.
@@ -327,10 +327,13 @@
# Remove any aggregates marked for reduction from the subquery
# and move them to the outer AggregateQuery.
+ self._aggregate_select_cache = None
+ self.aggregate_select_mask = None
for alias, aggregate in self.aggregate_select.items():
if aggregate.is_summary:
query.aggregate_select[alias] = aggregate
- del obj.aggregate_select[alias]
+ if alias in obj.aggregate_select:
+ del obj.aggregate_select[alias]
... дает результат:
{'count__max': 267, 'count__avg': 26.2563, 'count__min': 1}
2 ответа
Используя модель как есть (без FK для пользователя), вы можете получить счетчик user_id, а затем выполнить математику самостоятельно:
counts = SalesRecord.objects.values('user_id').\
annotate(count=Count('id')).values_list('count', flat=True)
(max(counts), min(counts), sum(counts) / float(len(counts)))
Если вы смогли изменить таблицу для использования ForeignKey и сделать вашу модель похожей на это:
class SalesRecord(model.Models):
user = model.ForeignKey(User)
product_code = models.CharField()
price = models.IntegerField()
created_at = models.DateTimeField()
Тогда вы можете подойти к проблеме из объекта User и использовать aggregate():
users_with_counts = Users.objects.annotate(count=Count('salesrecord'))
stats = users_with_counts.aggregate(Max('count'), Min('count'), Avg('count'))
Любой способ даст вам то, что вы хотите с помощью одного запроса к базе данных.
Ваш запрос ORM действительно правильный, но ошибка в Django 1.6. Видимо это было исправлено в 1.7. Источник: https://code.djangoproject.com/ticket/23669