Listedeki * her * öğe için __in Django filtresi sorgu kümesi


104

Diyelim ki şu modellere sahibim

class Photo(models.Model):
    tags = models.ManyToManyField(Tag)

class Tag(models.Model):
    name = models.CharField(max_length=50)

Görünümde, kategoriler adı verilen etkin filtrelerin olduğu bir listem var . Tüm etiketleri kategorilerde bulunan Fotoğraf nesnelerini filtrelemek istiyorum .

Denedim:

Photo.objects.filter(tags__name__in=categories)

Ancak bu , kategorilerdeki herhangi bir öğeyle eşleşir , tüm öğelerle eşleşmez .

Dolayısıyla, kategoriler ['tatil', 'yaz'] ise hem tatil hem de yaz etiketi olan Fotoğrafların olmasını istiyorum.

Bu başarılabilir mi?


7
Belki: qs = Photo.objects.all (); kategorilerdeki kategori için: qs = qs.filter (etiketler__name = kategori)
jpic

2
jpic haklı, gitmenin Photo.objects.filter(tags__name='holiday').filter(tags__name='summer')yolu bu. (Bu, jpic örneğiyle aynıdır). Her biri sorguya filterdaha fazla JOINs eklemelidir , böylece çok fazla ise ek açıklama yaklaşımını kullanabilirsiniz .
Davor Lucic

1

Bunun için Django tarafından yerleşik bir işlev olmasını beklersiniz
Vincent

Yanıtlar:


125

Özet:

Yorumlarda jpic ve sgallen tarafından önerildiği gibi, .filter()her kategori için eklenecek bir seçenek vardır . Her bir ek, filterdaha fazla birleşme ekler ve bu, küçük kategoriler kümesi için sorun olmamalıdır.

Orada toplama yaklaşımı . Bu sorgu, büyük bir kategori kümesi için daha kısa ve belki de daha hızlı olacaktır.

Ayrıca özel sorgular kullanma seçeneğiniz de vardır .


Bazı örnekler

Test kurulumu:

class Photo(models.Model):
    tags = models.ManyToManyField('Tag')

class Tag(models.Model):
    name = models.CharField(max_length=50)

    def __unicode__(self):
        return self.name

In [2]: t1 = Tag.objects.create(name='holiday')
In [3]: t2 = Tag.objects.create(name='summer')
In [4]: p = Photo.objects.create()
In [5]: p.tags.add(t1)
In [6]: p.tags.add(t2)
In [7]: p.tags.all()
Out[7]: [<Tag: holiday>, <Tag: summer>]

Zincirleme filtreler yaklaşımını kullanma :

In [8]: Photo.objects.filter(tags=t1).filter(tags=t2)
Out[8]: [<Photo: Photo object>]

Ortaya çıkan sorgu:

In [17]: print Photo.objects.filter(tags=t1).filter(tags=t2).query
SELECT "test_photo"."id"
FROM "test_photo"
INNER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
INNER JOIN "test_photo_tags" T4 ON ("test_photo"."id" = T4."photo_id")
WHERE ("test_photo_tags"."tag_id" = 3  AND T4."tag_id" = 4 )

Her birinin sorguya filterdaha fazlasını eklediğini unutmayın JOINS.

Ek açıklama yaklaşımını kullanma :

In [29]: from django.db.models import Count
In [30]: Photo.objects.filter(tags__in=[t1, t2]).annotate(num_tags=Count('tags')).filter(num_tags=2)
Out[30]: [<Photo: Photo object>]

Ortaya çıkan sorgu:

In [32]: print Photo.objects.filter(tags__in=[t1, t2]).annotate(num_tags=Count('tags')).filter(num_tags=2).query
SELECT "test_photo"."id", COUNT("test_photo_tags"."tag_id") AS "num_tags"
FROM "test_photo"
LEFT OUTER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
WHERE ("test_photo_tags"."tag_id" IN (3, 4))
GROUP BY "test_photo"."id", "test_photo"."id"
HAVING COUNT("test_photo_tags"."tag_id") = 2

ANDed Qnesneleri çalışmaz:

In [9]: from django.db.models import Q
In [10]: Photo.objects.filter(Q(tags__name='holiday') & Q(tags__name='summer'))
Out[10]: []
In [11]: from operator import and_
In [12]: Photo.objects.filter(reduce(and_, [Q(tags__name='holiday'), Q(tags__name='summer')]))
Out[12]: []

Ortaya çıkan sorgu:

In [25]: print Photo.objects.filter(Q(tags__name='holiday') & Q(tags__name='summer')).query
SELECT "test_photo"."id"
FROM "test_photo"
INNER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
INNER JOIN "test_tag" ON ("test_photo_tags"."tag_id" = "test_tag"."id")
WHERE ("test_tag"."name" = holiday  AND "test_tag"."name" = summer )

