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

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

Proste modele tylko po to, aby zadać moje pytanie.Union i Przecięcie w Django

Zastanawiam się, jak mogę wysyłać zapytania do blogów za pomocą tagów na dwa różne sposoby. wpisy

  • log, które są oznaczone tagiem "Tag1" lub "tag2": Blog.objects.filter(tags_in=[1,2]).distinct()
  • obiektów blogu, że są oznaczone tagiem "Tag1" i "tag2": ?
  • Obiekty na blogu oznaczone jako "tag1" i "tag2" i nic innego: ??

Tag i blog jest po prostu wykorzystywane do przykładu.

+0

Odjazd [to pytanie] (http://stackoverflow.com/q/12752601/1226722) z naprawdę wielką odpowiedź. Może być pomocny (jestem świadomy, że to pytanie ma ~ 6 lat, ale wciąż znajdowałem to podczas szukania odpowiedzi!) – gregoltsov

+0

Jest to raczej kwestia klauzuli where, a nie faktycznej unii SQL. Jeśli szukasz związku spójrz na https: // stackoverflow.com/questions/4411049/how-can-i-find-the-union-of-two-django-querysets – jocassid

Odpowiedz

21

Można używać obiektów Q za # 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') 
) 

związki i skrzyżowań, jak sądzę, są nieco poza zakres Django ORM, ale jego możliwości do nich. Poniższe przykłady pochodzą z aplikacji Django o nazwie django-tagging, która zapewnia tę funkcjonalność. Line 346 of models.py:

Do części drugiej, szukasz unii dwóch zapytań, w zasadzie

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

Dla części # 3 Wierzę szukasz skrzyżowaniu. Zobacz 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

Przetestowałem te obecnie z Django 1.0:

W "lub" Zapytania:

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

lub można użyć klasy Q:

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

Zapytanie "i":

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

Nie jestem pewien co do trzeciego, prawdopodobnie będziesz musiał przerwać SQL, aby to zrobić.

+0

Hrm, to zapytanie "i" wygląda jak poręczna sztuczka, tyle że na samym początku nie będziesz wiedział, ile razy .filter będzie musiał być zastosowany. Użytkownik może szukać psa + kozła + kota, w takim przypadku będziesz potrzebować .filter dwa razy. – mlissner

+0

Jeśli chodzi o dynamiczne zastosowanie zapytania "i" - po prostu powtarzaj po tagach i kumuluj filtrowanie za pomocą: query = query.filter (tags__name = 'tagN') – Lukasz

+0

Myślę, że pierwszy przykład rozwiązuje problem. Zastanawiałem się, czy to, co odróżnia, było potrzebne. W terminach SQL masz 2 dołącza Blog do BlogTagLink i BlogTagLink do Tagu dany rekord blogu będzie wyświetlany wiele razy w zestawie wyników. – jocassid

9

Nie wymyślaj ponownie koła i używaj django-tagging application, który został wykonany dokładnie dla twojego przypadku użycia. Może wykonywać wszystkie zapytania, które opisujesz, i wiele więcej.

Jeśli chcesz dodać niestandardowe pola do swojego modelu znaczników, możesz również spojrzeć na my branch of django-tagging.

5

to załatwi za Ciebie

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