Эквивалент Django для count и group by

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

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

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

Я хочу выбрать count (только количество) элементов для каждой категории, поэтому в SQL это будет так просто:

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

Есть ли эквивалент этого "пути Django"? Или простой SQL - единственный вариант? Я знаком с методом count() в Django, но я не вижу, как будет соответствовать группа.

Ответ 1

Здесь, как я только что узнал, как это сделать с API агрегации Django 1.1:

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

Ответ 2

(Обновление). Полная поддержка агрегации ORM теперь включена в Django 1.1. Верно нижеследующее предупреждение об использовании private APIs, описанный здесь метод больше не работает в версиях 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 здесь, это просто включено, чтобы продемонстрировать, как это сделать (это нужно сделать в вызове .extra(), а не в другом месте в цепочке построения запроса). Кроме того, вы можете просто сказать count (id) вместо count (1), но последнее может быть более эффективным.

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

Ответ 3

С тех пор, как я немного смутился о том, как группируются в 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, это не будет работать. Причина этого в том, что поля, требуемые для упорядочения, также выбираются и делают каждую строку уникальной, поэтому вы не будете группировать материал так, как хотите. Один быстрый способ исправить это - reset упорядочение:

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

Это даст точно результаты, которые вы хотите. Чтобы задать имя аннотации, вы можете использовать:

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

Затем вы получите аннотацию под названием mycount в результатах.

Все остальное о группировке было для меня очень простым. Для получения более подробной информации ознакомьтесь с API агрегации Django.

Ответ 4

Как это? (За исключением медленных.)

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