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

Ребята, у меня вот такая модель:

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 (о сериализации заботится поршень) будет выглядеть так:

"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)

Это подсчитывает только те взаимодействия, которые относятся к типу тегов или сообщений, вместо того, чтобы получать количество тегов и сообщений через группу. Очевидно, что это работает так из кода, но интересно, как это исправить...


person Tyler Brock    schedule 24.05.2011    source источник


Ответы (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. Вам, вероятно, потребуется изменить аннотации, поскольку у меня нет копии вашей модели, на которую я мог бы ссылаться; Я просто догадался о названиях полей.

ОБНОВЛЕНИЕ:

Таким образом, после лучшего понимания ваших моделей аннотации не будут работать для того, что вы ищете. На самом деле единственный способ подсчитать, сколько Containers имеют kinds «tag» или «com», это:

tag_count = Container.objects.filter(kind='tag').count()
com_count = Container.objects.filter(kind='com').count()

Аннотации не дадут вам эту информацию. Я думаю, что можно написать свои собственные агрегаты и аннотации, так что это может быть возможным решением. Тем не менее, я никогда не делал этого сам, поэтому я не могу дать вам никаких указаний. Вы, вероятно, застряли с использованием прямого SQL.

person Chris Pratt    schedule 24.05.2011
comment
Отличный ответ, но моя главная забота заключается в следующем: вместо того, чтобы перебирать контейнеры, я хотел иметь возможность выполнять агрегацию в базе данных. Я могу ошибаться, но кажется, что это будет подсчитывать теги и комментарии для каждого контейнера (подразумевается цикл), тогда как использование предложения group by позволит лучше использовать механизм БД. Мысли? - person Tyler Brock; 26.05.2011
comment
Нет никакой итерации. annotate работает как filter и остальная часть API набора запросов. Он просто добавляет материал к возможному SQL, который будет отправлен в БД. Есть только один запрос. - person Chris Pratt; 26.05.2011
comment
Kind на самом деле находится в моей модели взаимодействия, не могли бы вы упомянуть, как бы вы это сделали, если бы я хотел аннотировать количество, где 'interaction__kind' = 'tag' или 'com'? - person Tyler Brock; 26.05.2011
comment
просто вставьте .filter(Q(interaction__kind='tag') | Q(interaction__kind='com')).distinct() перед .annotate. Q находится в django.db.models. - person Chris Pratt; 26.05.2011
comment
Да, судя по вашему обновлению, это не сработает. Я написал ответ, исходя из предположения, что tag и com были чем-то исчисляемым, то есть внешними ключами. Я обновлю свой ответ выше, но он вам, вероятно, не понравится;). - person Chris Pratt; 27.05.2011
comment
Спасибо, Крис, это то, что я понял. Очень ценю помощь человека! - person Tyler Brock; 27.05.2011