Союз и пересечение в Джанго

class Tag(models.Model):
  name = models.CharField(maxlength=100)

class Blog(models.Model):
  name = models.CharField(maxlength=100)
  tags =  models.ManyToManyField(Tag)

Простые модели только для того, чтобы задать свой вопрос.

Интересно, как я могу запрашивать блоги, используя теги, двумя разными способами.

  • Записи блога с тегом "tag1" или "tag2": Blog.objects.filter(tags_in=[1,2]).distinct()
  • Объекты блога с тегами "tag1" и "tag2": ?
  • Объекты блога, помеченные точно тегами "tag1" и "tag2" и ничем другим : ??

Тег и блог используются только для примера.


person hamdiakoguz    schedule 20.09.2008    source источник
comment
Проверьте этот вопрос с действительно отличным ответом. Может быть полезно (я знаю, что этому вопросу ~ 6 лет, но я все же нашел его, ища ответы!)   -  person gregoltsov    schedule 26.02.2014
comment
Это скорее проблема предложения or in where, а не фактического объединения SQL. Если вы ищете союз, посмотрите stackoverflow.com/questions/4411049/   -  person jocassid    schedule 14.07.2017


Ответы (4)


Вы можете использовать объекты Q для # 1:

# Blogs who have either hockey or django tags.
from django.db.models import Q
Blog.objects.filter(
    Q(tags__name__iexact='hockey') | Q(tags__name__iexact='django')
)

Я считаю, что союзы и пересечения немного выходят за рамки Django ORM, но это возможно. Следующие примеры взяты из приложения Django под названием django-tagging, которое обеспечивает функциональность. Строка 346 файла models.py:

Во второй части вы ищете объединение двух запросов, в основном

def get_union_by_model(self, queryset_or_model, tags):
    """
    Create a ``QuerySet`` containing instances of the specified
    model associated with *any* of the given list of tags.
    """
    tags = get_tag_list(tags)
    tag_count = len(tags)
    queryset, model = get_queryset_and_model(queryset_or_model)

    if not tag_count:
        return model._default_manager.none()

    model_table = qn(model._meta.db_table)
    # This query selects the ids of all objects which have any of
    # the given tags.
    query = """
    SELECT %(model_pk)s
    FROM %(model)s, %(tagged_item)s
    WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
      AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
      AND %(model_pk)s = %(tagged_item)s.object_id
    GROUP BY %(model_pk)s""" % {
        'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
        'model': model_table,
        'tagged_item': qn(self.model._meta.db_table),
        'content_type_id': ContentType.objects.get_for_model(model).pk,
        'tag_id_placeholders': ','.join(['%s'] * tag_count),
    }

    cursor = connection.cursor()
    cursor.execute(query, [tag.pk for tag in tags])
    object_ids = [row[0] for row in cursor.fetchall()]
    if len(object_ids) > 0:
        return queryset.filter(pk__in=object_ids)
    else:
        return model._default_manager.none()

Что касается части № 3, я полагаю, вы ищете перекресток. См. строку 307 файла models.py

def get_intersection_by_model(self, queryset_or_model, tags):
    """
    Create a ``QuerySet`` containing instances of the specified
    model associated with *all* of the given list of tags.
    """
    tags = get_tag_list(tags)
    tag_count = len(tags)
    queryset, model = get_queryset_and_model(queryset_or_model)

    if not tag_count:
        return model._default_manager.none()

    model_table = qn(model._meta.db_table)
    # This query selects the ids of all objects which have all the
    # given tags.
    query = """
    SELECT %(model_pk)s
    FROM %(model)s, %(tagged_item)s
    WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
      AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
      AND %(model_pk)s = %(tagged_item)s.object_id
    GROUP BY %(model_pk)s
    HAVING COUNT(%(model_pk)s) = %(tag_count)s""" % {
        'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
        'model': model_table,
        'tagged_item': qn(self.model._meta.db_table),
        'content_type_id': ContentType.objects.get_for_model(model).pk,
        'tag_id_placeholders': ','.join(['%s'] * tag_count),
        'tag_count': tag_count,
    }

    cursor = connection.cursor()
    cursor.execute(query, [tag.pk for tag in tags])
    object_ids = [row[0] for row in cursor.fetchall()]
    if len(object_ids) > 0:
        return queryset.filter(pk__in=object_ids)
    else:
        return model._default_manager.none()
person Clint Ecker    schedule 20.09.2008

Я проверил это с Django 1.0:

Запросы «или»:

Blog.objects.filter(tags__name__in=['tag1', 'tag2']).distinct()

или вы можете использовать класс Q:

Blog.objects.filter(Q(tags__name='tag1') | Q(tags__name='tag2')).distinct()

Запрос «и»:

Blog.objects.filter(tags__name='tag1').filter(tags__name='tag2')

Я не уверен насчет третьего, вам, вероятно, придется перейти на SQL, чтобы сделать это.

person Ycros    schedule 20.09.2008
comment
Хм, это и запрос выглядят как удобный трюк, за исключением того, что вы не будете знать с самого начала, сколько раз нужно будет применять .filter. Пользователь может искать собаку+козу+кошку, и в этом случае вам понадобится .filter дважды. - person mlissner; 06.06.2011
comment
Что касается динамического применения запроса и - просто перебирайте теги и накапливайте фильтрацию, используя: query = query.filter(tags__name='tagN') - person Lukasz; 15.08.2016
comment
Я думаю, что первый пример делает свое дело. Я размышлял, нужен ли отчет или нет. В терминах SQL у вас будет 2 соединения Blog to BlogTagLink и BlogTagLink to Tag, и данная запись блога будет указана несколько раз в результирующем наборе. - person jocassid; 14.07.2017

Пожалуйста, не изобретайте велосипед и используйте приложение для тегов django, которое было создано специально для ваш вариант использования. Он может выполнять все запросы, которые вы описываете, и многое другое.

Если вам нужно добавить настраиваемые поля в модель тега, вы также можете взглянуть на мою ветку django. -пометка.

person zuber    schedule 21.09.2008

Это поможет вам

Blog.objects.filter(tags__name__in=['tag1', 'tag2']).annotate(tag_matches=models.Count(tags)).filter(tag_matches=2)
person amit    schedule 05.01.2011