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