Django'da birden çok filtreyi () zincirlemek, bu bir hata mı?


114

Her zaman Django'da çoklu filtre () çağrılarını zincirlemenin her zaman onları tek bir çağrıda toplamakla aynı olduğunu varsaydım.

# Equivalent
Model.objects.filter(foo=1).filter(bar=2)
Model.objects.filter(foo=1,bar=2)

ancak kodumda durumun böyle olmadığı karmaşık bir sorgu kümesiyle karşılaştım

class Inventory(models.Model):
    book = models.ForeignKey(Book)

class Profile(models.Model):
    user = models.OneToOneField(auth.models.User)
    vacation = models.BooleanField()
    country = models.CharField(max_length=30)

# Not Equivalent!
Book.objects.filter(inventory__user__profile__vacation=False).filter(inventory__user__profile__country='BR')
Book.objects.filter(inventory__user__profile__vacation=False, inventory__user__profile__country='BR')

Oluşturulan SQL

SELECT "library_book"."id", "library_book"."asin", "library_book"."added", "library_book"."updated" FROM "library_book" INNER JOIN "library_inventory" ON ("library_book"."id" = "library_inventory"."book_id") INNER JOIN "auth_user" ON ("library_inventory"."user_id" = "auth_user"."id") INNER JOIN "library_profile" ON ("auth_user"."id" = "library_profile"."user_id") INNER JOIN "library_inventory" T5 ON ("library_book"."id" = T5."book_id") INNER JOIN "auth_user" T6 ON (T5."user_id" = T6."id") INNER JOIN "library_profile" T7 ON (T6."id" = T7."user_id") WHERE ("library_profile"."vacation" = False  AND T7."country" = BR )
SELECT "library_book"."id", "library_book"."asin", "library_book"."added", "library_book"."updated" FROM "library_book" INNER JOIN "library_inventory" ON ("library_book"."id" = "library_inventory"."book_id") INNER JOIN "auth_user" ON ("library_inventory"."user_id" = "auth_user"."id") INNER JOIN "library_profile" ON ("auth_user"."id" = "library_profile"."user_id") WHERE ("library_profile"."vacation" = False  AND "library_profile"."country" = BR )

Zincirleme filter()çağrılara sahip ilk sorgu kümesi , Envanter modelini iki kez etkili bir şekilde birleştirerek iki koşul arasında bir VEYA oluştururken, ikinci sorgu kümesi iki koşulu birlikte VE oluşturur. İlk sorgunun da VE iki koşulu olacağını bekliyordum. Bu beklenen davranış mı yoksa bu Django'da bir hata mı?

İlgili sorunun cevabı Django'da ".filter (). Filter (). Filter () ..." kullanmanın bir dezavantajı var mı? Görünüşe göre iki sorgu kümesinin eşdeğer olması gerektiği belirtiliyor.

Yanıtlar:


123

Anladığım kadarıyla, tasarım açısından incelikli bir şekilde farklılar (ve kesinlikle düzeltmeye açığım): filter(A, B)önce A'ya göre filtre uygulayacak, sonra B'ye göre alt filtre uygulayacak, filter(A).filter(B)A 've' potansiyel olarak farklı bir satırla eşleşen bir satır döndürecektir. B ile eşleşen satır.

Buradaki örneğe bakın:

https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships

özellikle:

Tüm bu gereksinimleri karşılayan öğeleri filtrelemek için tek bir filtre () çağrısı içindeki her şey aynı anda uygulanır. Ardışık filtre () çağrıları, nesne kümesini daha da kısıtlar

...

Bu ikinci örnekte (filtre (A) .filter (B)), ilk filtre sorgu kümesini (A) ile sınırlandırmıştır. İkinci filtre, blog kümesini aynı zamanda (B) olanlarla daha da kısıtladı. İkinci filtre tarafından seçilen girişler, birinci filtredeki girişlerle aynı olabilir veya olmayabilir. '


20
Bu davranış, belgelenmesine rağmen, en az şaşkınlık ilkesini ihlal ediyor gibi görünüyor. Alanlar aynı modelde olduğunda birden çok filtrenin () VE birlikte, ancak daha sonra ilişkileri kapsarken VEYA birlikte.
gerdemb

3
İlk paragrafta yanlış bir şekilde anladığınıza inanıyorum - filtre (A, B) AND durumudur (belgelerde 'lennon' VE 2008), filtre (A) .filter (B) ise OR durumudur ( 'lennon' VEYA 2008). Soruda oluşturulan sorgulara baktığınızda bu mantıklı geliyor - .filter (A) .filter (B) durumu birleşimleri iki kez oluşturarak bir OR ile sonuçlanır.
Sam

