Как сделать SELECT COUNT (*) GROUP BY и ORDER BY в Django?

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

class Transaction(models.Model):
    actor = models.ForeignKey(User, related_name="actor")
    acted = models.ForeignKey(User, related_name="acted", null=True, blank=True)
    action_id = models.IntegerField() 
    ......

как мне получить 5 лучших участников моей системы?

В sql это будет в основном

SELECT actor, COUNT(*) as total 
FROM Transaction 
GROUP BY actor 
ORDER BY total DESC

Ответ 1

В соответствии с документацией вы должны использовать:

from django.db.models import Count
Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')

values ​​(): указывает, какие столбцы будут использоваться для группировки по

Django docs:

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

annotate(): задает операцию над сгруппированными значениями

Django docs:

Второй способ создания сводных значений - создать независимое резюме для каждого объекта в QuerySet. Например, если вы извлекают список книг, вы можете знать, сколько авторов способствовали каждой книге. Каждая книга имеет отношение "многие ко многим" с Автором; мы хотим обобщить эти отношения для каждой книги в QuerySet.

Резюме для объекта могут быть сгенерированы с использованием предложения annotate(). Когда указано предложение annotate(), каждый объект в QuerySet будут аннотированы с указанными значениями.

Предложение order by является самоочевидным.

Подводя итог: вы группируете группу, создавая набор запросов для авторов, добавляете аннотацию (это добавит дополнительное поле к возвращаемым значениям), и, наконец, вы заказываете их с помощью этого значения

Обратитесь к https://docs.djangoproject.com/en/dev/topics/db/aggregation/ для получения дополнительной информации

Ответ 2

Так же, как @Alvaro ответил на прямой эквивалент Django для оператора GROUP BY:

SELECT actor, COUNT(*) AS total 
FROM Transaction 
GROUP BY actor

заключается в использовании методов values() и annotate() следующим образом:

Transaction.objects.values('actor').annotate(total=Count('actor')).order_by()

Однако нужно еще одно:

Если модель имеет порядок по умолчанию, определенный в class Meta, предложение .order_by() является обязательным для правильных результатов. Вы просто не можете пропустить его, даже если заказы не предназначены.

Кроме того, для кода высокого качества рекомендуется всегда ставить предложение .order_by() после annotate(), даже если нет class Meta: ordering. Такой подход сделает утверждение перспективным: он будет работать точно так же, как и предполагалось, независимо от каких-либо будущих изменений в class Meta: ordering.


Позвольте мне привести вам пример. Если модель имела:

class Transaction(models.Model):
    actor = models.ForeignKey(User, related_name="actor")
    acted = models.ForeignKey(User, related_name="acted", null=True, blank=True)
    action_id = models.IntegerField()

    class Meta:
        ordering = ['id']

Тогда такой подход НЕ РАБОТАЕТ:

Transaction.objects.values('actor').annotate(total=Count('actor'))

Это потому, что Django выполняет дополнительные GROUP BY для каждого поля в class Meta: ordering

Если вы напечатаете запрос:

>>> print Transaction.objects.values('actor').annotate(total=Count('actor')).query
  SELECT "Transaction"."actor_id", COUNT("Transaction"."actor_id") AS "total"
  FROM "Transaction"
  GROUP BY "Transaction"."actor_id", "Transaction"."id"

Будет ясно, что агрегация НЕ работает так, как предполагалось, и поэтому предложение .order_by() должно использоваться для устранения этого поведения и получения правильных результатов агрегирования.

Смотрите: Взаимодействие с порядком по умолчанию или order_by() в официальной документации Django.