Как выбрать 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

2 ответов


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

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

values (): указывает, какие столбцы будут использоваться для "group by"

Django docs:

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

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

Django docs:

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

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

предложение order by не требует пояснений.

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

смотрите https://docs.djangoproject.com/en/dev/topics/db/aggregation/ для большего понимания


так же, как @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.