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

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

Einfache Modelle nur meine Frage zu stellen.Union und schneiden in Django

Ich frage mich, wie kann ich Blogs mit Tags auf zwei verschiedene Arten abfragen.

  • Blog-Einträge, die mit "tag1" oder "tag2" markiert sind: Blog.objects.filter(tags_in=[1,2]).distinct()
  • Blog-Objekte, die mit "tag1" und "tag2" markiert sind: ?
  • Blog-Objekte, die mit genau "tag1" und "tag2" und nichts anderes markiert sind: ??

Tag und Blog ist nur ein Beispiel verwendet.

+0

Check out [diese Frage] (http://stackoverflow.com/q/12752601/1226722) mit einer wirklich großen Antwort. Könnte hilfreich sein (ich bin mir bewusst, dass diese Frage ~ 6 Jahre alt ist, aber ich fand es immer noch bei der Suche nach Antworten!) – gregoltsov

+0

Dies ist eher ein Problem einer oder in der WHERE-Klausel anstelle einer tatsächlichen SQL-Union. Wenn Sie nach einer Union suchen, sehen Sie sich https: // stackoverflow an.com/questions/4411049/how-can-ich-finde-die-Union-von-zwei-django-querysets – jocassid

Antwort

21

Sie Q-Objekte für 1 # verwenden:

# 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') 
) 

Unions und Kreuzungen, glaube ich, sind ein wenig außerhalb des Bereichs des Django ORM, aber seine möglich auf diese. Die folgenden Beispiele stammen aus einer Django-Anwendung namens django-tagging, die die Funktionalität bereitstellt. Line 346 of models.py:

Für Teil zwei, die Sie für eine Vereinigung von zwei Abfragen suchen, im Grunde

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

Für einen Teil # 3 Ich glaube, dass Sie für eine Kreuzung suchen. Siehe 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

ich diese heraus mit Django 1.0 getestet haben:

Die "oder" Anfragen:

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

oder man konnte die Q-Klasse verwenden:

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

Die Abfrage "und":

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

Ich bin nicht sicher über die dritte, Sie müssen wahrscheinlich zu SQL zu tun, um es zu tun.

+0

Hrm, diese "und" Abfrage sieht aus wie ein handlicher Trick, außer Sie werden nicht zu Beginn wissen, wie viele Zeiten .filter muss angewendet werden. Der Benutzer könnte nach Hund + Ziege + Katze suchen, in diesem Fall würden Sie .filter zweimal benötigen. – mlissner

+0

In Bezug auf die dynamische Anwendung der "und" -Abfrage - einfach durch Tags und akkumulieren Filterung mit: query = query.filter (tags__name = 'tagN') – Lukasz

+0

Ich denke, dass das erste Beispiel den Trick tut. Ich überlegte, ob das Besondere gebraucht wurde oder nicht. In SQL-Begriffen hätten Sie zwei Joins Blog zu BlogTagLink und BlogTagLink zu Tag einen gegebenen Blog-Datensatz würde mehrfach in der Ergebnismenge aufgeführt. – jocassid

9

Bitte erfinden Sie das Rad nicht neu und verwenden Sie django-tagging application, das genau für Ihren Anwendungsfall gemacht wurde. Es kann alle Abfragen, die Sie beschreiben, und vieles mehr. Wenn Sie Ihrem Tag-Modell benutzerdefinierte Felder hinzufügen müssen, können Sie auch my branch of django-tagging ansehen.

5

Dies wird den Trick für Sie

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