Як зробити SELECT COUNT (*) GROUP BY та ORDER BY у Django?


99

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

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

Будь ласка, прочитайте: docs.djangoproject.com/en/dev/topics/db/aggregation
mariodev

Відповіді:


181

Згідно з документацією, ви повинні використовувати:

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

values ​​(): визначає, які стовпці будуть використовуватися для "групування за"

Документи Django:

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

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

Документи Django:

Другий спосіб генерації підсумкових значень - це створення незалежного підсумку для кожного об’єкта в QuerySet. Наприклад, якщо ви отримуєте список книг, ви можете знати, скільки авторів внесли до кожної книги. Кожна книга має стосунки «багато-до-багатьох» з автором; ми хочемо узагальнити ці відносини для кожної книги в QuerySet.

Резюме для кожного об’єкта можна створити за допомогою речення annotate (). Коли вказано пропозицію annotate (), кожен об'єкт у QuerySet буде анотований зазначеними значеннями.

Порядок за пунктом є зрозумілим.

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

Зверніться до https://docs.djangoproject.com/en/dev/topics/db/aggregation/ для отримання додаткової інформації

Варто зауважити: якщо використовується Count, значення, передане Count, не впливає на агрегування, а лише ім’я, надане кінцевому значенню. Агрегатор групується за унікальними комбінаціями значень (як згадано вище), а не за значенням, переданим в Count. Наступні запити однакові:

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

Для мене це працювало як Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total'), не забудьте імпортувати Count з django.db.models. Дякую
Іванчо

3
Варто зауважити: якщо використовується Count(і, можливо, інші агрегатори), значення, яке передається Count, не впливає на агрегування, а лише назва, надана кінцевому значенню. Агрегатор групується за унікальними комбінаціями values(як зазначено вище), а не за значенням, переданим Count.
kronosapiens

Ви навіть можете використовувати це для наборів запитів результатів пошуку postgres, щоб отримати фасетування!
yekta

2
@kronosapiens Це впливає на це, принаймні сьогодні (я використовую Django 2.1.4). У прикладі, чи totalвказано ім'я, і ​​кількість, яка використовується в sql, є, COUNT('actor')що в даному випадку не має значення, але якщо, наприклад values('x', 'y').annotate(count=Count('x')), ви отримаєте COUNT(x), ні COUNT(*)чи COUNT(x, y)просто спробували./manage.py shell
timdiels

35

Так само, як @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.


3
.order_by()врятував мене orderingв Меті.
Бабкен Варданян
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.