17
filtresi (A, B) VE filtresidir (A). filtre (B) OR
WeizhongTu

3
yani further restrictanlamı less restrictive?
boh

7
Bu cevap yanlış. "VEYA" değil. Bu cümle "İkinci filtre, blog kümesini aynı zamanda (B) olanlarla daha da sınırladı." açıkça "bunlar da (B)" den bahseder. Bu özel örnekte VEYA'ya benzer bir davranış gözlemlerseniz, kendi yorumunuzu genelleştirebileceğiniz anlamına gelmez. Lütfen "Kevin 3112" ve "Johnny Tsang" tarafından verilen cevaplara bakın. Bunların doğru cevaplar olduğuna inanıyorum.
kişi

74

Bu iki filtre stili çoğu durumda eşdeğerdir, ancak ForeignKey veya ManyToManyField tabanlı nesneler üzerinde sorgulama yapıldığında biraz farklıdırlar.

Belgelerden örnekler .

model
Blog to Entry, bire çok ilişkisidir.

from django.db import models

class Blog(models.Model):
    ...

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    pub_date = models.DateField()
    ...

nesneler
Burada bazı blog ve giriş nesneleri olduğunu varsayarsak.
görüntü açıklamasını buraya girin

sorguları

Blog.objects.filter(entry__headline_contains='Lennon', 
    entry__pub_date__year=2008)
Blog.objects.filter(entry__headline_contains='Lennon').filter(
    entry__pub_date__year=2008)  
    

1. sorgu için (tek filtre bir), yalnızca blog1 ile eşleşir.

2. sorgu için (zincirleme filtreler bir), blog1 ve blog2'yi filtreler.
İlk filtre, sorgu kümesini blog1, blog2 ve blog5 ile sınırlar; ikinci filtre, blog kümesini blog1 ve blog2 ile sınırlar.

Ve bunun farkına varmalısın

Blog öğelerini Giriş öğelerini değil, her filtre deyimiyle filtreliyoruz.

Öyleyse, aynı değil çünkü Blog ve Giriş çok değerli ilişkilerdir.

Referans: https://docs.djangoproject.com/en/1.8/topics/db/queries/#spanning-multi-valued-relationships
Bir sorun varsa lütfen beni düzeltin.

Düzenleme: 1.6 bağlantıları artık mevcut olmadığından v1.6, v1.8 olarak değiştirildi.


3
Görünüşe göre "eşleşmeler" ve "filtreler" arasında karışmışsınız. "Bu sorgu geri dönüyor" seçeneğine takılırsanız, çok daha net olacaktır.
OrangeDog

7

Oluşturulan SQL ifadelerinde görebileceğiniz gibi, bazılarının şüphe edebileceği gibi, fark "VEYA" değildir. WHERE ve JOIN nasıl yerleştirilir.

Örnek1 (aynı birleştirilmiş tablo):

( https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships adresinden örnek )

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

Bu size hem (entry_ headline _contains = 'Lennon') VE (entry__pub_date__year = 2008) ile tek bir girişi olan tüm Blogları verecektir , bu da bu sorgudan bekleyeceğiniz şeydir. Sonuç: {entry.headline: 'Life of Lennon', entry.pub_date: '2008'} ile kitap

Örnek 2 (zincirlenmiş)

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

Bu, 1. Örnekteki tüm sonuçları kapsayacak, ancak biraz daha fazla sonuç üretecektir. Çünkü önce tüm blogları (entry_ headline _contains = 'Lennon') ve ardından sonuç filtrelerinden (entry__pub_date__year = 2008) filtreler.

Aradaki fark, size aynı zamanda şu sonuçları da vermesidir: {entry.headline: ' Lennon ', entry.pub_date: 2000} ile rezervasyon yapın, {entry.headline: 'Bill', entry.pub_date: 2008 }

Senin durumunda

Sanırım ihtiyacın olan bu:

Book.objects.filter(inventory__user__profile__vacation=False, inventory__user__profile__country='BR')

VEYA kullanmak istiyorsanız lütfen şunu okuyun: https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects


İkinci örnek aslında doğru değil. Zincirli filtrelerin tümü sorgulanan nesnelere uygulanır, yani sorguda birlikte AND işlemine tabi tutulurlar.
Janne

