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

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

Modelos simples solo para hacer mi pregunta.Unión e Intersección en Django

Me pregunto cómo puedo consultar blogs usando etiquetas de dos maneras diferentes. entradas

  • blog que están etiquetadas con "etiqueta 1" o "etiqueta 2": Blog.objects.filter(tags_in=[1,2]).distinct()
  • objetos de blog que están etiquetadas con "etiqueta 1" y "tag2": ?
  • Objetos de blog que están etiquetados exactamente con "tag1" y "tag2" y nada más: ??

Etiqueta y Blog es sólo utilizado para un ejemplo.

+0

Salida [esta cuestión] (http://stackoverflow.com/q/12752601/1226722) con una muy buena respuesta. Podría ser útil (soy consciente de que esta pregunta tiene ~ 6 años, pero aún la encontré mientras buscaba respuestas!) – gregoltsov

+0

Esto es más un problema de una o en la cláusula where en lugar de una unión de SQL real. Si está buscando una unión, mire https: // stackoverflow.com/questions/4411049/how-can-i-find-the-union-of-two-django-querysets – jocassid

Respuesta

21

Se puede usar objetos para 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') 
) 

uniones e intersecciones, creo, está un poco fuera del alcance de la ORM de Django, pero su posible a éstos. Los siguientes ejemplos provienen de una aplicación de Django llamada llamada django-tagging que proporciona la funcionalidad. Line 346 of models.py:

Para la segunda parte, que busca la unión de dos consultas, básicamente

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

Por parte # 3 Creo que usted está buscando una intersección. Veo 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

He probado estos fuera con Django 1.0:

Los "o" consultas:

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

o puede utilizar la clase Q:

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

La consulta "y":

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

No estoy seguro acerca de la tercera, probablemente deba bajar a SQL para hacerlo.

+0

Hrm, esta "y" consulta parece un truco útil, excepto que no sabrá desde el principio cuántos veces será necesario aplicar .filter. El usuario podría estar buscando dog + goat + cat, en cuyo caso necesitaría .filter dos veces. – mlissner

+0

En cuanto a la aplicación dinámica de la consulta "y", simplemente itere a través de las etiquetas y acumule el filtrado usando: query = query.filter (tags__name = 'tagN') – Lukasz

+0

Creo que el primer ejemplo es el truco. Estaba reflexionando si lo distinto era necesario o no. En términos de SQL tendría 2 se une a Blog to BlogTagLink y BlogTagLink to Tag un registro de blog determinado se enumeraría varias veces en el conjunto de resultados. – jocassid

9

Por favor, no reinvente la rueda y use django-tagging application que se hizo exactamente para su caso de uso. Puede hacer todas las consultas que describes, y mucho más.

Si necesita agregar campos personalizados a su modelo de etiqueta, también puede consultar my branch of django-tagging.

5

esto va a hacer el truco para usted

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