#django #django-models #django-orm #django-piston
#django #django-модели #django-orm #django-поршень
Вопрос:
Эй, ребята, у меня есть модель, которая выглядит следующим образом:
class Interaction(DateAwareModel, UserAwareModel):
page = models.ForeignKey(Page)
container = models.ForeignKey(Container, blank=True, null=True)
content = models.ForeignKey(Content)
interaction_node = models.ForeignKey(InteractionNode)
kind = models.CharField(max_length=3, choices=INTERACTION_TYPES)
Я хочу иметь возможность выполнить один запрос, чтобы получить количество взаимодействий, сгруппированных по контейнеру, а затем по виду. Идея в том, что структура выходных данных JSON (о сериализации позаботится piston) будет выглядеть следующим образом:
"data": {
"container 1": {
"tag_count": 3,
"com_count": 1
},
"container 2": {
"tag_count": 7,
"com_count": 12
},
...
}
SQL будет выглядеть следующим образом:
SELECT container_id, kind, count(*) FROM rb_interaction GROUP BY container_id, kind;
Есть идеи о том, как группировать по нескольким полям с помощью ORM? (Я не хочу писать необработанные запросы для этого проекта, если я могу избежать id) Это кажется простым и распространенным запросом.
Прежде чем вы спросите: я видел документацию по агрегатам django и документацию по необработанным запросам.
Обновить В соответствии с приведенными ниже рекомендациями я создал пользовательский менеджер для обработки этого:
class ContainerManager(models.Manager):
def get_query_set(self, *args, **kwargs):
qs = super(ContainerManager, self).get_query_set(*args, **kwargs)
qs.filter(Q(interaction__kind='tag') | Q(interaction__kind='com')).distinct()
annotations = {
'tag_count':models.Count('interaction__kind'),
'com_count':models.Count('interaction__kind')
}
return qs.annotate(**annotations)
Это учитывает только взаимодействия типа tag или com вместо извлечения количества тегов и coms через group by. Очевидно, что из кода это работает именно так, но интересно, как это исправить…
Ответ №1:
Создайте пользовательский менеджер:
class ContainerManager(models.Manager):
def get_query_set(self, *args, **kwargs):
qs = super(ContainerManager, self).get_query_set(*args, **kwargs)
annotations = {'tag_count':models.Count('tag'), 'com_count':models.Count('com')}
return qs.annotate(**annotations)
class Container(models.Model):
...
objects = ContainerManager()
Тогда Container
запросы всегда будут включать в себя атрибуты tag_count
и com_count
. Вероятно, вам потребуется изменить аннотации, поскольку у меня нет копии вашей модели, на которую можно ссылаться; Я просто угадал по именам полей.
Обновить:
Итак, после лучшего понимания ваших моделей аннотации не будут работать для того, что вы ищете. На самом деле единственное, что можно подсчитать, сколько Container
s имеют kind
‘tag’ или ‘com’, это:
tag_count = Container.objects.filter(kind='tag').count()
com_count = Container.objects.filter(kind='com').count()
Аннотации не дадут вам этой информации. Я думаю, что можно написать свои собственные агрегаты и аннотации, так что это может быть возможным решением. Тем не менее, я никогда не делал этого сам, поэтому я не могу дать вам никаких указаний там. Вероятно, вы застряли на использовании прямого SQL.
Комментарии:
1. Отличный ответ, но моя главная проблема заключается в следующем: вместо перебора контейнеров я хотел иметь возможность выполнять работу по агрегированию в базе данных. Возможно, я ошибаюсь, но кажется, что это привело бы к подсчету тегов и комментариев для каждого контейнера (подразумевается цикл), тогда как использование предложения group by позволило бы лучше использовать механизм DB. Мысли?
2. Итерации нет.
annotate
работает так же,filter
как и остальная часть API набора запросов. Он просто привязывает материал к конечному SQL, который будет отправлен в базу данных. Есть только один запрос.3. Kind на самом деле относится к моей модели взаимодействия, не могли бы вы упомянуть, как бы вы это сделали, если бы я хотел аннотировать count, где ‘interaction__kind’ = ‘tag’ или ‘com’?
4. просто вставьте
.filter(Q(interaction__kind='tag') | Q(interaction__kind='com')).distinct()
перед.annotate
.Q
выполняетсяdjango.db.models
.5. Да, основываясь на вашем обновлении, это не сработало бы. Я написал ответ, основанный на предположении, что
tag
иcom
были чем-то «счетным», то есть внешними ключами. Я обновлю свой ответ выше, но вам, вероятно, это не понравится ;).