Еквівалент Django для підрахунку та групування за


91

У мене є модель, яка виглядає так:

class Category(models.Model):
    name = models.CharField(max_length=60)

class Item(models.Model):
    name = models.CharField(max_length=60)
    category = models.ForeignKey(Category)

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

select category_id, count(id) from item group by category_id

Чи є еквівалент виконання цього "шляху Джанго"? Або звичайний SQL - єдиний варіант? Я знайомий з методом count () у Django, однак не бачу, як би там вмістилася група за .



@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 як це дублікат? це питання було задано в 2008 році, а те, про яке ви говорите, - це через 2 роки.
Сергій Головченко

Поточний консенсус полягає у тому, щоб закрити "якість": < meta.stackexchange.com/questions/147643/… > Оскільки "якість" не піддається вимірюванню, я просто йду за голосами. ;-) Можливо, справа зводиться до того, яке питання потрапило в найкращі ключові слова для початківців Google у назві.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Відповіді:


131

Ось, як я щойно виявив, ось як це зробити за допомогою агрегаційного API Django 1.1:

from django.db.models import Count
theanswer = Item.objects.values('category').annotate(Count('category'))

3
як і більшість речей у Django, нічого з цього не має сенсу дивитись, але (на відміну від більшості речей у Django), коли я насправді спробував, це було приголомшливо: P
jsh

3
зауважте, що вам потрібно використовувати, order_by()якщо 'category'замовлення за замовчуванням не є. (Див. Більш вичерпну відповідь Даніеля.)
Рік Вестера,

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

58

( Оновлення : Повна підтримка агрегації ORM тепер включена в Django 1.1 . Згідно з наведеним нижче застереженням про використання приватних API, метод, задокументований тут, більше не працює у версіях Django після 1.1. Я не розбирався, щоб з’ясувати, чому; якщо ви використовуєте 1.1 або пізнішу версію, ви все одно повинні використовувати реальний API агрегування .)

Основна підтримка агрегації була вже в 1.0; він просто недокументований, не підтримується і ще не має дружнього API поверх нього. Але ось як ви можете використовувати його в будь-якому випадку до появи 1.1 (на свій страх і ризик, і знаючи, що атрибут query.group_by не є частиною загальнодоступного API і може змінитися):

query_set = Item.objects.extra(select={'count': 'count(1)'}, 
                               order_by=['-count']).values('count', 'category')
query_set.query.group_by = ['category_id']

Якщо потім виконати ітерацію над query_set, кожне повернене значення буде словником із ключем "категорія" та ключем "count".

Тут вам не потрібно замовляти за -count, це просто включено, щоб продемонструвати, як це робиться (це має бути зроблено у виклику .extra (), а не деінде в ланцюжку побудови набору запитів). Крім того, ви можете так само сказати count (id) замість count (1), але останній може бути більш ефективним.

Також зауважте, що при встановленні .query.group_by значеннями повинні бути фактичні імена стовпців БД ('category_id'), а не імена полів Django ('категорія'). Це пов’язано з тим, що ви налаштовуєте внутрішні елементи запитів на рівні, де все в термінах БД, а не в термінах Django.


+1 для старого методу. Навіть якщо в даний час не підтримується, це м’яко кажучи просвітницьке. Дивно, справді.
авіаудар

Погляньте на API агрегації Django за адресою docs.djangoproject.com/en/dev/topics/db/aggregation/ ... з ним можна виконати інші складні завдання, там ви знайдете кілька потужних прикладів.
serfer2

@ serfer2 так, ці документи вже пов'язані вгорі цієї відповіді.
Carl Meyer

56

Оскільки я був трохи заплутаний щодо того, як працює групування в Django 1.1, я подумав, що я детальніше розкажу тут, як саме ви використовуєте його. По-перше, повторити те, що сказав Майкл:

Ось, як я щойно виявив, ось як це зробити за допомогою агрегаційного API Django 1.1:

from django.db.models import Count
theanswer = Item.objects.values('category').annotate(Count('category'))

Також зверніть увагу, що вам потрібно from django.db.models import Count!

Буде вибрано лише категорії, а потім додано анотацію з назвою category__count. Залежно від замовлення за замовчуванням, це може бути все, що вам потрібно, але якщо в порядку замовлення за замовчуванням використовується інше поле, ніж categoryце не буде працювати . Причиною цього є те, що поля, необхідні для впорядкування, також вибираються і роблять кожен рядок унікальним, тому ви не отримуватимете речі, згруповані за вашим бажанням. Один із швидких способів виправити це - скинути порядок:

Item.objects.values('category').annotate(Count('category')).order_by()

Це має дати саме ті результати, яких ви хочете. Щоб встановити назву анотації, ви можете використовувати:

...annotate(mycount = Count('category'))...

Тоді у вас буде анотація, що називається mycountв результатах.

Все інше щодо групування було для мене дуже простим. Для отримання більш детальної інформації обов’язково перегляньте API агрегування Django .


1
виконати той самий набір дій з полем зовнішнього ключа Item.objects.values ​​('category__category'). annotate (Count ('category__category')). order_by ()
Мутант

Як визначити поле замовлення за замовчуванням?
Богатир

2

Як це? (Крім повільного.)

counts= [ (c, Item.filter( category=c.id ).count()) for c in Category.objects.all() ]

Ця перевага полягає в тому, що він короткий, навіть якщо він отримує багато рядків.


Редагувати.

Версія одного запиту. До речі, це часто швидше, ніж SELECT COUNT (*) у базі даних. Спробуйте побачити.

counts = defaultdict(int)
for i in Item.objects.all():
    counts[i.category] += 1

Це приємно і коротко, проте я хотів би уникати окремих викликів до бази даних для кожної категорії.
Сергій Головченко

Це дійсно хороший підхід для простих справ. Він падає, коли у вас великий набір даних, і ви хочете впорядкувати + ліміт (тобто сторінку) відповідно до підрахунку, не витягуючи тонни непотрібних даних.
Carl Meyer

@Carl Meyer: Правда - це може бути собачим для великого набору даних; Вам потрібно порівняти, щоб бути впевненим у цьому, однак. Крім того, він також не покладається на непідтримувані речі; він тимчасово працює, поки не підтримуються непідтримувані функції.
S.Lott
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.