2008-09-20 10 views
31
class Tag(models.Model): 
    name = models.CharField(maxlength=100) 

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

Простые модели, чтобы задать вопрос.Union and Intersect in Django

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

  • Блог, которые с тэгом "tag1" или "tag2": Blog.objects.filter(tags_in=[1,2]).distinct()
  • объектов Блог, которые помечены "tag1" и "tag2": ?
  • Объекты в блоге, помеченные точно «tag1» и «tag2», и ничего больше: ??

Tag и Блог используется только для примера.

+0

Выезда [этот вопрос] (http://stackoverflow.com/q/12752601/1226722) с очень большим ответом. Может быть полезно (я знаю, что этот вопрос ~ 6 лет, но я все еще нашел его при поиске ответов!) – gregoltsov 2014-02-26 18:33:37

+0

Это скорее проблема или в предложении where, а не в действительном объединении SQL. Если вы ищете объединенный взгляд на https: // stackoverflow.com/questions/4411049/how-can-i-find-the-union-of-two-django-querysets – jocassid 2017-07-13 22:30:19

ответ

21

Вы можете использовать объекты 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, которое предоставляет функциональные возможности. Line 346 of 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 Я считаю, что вы ищете пересечение. См line 307 of 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() 
16

Я проверил это с 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, чтобы сделать это.

9

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

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

5

Это будет делать трюк для вас

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

 Смежные вопросы

  • Нет связанных вопросов^_^