Запрос Django (агрегирует и подсчитывает)

#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 были чем-то «счетным», то есть внешними ключами. Я обновлю свой ответ выше, но вам, вероятно, это не понравится ;).