1. ホーム
  2. django

[解決済み] Djangoで複数のfilter()を連鎖させる、これはバグ?

2022-06-02 08:23:25

質問

私はいつも、Django で複数の filter() 呼び出しを連結することは、常に 1 回の呼び出しでそれらを集めることと同じだと考えていました。

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

しかし、私のコードでは、複雑なクエリセットに遭遇したことがあります。

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

生成された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 )

最初のクエリセットで、連鎖した filter() の呼び出しが Inventory モデルを 2 回結合し、2 つの条件の間に OR を作成するのに対し、2 番目のクエリセットでは 2 つの条件を AND しています。私は、最初のクエリが 2 つの条件を AND にすることを期待していました。これは期待された動作なのでしょうか、それとも Django のバグなのでしょうか?

関連する質問に対する回答 Djangoで".filter().filter()..."を使用することに不利な点はありますか? は、2つのクエリセットが等価であるべきであることを示しているようです。

どのように解決するのですか?

私の理解では、デザインによって微妙に異なるということです(確かに訂正は受け付けますが)。 filter(A, B) はまず A に従ってフィルタリングし、次に B に従ってサブフィルタリングします。 filter(A).filter(B) は A にマッチする行と B にマッチする別の行を返します。

ここの例を見てください。

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

は特に。

単一の filter() 呼び出しの中のすべてが同時に適用され、これらすべての要件に一致する項目をフィルタリングします。連続したfilter()呼び出しは、さらにオブジェクトのセットを制限します。

...

<ブロッククオート

この2番目の例 (filter(A).filter(B)) では、最初のフィルタがクエリセットを (A) に制限しています。2番目のフィルタは、ブログのセットをさらに(B)に制限します。二番目のフィルターで選択されたエントリーは、一番目のフィルターのエントリーと同じかもしれませんし、そうでないかもしれません`。