Enchaîner multiple filter() dans Django, est-ce un bug?

j'ai toujours supposé que enchaîner plusieurs appels filter() à Django était toujours la même chose que les collecter en un seul appel.

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

mais j'ai rencontré un queryset compliqué dans mon code où ce n'est pas le cas

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

le SQL généré est

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 )

le premier queryset avec les appels enchaînés filter() rejoint le modèle D'Inventaire deux fois créant effectivement un ou entre les deux conditions alors que la seconde queryset Padn les deux conditions. Je m'attendais à ce que la première requête aussi et les deux conditions. Est-ce le comportement attendu ou est-ce un bug dans Django?

La réponse à une question relative à la Est-il un inconvénient à l'utilisation ".filtrer.)(filtrer.)(filtrer.)(.."à Django? semble indiquer que les deux querysets devraient être équivalents.

59
demandé sur Community 2011-11-17 13:17:23

3 réponses

si je comprends bien, c'est qu'ils sont subtilement différents de par leur conception (et je suis certainement ouvert à la correction): filter(A, B) filtrera d'abord selon A et ensuite sous-filtrera selon B, tandis que filter(A).filter(B) retournera une rangée qui correspond à A ' et 'une rangée potentiellement différente qui correspond à B.

regardez l'exemple ici:

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

en particulier:

Tout ce qui se trouve à l'intérieur d'un seul appel de filtre() est appliqué simultanément pour filtrer les éléments correspondant à toutes ces exigences. Les appels successifs de filter() restreignent encore plus l'ensemble des objets

...

dans ce deuxième exemple (Filtre(a).filtre(B)), le premier filtre a limité le queryset à (a). Le deuxième filtre restreint l' ensemble de blogs plus loin de ceux qui sont aussi (B). Les entrées sélectionnées par le second filtre peuvent être ou ne pas être les mêmes que les entrées du premier filtre.

64
répondu Timmy O'Mahony 2017-07-14 08:55:38

ces deux styles de filtrage sont équivalents dans la plupart des cas, mais lorsque la requête sur les objets base sur ForeignKey ou ManyToManyField, ils sont légèrement différents.

Exemples de la documentation .

modèle

Blog à L'entrée est une relation un-à-plusieurs.

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

objets

En supposant qu'il y ait des objets de blog et d'entrée ici.

enter image description here

requêtes

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)  

pour la 1ère requête (un seul filtre), il ne correspond qu'à blog1.

pour la 2ème requête (chained filters one), Il filtre blog1 et blog2.

Le premier filtre limite le queryset à blog1, blog2 et blog5; le second filtre restreint l'ensemble de blogs à blog1 et blog2.

et vous devriez réaliser que

nous filtrons les éléments de Blog avec chaque énoncé de filtre, pas les éléments D'entrée.

donc, ce n'est pas la même chose, parce que le Blog et L'entrée sont des relations multi-valeur.

référence: https://docs.djangoproject.com/en/1.8/topics/db/queries/#spanning-multi-valued-relationships

Si quelque chose ne va pas, corrigez-moi.

Edit: Changed v1.6 à v1.8 puisque les liens 1.6 ne sont plus disponibles.

44
répondu Kevin_wyx 2016-03-10 20:48:44

comme vous pouvez le voir dans les instructions SQL générées la différence n'est pas le" ou " comme certains peuvent le soupçonner. C'est la façon dont L'endroit et la jointure sont placés.

exemple1 (même tableau joint) :

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

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

ce qui vous donnera tous les Blogs qui ont un entrée avec les deux (entry_ titre _contains='Lennon') ET (entrée__pub_date__année=2008), qui est ce que vous attendez de cette requête. Résultat: Réserver avec entrée.titre: "Life of Lennon", entrée.pub_date: '2008'}

exemple 2 (enchaîné)

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

cela couvrira tous les résultats de L'exemple 1, mais générera un peu plus de résultats. Parce qu'il filtre d'abord tous les blogs avec (entry_ headline _contains='Lennon') et ensuite à partir des filtres de résultat (entrée__pub_date__année=2008).

la différence est qu'il vous donnera aussi des résultats comme: Réserver avec entrée.titre: ' Lennon ' de l'entrée.pub_date: 2000}, {entrée.titre: 'Loi' de l'entrée.date de publication: 2008 }

dans votre cas

je pense que c'est ce dont vous avez besoin:

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

et si vous voulez utiliser Ou s'il vous plaît lire: https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects

4
répondu Johnny Tsang 2012-06-14 01:37:14