Django Q过滤器否定不会产生预期的查询

Posted

技术标签:

【中文标题】Django Q过滤器否定不会产生预期的查询【英文标题】:Django Q filter negating doesn't produce expected query 【发布时间】:2015-04-30 08:40:22 【问题描述】:

简化设置

class System(models.Model):
    fields...
    vulnerabilities = models.ManyToManyField('Vulnerability', through='SystemVulnerability')

class Vulnerablity(models.Model):
    name = ...
    risk = CharField(choices=['H','M','L','I'])
    ...

class SystemVulnerability(models.Model):
    vulnerability = ForeignKey
    system = ForeignKey
    fields...

sv_list = [199026, 199036, 199046, 199048, ....]
other_filter = Q(... lots of stuff...) #not including this for brevity

System.objects.filter(other_filter & Q(systemvulnerability__in=sv_list)).query.__str__()
u'SELECT "system"."id", "system"."ip", "system"."org_id", "system"."dnsname", "system"."ipownercode", "system"."securityplan" 
    FROM "system" 
    INNER JOIN "systemvulnerability" ON ( "system"."id" = "systemvulnerability"."system_id" ) 
    WHERE "systemvulnerability"."id" IN (199026, 199036, 199046, 199048, ....)'

看起来不错。如果我们否定它呢?

System.objects.filter(other_filter & ~Q(systemvulnerability__in=sv_list)).query.__str__()
u'SELECT "system"."id", "system"."ip", "system"."org_id", "system"."dnsname", "system"."ipownercode", "system"."securityplan" 
    FROM "system" 
    WHERE NOT ("system"."id" IN (SELECT U1."system_id" FROM "systemvulnerability" U1 WHERE U1."id" IN (199026, 199036, 199046, 199048, ....)))'

什么? 我希望得到的查询是:

u'SELECT "system"."id", "system"."ip", "system"."org_id", "system"."dnsname", "system"."ipownercode", "system"."securityplan" 
    FROM "system" 
    INNER JOIN "systemvulnerability" ON ( "system"."id" = "systemvulnerability"."system_id" ) 
    WHERE "systemvulnerability"."id" NOT IN (199026, 199036, 199046, 199048, ....)'

如何修改我的语句以使结果查询更符合我的预期?

Example data

System:
s1
s2
s3

Vulnerability:
v1
v2
v3

SystemVulnerability:
sv1 = s1, v1
sv2 = s1, v2
sv3 = s2, v2
sv4 = s3, v2
sv5 = s3, v3

System.filter(Q(systemvulnerability__in=[sv1, sv4]))
Get back s1, s3: Good!

System.filter(~Q(systemvulnerability__in=[sv1, sv2]))
Get back: s2, s3: Good!

System.filter(~Q(systemvulnerability__in=[sv1, sv4, sv5]))
Get back s2: Bad!
But I wanted s1, s2

显然我可以根据 SystemVulnerability 进行查询,但我的过滤器实际上比这要复杂得多,所以我想保持它基于 System.Vulnerability。

我想System.objects.filter(pk__in=SystemVulnerability.filter(~Q(systemvulnerability__in=sv_list)).values_list('system')) 可以工作,但在 SQL 中生成的 IN 子句相当大而且速度很慢,尤其是随着我的数据增长。

编辑:我去尝试用前一行的 IN 子句来做。它有效,但最终否定了我的许多其他过滤器。我真的只需要 ~Q() 才能正常工作!

【问题讨论】:

System.objects.filter(other_filter).exclude(systemvulnerability__in=sv_list)? 同~Q() AND NOT ("system"."id" IN 【参考方案1】:

这不是一个真正的答案,但几天后我设法考虑如何在不排除的情况下重写它。我的 SQL 开发人员对我的所作所为感到痛苦,但它在 django 中的效果要比我开始做的 .extra(where=[]) 好得多。那样的话我会重写太多查询。

【讨论】:

以上是关于Django Q过滤器否定不会产生预期的查询的主要内容,如果未能解决你的问题,请参考以下文章

Django 数据库操作

django基础知识之模型查询:

Django添加Q过滤器以查询相关对象存在时,条件查询

Django过滤查询和或

Django Q过滤器,无法在单个查询中获得结果

Django REST Framework 中的否定或排除过滤器