6
Özel aramalı bir çözüm var mı? docs.djangoproject.com/en/1.10/howto/custom-lookups "__in" i "__all" olarak değiştirmek ve doğru sql sorgusunu oluşturmasını sağlamak harika olurdu.
t1m0

1
Bu açıklama çözümü yanlış görünüyor. Ne (üç etiketleri mümkün olup olmadığını için ek bir arama sağlar t3ve bir fotoğraf etiketi yok t2ve t3O zaman bu fotoğrafı hala verilen sorguyu eşleşir..
beruic

@beruic Sanırım fikir num_tags = 2'yi num_tags = len (etiketler) ile değiştireceğinizdir; Sabit kodlu 2'nin sadece örnek olarak olmasını bekliyorum.
tbm

3
@tbm Yine de işe yaramaz. Photo.objects.filter(tags__in=tags)Yalnızca tümüne sahip olanları değil, etiketlerinin herhangi birini içeren fotoğrafları eşleştirir. Yalnızca istenen etiketlere sahip olanlardan bazıları, tam olarak aradığınız etiket miktarına sahip olabilir ve istenen tüm etiketlere sahip olanların bazıları da ek etiketlere sahip olabilir.
beruic

1
@beruic ek açıklama yalnızca sorgu tarafından döndürülen etiketleri sayar, bu nedenle (sorgu tarafından döndürülen num etiketler) == (aranan num etiketler) ise satır dahil edilir; "ekstra" etiketler aranmaz, dolayısıyla sayılmaz. Bunu kendi uygulamamda doğruladım.
tbm

8

Yalnızca PostgreSQL olmasına rağmen çalışan başka bir yaklaşım django.contrib.postgres.fields.ArrayField:

Dokümanlardan kopyalanan örnek :

>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])

>>> Post.objects.filter(tags__contains=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__contains=['django'])
<QuerySet [<Post: First post>, <Post: Third post>]>

>>> Post.objects.filter(tags__contains=['django', 'thoughts'])
<QuerySet [<Post: First post>]>

ArrayFieldörtüşme ve dizin dönüşümleri gibi bazı daha güçlü özelliklere sahiptir .


3

Bu aynı zamanda Django ORM ve biraz Python sihri kullanılarak dinamik sorgu oluşturma yoluyla da yapılabilir :)

from operator import and_
from django.db.models import Q

categories = ['holiday', 'summer']
res = Photo.filter(reduce(and_, [Q(tags__name=c) for c in categories]))

Buradaki fikir, her kategori için uygun Q nesneleri oluşturmak ve ardından bunları AND operatörünü kullanarak tek bir QuerySet'te birleştirmektir. Örneğin, örneğiniz için şuna eşit olacaktır:

res = Photo.filter(Q(tags__name='holiday') & Q(tags__name='summer'))

4
Bu işe yaramaz. Sorgu örnekleriniz söz konusu modeller için hiçbir şey döndürmez.
Davor Lucic

Düzeltme için teşekkürler. Zincirlemenin , tek bir filtrede Q nesneleri için filterkullanmakla aynı olacağını düşündüm and... Hatam.
demalexx

Endişelenme, ilk düşüncem, Q'nun da nesnesi.
Davor Lucic

1
Karşılaştırmak için büyük tablolar ve büyük verilerle çalışırsanız bu daha yavaş olur. (her biri 1 Milyon gibi)
gies0r

1
Bu yaklaşım gerektiğini size geçiş çalışmayabilir filteriçin excludeve bir negate operatörünü kullanın. Öyle: res = Photo.exclude(~reduce(and_, [Q(tags__name=c) for c in categories]))
Ben

1

Belirli bir operatör için bir liste üzerinde filtreleri ve sütun adını yineleyen küçük bir işlev kullanıyorum:

def exclusive_in (cls,column,operator,value_list):         
    myfilter = column + '__' + operator
    query = cls.objects
    for value in value_list:
        query=query.filter(**{myfilter:value})
    return query  

ve bu işlev şu şekilde çağrılabilir:

exclusive_in(Photo,'tags__name','iexact',['holiday','summer'])

ayrıca listedeki herhangi bir sınıf ve daha fazla etiketle çalışır; operatörler 'iexact', 'in', 'contains', 'ne', ... gibi herhangi biri olabilir.



-1

Bunu dinamik olarak yapmak istiyorsak, örneği takip edin:

tag_ids = [t1.id, t2.id]
qs = Photo.objects.all()

for tag_id in tag_ids:
    qs = qs.filter(tag__id=tag_id)    

print qs

Yakında ikinci tekrarında olarak olduğu kadar değil iş, QuerySet boş olacaktır Can
lapin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.