WHERE子句中带有反向Q对象的子查询

时间:2018-08-23 10:04:59

标签: python django postgresql orm

我有两种模型,其中一种是指另一种:

class A(models.Model):                                                          

    variable = models.BooleanField(default=False, null=False)                   
    b = models.ForeignKey(B, on_delete=models.CASCADE, related_name='as', related_query_name="a") 


class B(models.Model):
    pass                                                                        

我想在过滤variable时追随该关系:

B.objects.filter(~Q(a__variable))

问题: 这样会在where子句中产生一个额外的子查询:

'SELECT "b"."id" FROM "b" WHERE NOT ("b"."id" IN (SELECT U1."b_id" FROM "a" U1 WHERE U1."variable" = True))'

另一方面,当不反转Q表达式时

B.objects.filter(Q(a__variable))

联接是“正确”完成的,即在where子句之外:

'SELECT "b"."id" FROM "b" INNER JOIN "a" ON ("b"."id" = "a"."b_id") WHERE "a"."variable" = True'

注意::我仅以布尔值为例(可以将其转换为False

我正在使用Django 2.0.4和Postgres 9.6.2

1 个答案:

答案 0 :(得分:3)

简短答案:“所有带有相关B对象和A对象的variable = True对象的求反不是查询“ *所有与B相关的A对象相关的variable = False对象”。

您可以像这样查询:

B.objects.filter(a__variable=False)

或者该字段是NULL允许的:

B.objects.filter(Q(a__variable=None) | Q(a__variable=False))

背景:否定存在的量化表达式

这是预期行为。由于如果您以一对多的方式查询 related 模型,则Django ORM的设计人员已选择 existential量词∃而不是通用量词∀ 。虽然我认为人类将执行的大多数查询都是存在量化的*,但没有固有的最佳选择

存在量词的意思是“存在”,因此如果您写B.objects.filter(a__variable=True),则会要求B对象,其中“ 存在一个相关的A对象variable=True”。

但是,否定不是 B个对象的列表,“ 存在的地方有一个相关的{{1} } A”对象(让我们暂时忽略variable=False花瓶)。实际上,在两个原始变体中,一个NULL对象具有两个相关的B对象,一个带有A,一个带有variable = True的对象会出现,及其否定。

存在量化要求的否定是该谓词否定的普遍量化变体。或在数学中:

¬∃ x :P(x)↔ x :¬P(x)

因此,这意味着查询“ 所有variable = FalseB A对象的否定查询” *与 all 相关的所有variable=True对象的所有B对象都具有一个 not A *“的variable。注意第二个查询中的 all 。因此,这意味着对于B表中的每一行,我们需要“迭代”相关的“ B”对象,以检查所有这些A是否都是 not variable。对于True来说,这并不是真正的“定制”。对于JOIN,我们可以用BooleanFieldGROUP BY来做到这一点,以检查是否至少有一个这样的MAX(..)存在,并且因此将其约束为不是 TRUE。像这样:

TRUE

但是Django ORM查询构建器需要使用此“ 技巧”进行一些“高级” 平铺。也许在将来的版本中最终会获得支持,但是无论如何,效率将大致相同。