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

Другие вопросы по тегам