Django queryset exclude()带有多个相关字段子句



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

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

我的问题是,前一个问题的工作,但后一个问题(相同的查询子句,但与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中仍然存在的问题,其中exclude()不作为filter()的反向。下面是解释差异的文档:

<标题>注意

filter()对于跨多值查询的行为如上所述,关系并没有等价地实现exclude()。相反,单个exclude()调用中的条件不会必须指同一项

例如,下面的查询将排除包含两者的博客标题为"Lennon"的参赛作品和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,
    ),
)

我实现了一个快速和肮脏的解决方案,所以我可以继续,期望它是非常低效的;然而,在检查生成的SQL后,结果证明并没有那么糟糕:

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

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

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

我把这个作为一个可能的答案,但我仍然感兴趣的是,如果有一种方法可以用直接的排除子句来做到这一点,或者通过ORM生成查询的方法,该方法可以使用直接的连接,没有子查询。

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

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

最新更新