我在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()
Run Code Online (Sandbox Code Playgroud)
某些首选项具有默认状态,因此我需要能够向数据库询问两个问题:"哪些用户将此首选项设置为某个值?" 和"哪些用户没有将此首选项设置为该值(因为他们没有设置首选项,或者因为他们主动将首选项设置为另一个值)?"
我的问题是前一个问题有效,但后一个问题(相同的查询条款,但用一个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
Run Code Online (Sandbox Code Playgroud)
所以,我的结果说:
共有14位用户
1个用户将PREF_A设置为True
13个用户没有将PREF_A设置为True
0个用户将PREF_A设置为False
13个用户没有将PREF_A设置为False <---这是不准确的
此查询在哪里出错,如何编写查询以正确排除将特定首选项设置为特定值的人?
我尝试过使用Q并~Q查看行为是否会有所不同,但结果是一样的.
M S*_*lle 15
这是Django中仍然存在的陷阱exclude()并不是反向的filter.这是解释差异的文档:
注意
如上所述
filter(),跨越多值关系的查询的行为不是等效实现的exclude().相反,单个exclude()调用中的条件不一定是指同一个项目.例如,以下查询将排除包含标题中带有"Lennon"和2008年发布的条目的博客的博客:
Run Code Online (Sandbox Code Playgroud)Blog.objects.exclude( entry__headline__contains='Lennon', entry__pub_date__year=2008, )但是,与使用时的行为不同
filter(),这不会限制基于满足这两个条件的条目的博客.为了做到这一点,即选择所有不包含2008年发布的"Lennon"条目的博客,您需要进行两次查询:Run Code Online (Sandbox Code Playgroud)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))
Run Code Online (Sandbox Code Playgroud)
我认为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))
Run Code Online (Sandbox Code Playgroud)
我把它作为一个可能的答案,但我仍然感兴趣,如果有一种方法可以使用简单的排除子句,或通过ORM生成查询的方式,该查询使用简单的连接和任何子查询.