【问题标题】:Django queryset exclude() with multiple related field clauses具有多个相关字段子句的 Django queryset exclude()
【发布时间】:2013-05-18 06:14:24
【问题描述】:

我正在 Django 中创建一个稀疏首选项表。我的模型很简单:

class Preference(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='preferences')
    preference = models.CharField(max_length=255, db_index=True)
    value = models.BooleanField()

有些首选项有默认状态,所以我需要能够问数据库两个问题:“哪些用户将此首选项设置为某个值?”和“哪些用户没有将此首选项设置为该值(因为他们没有设置首选项或因为他们主动将首选项设置为另一个值)?”

我的问题是前一个问题有效,但后一个问题(相同的查询子句,但使用exclude() 而不是filter())不起作用。例如:

我的测试数据库有 14 个用户,单个用户有两个首选项集:'PREF_A' 设置为 True'PREF_B' 设置为 False

>>> User.objects.all().count()
14
>>> User.objects.filter(preferences__preference="PREF_A", preferences__value=True).count()
1
>>> User.objects.exclude(preferences__preference="PREF_A", preferences__value=True).count()
13
>>> User.objects.filter(preferences__preference="PREF_A", preferences__value=False).count()
0
>>> User.objects.exclude(preferences__preference="PREF_A", preferences__value=False).count()
13

所以,我的结果表明:

一共有14个用户

  • 1 个用户将 PREF_A 设置为 True

  • 13 个用户没有将 PREF_A 设置为 True

  • 0 个用户的 PREF_A 设置为 False

  • 13 个用户没有将 PREF_A 设置为 False

此查询哪里出错了,我如何编写查询以正确排除将特定偏好设置为特定值的人?

我尝试使用Q~Q 来查看行为是否会有所不同,但结果是相同的。

【问题讨论】:

    标签: django django-orm


    【解决方案1】:

    这是 Django 中仍然存在的问题,其中exclude() 不是filter() 的反面。这里是the documentation explaining the difference

    注意

    filter() 对跨多值查询的行为 如上所述,关系不是等效地实现的 exclude()。相反,单个 exclude() 调用中的条件不会 一定是指同一个项目。

    例如,以下查询将排除同时包含两者的博客 标题中带有“列侬”的条目和2008年发表的条目:

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

    但是,与使用filter() 时的行为不同,这不会限制 基于满足这两个条件的条目的博客。为了做 即选择所有不包含已发布条目的博客 与 2008 年出版的《列侬》,你需要做两个 查询:

    Blog.objects.exclude(
        entry__in=Entry.objects.filter(
            headline__contains='Lennon',
            pub_date__year=2008,
        ),
    )
    

    你所做的可能就是要走的路。

    【讨论】:

    • 谢谢,这个答案是反直觉的,但知道这一点非常重要!
    • 天哪。我要怎么感谢你?这将是我的圣诞礼物 :)
    【解决方案2】:

    我实施了一个快速而肮脏的解决方案,因此我可以继续前进,预计它会非常低效;然而,在检查生成的 SQL 后,结果证明它并没有那么糟糕:

    >>> User.objects.exclude(id__in=User.objects.filter(preferences__preference="PREF_A", preferences__value=True))
    

    我以为 ORM 会在完成之前将下级查询的结果加载到网络服务器的内存中(这是一个问题,因为我们的生产应用程序将拥有数百万用户),但实际上它正确地使用了子查询:

    >>> User.objects.exclude(id__in=User.objects.filter(preferences__preference="PREF_A", preferences__value=True)).values('id').query.sql_with_params()
    (u'SELECT "sgauth_user"."id" FROM "sgauth_user" WHERE NOT ("sgauth_user"."id" IN (SELECT U0."id" FROM "sgauth_user" U0 INNER JOIN "feeds_preference" U1 ON (U0."id" = U1."user_id") WHERE (U1."preference" = %s  AND U1."value" = %s )))', ('PREF_A', True))
    

    我将此作为一个可能的答案,但我仍然感兴趣是否有一种方法可以使用简单的 exclude 子句,或者一种通过 ORM 生成查询的方法,该方法适用于简单的连接并且没有子查询。

    【讨论】:

      【解决方案3】:

      您可以使用新的 django SubQuery 来避免对服务器进行 2 次查询:

      User.objects.exclude(id__in=SubQuery(User.objects.filter(preferences__preference="PREF_A", preferences__value=True)))
      

      【讨论】:

      • ORM 在没有SubQuery 的情况下自动执行此操作 - 检查 OP 的答案。
      猜你喜欢
      • 2020-02-06
      • 2014-08-07
      • 2020-05-25
      • 2011-08-05
      • 2016-06-30
      • 2022-06-17
      • 2011-05-26
      • 1970-01-01
      • 2019-08-17
      相关资源
      最近更新 更多