Örnek 2'nin doğru olduğuna inanıyorum ve aslında referans verildiği gibi resmi Django belgelerinden alınmış bir açıklamadır. En iyi açıklayıcı ben olmayabilirim ve bunun için özür dilerim. Örnek 1, normal bir SQL yazımında beklediğiniz gibi doğrudan bir AND'dir. Örnek 1 şuna benzer bir şey verir: 'SELECT blog JOIN entry WHERE entry.head_line WHERE entry.head_line GİBİ " Lennon " AND entry.year == 2008 Örnek 2 şöyle bir şey verir:' SELECT blog JOIN entry WHERE entry.head_list " Lennon " UNION SELECT blog WHERE girişine KATIL entry.head_list " Lennon " ' GİBİ
Johnny Tsang

Efendim, çok haklısınız. Aceleyle, filtreleme kriterlerimizin blogun kendisine değil, bire çok ilişkisine işaret ettiği gerçeğini kaçırdım.
Janne

2

Gönderen Django dokümanlar :

Bu durumların her ikisini de ele almak için Django, filter () çağrılarını işlemek için tutarlı bir yönteme sahiptir. Tüm bu gereksinimleri karşılayan öğeleri filtrelemek için tek bir filtre () çağrısı içindeki her şey aynı anda uygulanır. Ardışık filter () çağrıları, nesne kümesini daha da kısıtlar, ancak çok değerli ilişkiler için, birincil modele bağlı herhangi bir nesneye uygulanır, daha önceki bir filter () çağrısı tarafından seçilen nesnelere gerek yoktur.

  • Tek bir durumdaki birden çok koşulun filter()aynı anda uygulandığı açıkça söyleniyor . Bu, şunu yapmak anlamına gelir:
objs = Mymodel.objects.filter(a=True, b=False)

modelinden raws ile Sorgu Kümesi dönecektir VE .Mymodela=True b=False

  • filter()Bazı durumlarda arka arkaya aynı sonucu verecektir. Yapıyor:
objs = Mymodel.objects.filter(a=True).filter(b=False)

modelinden raws ile Sorgu Kümesi dönecektir VE de. "Önce" olan kayıtları içeren bir sorgu kümesi elde ettiğinizden ve daha sonra aynı anda sahip olanlarla sınırlıdır .Mymodela=True b=Falsea=Trueb=False

  • Zincirlemedeki fark, filter()var olduğu zaman ortaya çıkar multi-valued relations, bu da diğer modellerden geçtiğiniz anlamına gelir (örneğin, Blog ve Giriş modelleri arasındaki belgelerde verilen örnek). Bu durumda söyleniyor(...) they apply to any object linked to the primary model, not necessarily those objects that were selected by an earlier filter() call.

Bu, ardışıkları filter()önceki modele değil, doğrudan hedef modele uyguladığı anlamına gelir.filter()

Dokümanlardan örnek alırsam:

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

bunun Blogfiltrelenen model olduğunu Entry,. Yani 2'yi filter()bağımsız olarak ele alacak .

Örneğin, 'Lennon' içeren girişleri (2008'den olmasalar bile) ve 2008'den gelen girişleri (başlıkları 'Lennon' içermese bile) içeren Blog'larla bir sorgu kümesi döndürecektir.

BU CEVAP açıklamada daha da ileri gidiyor. Ve asıl soru benzer.


0

Bazen birden çok filtreyi şu şekilde birleştirmek istemezsiniz:

def your_dynamic_query_generator(self, event: Event):
    qs \
    .filter(shiftregistrations__event=event) \
    .filter(shiftregistrations__shifts=False)

Ve aşağıdaki kod aslında doğru olanı döndürmeyecektir.

def your_dynamic_query_generator(self, event: Event):
    return Q(shiftregistrations__event=event) & Q(shiftregistrations__shifts=False)

Şimdi yapabileceğiniz şey bir ek açıklama sayma filtresi kullanmaktır.

Bu durumda, belirli bir olaya ait tüm vardiyaları sayarız.

qs: EventQuerySet = qs.annotate(
    num_shifts=Count('shiftregistrations__shifts', filter=Q(shiftregistrations__event=event))
)

Daha sonra ek açıklamaya göre filtreleyebilirsiniz.

def your_dynamic_query_generator(self):
    return Q(num_shifts=0)

Bu çözüm ayrıca büyük sorgu kümelerinde daha ucuzdur.

Bu yardımcı olur umarım.

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.