首页 文章

在Django中链接多个filter(),这是一个错误吗?

提问于
浏览
66

我一直认为在Django中链接多个filter()调用始终与在一次调用中收集它们相同 .

# 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模型两次,有效地在两个条件之间创建OR,而第二个查询集将这两个条件组合在一起 . 我原以为第一个查询也会和两个条件相符 . 这是预期的行为还是Django中的一个错误?

相关问题Is there a downside to using ".filter().filter().filter()..." in Django?的答案似乎表明两个查询集应该是等价的 .

3 回答

  • 4

    我理解它的方式是它们在设计上略有不同(我当然可以进行修正): filter(A, B) 将首先根据A进行过滤,然后根据B进行子过滤,而 filter(A).filter(B) 将返回与A 'and'匹配的行可能不同匹配B的行

    看一下这里的例子:

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

    尤其:

    同时应用单个filter()调用内的所有内容来过滤掉符合所有这些要求的项目 . 连续的filter()调用进一步限制了对象集

    ...

    在第二个示例(filter(A).filter(B))中,第一个过滤器将查询集限制为(A) . 第二个过滤器将博客集进一步限制为(B) . 由第二过滤器选择的条目可以与第一过滤器中的条目相同或不同

  • 47

    在大多数情况下,这两种过滤方式是等效的,但是当基于ForeignKey或ManyToManyField查询对象时,它们略有不同 .

    the documentation中的示例 .

    model
    博客到条目是一对多的关系 .

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

    objects
    假设这里有一些博客和入口对象 .

    enter image description here

    queries

    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)
    

    对于第一个查询(单个过滤器),它只匹配blog1 .

    对于第二个查询(链式过滤器一个),它过滤掉了blog1和blog2 .
    第一个过滤器将查询集限制为blog1,blog2和blog5;第二个过滤器将博客集进一步限制为blog1和blog2 .

    你应该意识到这一点

    我们使用每个过滤器语句过滤博客项目,而不是条目项目 .

    所以,它不一样,因为Blog和Entry是多值关系 .

    参考:https://docs.djangoproject.com/en/1.8/topics/db/queries/#spanning-multi-valued-relationships
    如果有问题,请纠正我 .

    编辑:由于1.6链接不再可用,因此将v1.6更改为v1.8 .

  • 74

    正如您在生成的SQL语句中看到的那样,差异不是某些人可能怀疑的“OR” . 这是WHERE和JOIN的放置方式 .

    Example1(相同的连接表):

    (例子来自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)
    

    这将为您提供所有具有 one 条目的博客(entry_headline_contains = 'Lennon')AND(entry__pub_date__year = 2008),这是您对此查询的期望 . 结果:预订{entry.headline:'Life of Lennon',entry.pub_date:'2008'}

    示例2(链式)

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

    这将涵盖示例1的所有结果,但会产生稍多的结果 . 因为它首先使用(entry_headline_contains = 'Lennon')过滤所有博客,然后从结果过滤器(entry__pub_date__year = 2008)过滤 .

    不同之处在于它还会为您提供以下结果:预订{entry.headline:' Lennon ', entry.pub_date: 2000}, {entry.headline: ' Bill',entry.pub_date: 2008 }

    在你的情况下

    我认为这是你需要的:

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

    如果您想使用OR,请阅读:https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects

相关问题