ProgrammingError при агрегировании по аннотированному и сгруппированному запросу Django ORM - PullRequest
2 голосов
/ 01 апреля 2010

Я пытаюсь создать запрос, чтобы получить «среднее, максимальное и минимальное количество предметов, приобретенных на пользователя».

Источник данных - это простая таблица записей продаж:

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 AS 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 ]

2 голосов
/ 01 апреля 2010

Используя модель как есть (без 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'))

В любом случае вы получите то, что вам нужно, с помощью одного запроса к базе данных.

0 голосов
/ 17 октября 2014

Ваш запрос ORM действительно правильный, но ошибка в Django 1.6. Видимо это было исправлено в 1.7. Источник: https://code.djangoproject.com/ticket/23669#comment:5

...