Эквивалент Джанго для подсчета и группировки по - PullRequest
87 голосов
/ 29 ноября 2008

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

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, однако я не вижу, как группа по подойдет туда.

Ответы [ 4 ]

127 голосов
/ 23 августа 2009

Вот, как я только что обнаружил, как это сделать с помощью API агрегации Django 1.1:

from django.db.models import Count
theanswer = Item.objects.values('category').annotate(Count('category'))
58 голосов
/ 29 ноября 2008

( Обновление : полная поддержка агрегации ORM теперь включена в Django 1.1 . В соответствии с приведенным ниже предупреждением об использовании частных API, описанный здесь метод больше не работает в пост-1.1 версии Django. Я не стал разбираться, почему; если вы используете 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, каждое возвращаемое значение будет словарем с ключом "category" и ключом "count".

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

Обратите внимание, что при установке .query.group_by значения должны быть фактическими именами столбцов БД ('category_id'), а не именами полей Django ('category'). Это потому, что вы настраиваете внутренности запросов на уровне, где все в терминах БД, а не в терминах Django.

55 голосов
/ 27 августа 2009

Поскольку я был немного озадачен тем, как работает группировка в 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 для получения более подробной информации.

2 голосов
/ 30 ноября 2008

Как это? (Кроме медленных.)

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...