У меня есть модель, которая выглядит так:
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
Есть ли аналог этого "пути Django"? Или единственный вариант - простой SQL? Я знаком с методом count () в Django, но не понимаю, насколько там подходит group by .
Ответы:
Вот, как я только что обнаружил, как это сделать с помощью API агрегации Django 1.1:
from django.db.models import Count theanswer = Item.objects.values('category').annotate(Count('category'))
источник
order_by()
если'category'
это не порядок по умолчанию. (См. Более исчерпывающий ответ Дэниела.).annotate()
работает несколько иначе после.values()
: «Однако, когда предложение values () используется для ограничения столбцов, возвращаемых в наборе результатов, метод оценки аннотаций немного отличается. Вместо возврата аннотированного result для каждого результата в исходном QuerySet, исходные результаты группируются в соответствии с уникальными комбинациями полей, указанными в предложении values () ".( Обновление : полная поддержка агрегации 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, каждое возвращаемое значение будет словарем с ключом «category» и ключом «count».
Здесь нет необходимости заказывать по -count, это просто показано, чтобы продемонстрировать, как это делается (это нужно делать в вызове .extra (), а не где-либо еще в цепочке построения набора запросов). Кроме того, вы также можете сказать count (id) вместо count (1), но последнее может быть более эффективным.
Также обратите внимание, что при настройке .query.group_by значения должны быть фактическими именами столбцов БД ('category_id'), а не именами полей Django ('category'). Это связано с тем, что вы настраиваете внутренние компоненты запроса на уровне, где все определяется терминами БД, а не терминами Django.
источник
Поскольку я был немного сбит с толку тем, как работает группировка в Django 1.1, я подумал, что подробно остановлюсь здесь на том, как именно вы собираетесь ее использовать. Во-первых, повторим то, что сказал Майкл:
Также обратите внимание, что вам необходимо
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 для получения более подробной информации.
источник
Как это? (Кроме медленного.)
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
источник