Как сделать 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.