16

我正在 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()

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

我的问题是前一个问题有效,但后一个问题(相同的查询子句,但使用 aexclude()而不是 a 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查看行为是否会有所不同,但结果是相同的。

4

3 回答 3

29

这是 Django 中仍然存在的一个问题,exclude()它没有作为filter(). 这是解释差异的文档

笔记

filter()如上所述,对于跨越多值关系的查询 的行为,对于exclude(). 相反,单个exclude()调用中的条件不一定会引用相同的项目。

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

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

但是,与使用 时的行为不同filter(),这不会根据满足这两个条件的条目来限制博客。为了做到这一点,即选择所有不包含在 2008 年发布的以“Lennon”发布的条目的博客,您需要进行两个查询:

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

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

于 2013-05-23T09:28:58.230 回答
6

我实施了一个快速而肮脏的解决方案,所以我可以继续前进,预计它会非常低效;然而,在检查生成的 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 生成查询的方法,该方法适用于直接的连接并且没有任何子查询。

于 2013-05-23T02:46:07.107 回答
2

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

User.objects.exclude(id__in=SubQuery(User.objects.filter(preferences__preference="PREF_A", preferences__value=True)))
于 2018-10-18T13:37:24.480 回答