【问题标题】:subquery in WHERE clause with inverted Q object带有倒置 Q 对象的 WHERE 子句中的子查询
【发布时间】:2019-01-29 16:23:39
【问题描述】:

我有两个模型,其中一个引用另一个:

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

【问题讨论】:

  • 这是预期行为。 Django 已指定相关模型上的过滤器使用 existential 量词完成。存在量词的否定是带有否定谓词的全称量词。
  • 对于您的情况,您可以使用B.objects.filter(a__variable=False)(或者严格来说是B.objects.filter(Q(a__variable=None) | Q(a__variable=False))),这与B.objects.filter(Q(a__variable=True))不同
  • 但是将a__variable=False 更改为a__variable=True 应该与在Q 表达式前面放置一个反转相同。如果我按照正向参考,即A.object.filter(Q(b__another_var=True)),为什么这会起作用?
  • 不,因为这会使它与“谓词逻辑”不符。假设有两个相关的As,一个与variable=True,一个与variable=False,您希望查询B.objects.filter(~Q(a__variable=True)) 恰好包含 发生的Bs在查询 B.objects.filter(Q(a___variable=True)) 中(因为您反转了条件),但这里 B 对象将出现在 both 查询集中。

标签: python django postgresql orm


【解决方案1】:

简答:“所有B 对象与A 相关variable = True 对象”的否定不是查询“*all B objects with a related A object with variable = False”。

你可以这样查询:

B.objects.filter(a__variable=False)

或者如果字段是NULL-able:

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

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

这是预期行为。因为如果您以一对多的方式查询 相关 模型,Django ORM 的设计者选择了 存在量词 ∃ 而不是 通用量词 ∀ 。没有固有的最佳选择,尽管我认为人类将执行的大多数查询都是存在量化的*。

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

但是否定 不是B对象列表“其中存在一个相关的A对象与@ 987654336@"(让我们暂时忽略NULL 花瓶)。实际上,B 对象具有两个相关的A 对象,一个具有variable = True,另一个具有variable = False,将在原始变体及其否定中发生

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

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

因此,这意味着查询“存在Avariable=True 的所有B 对象”的否定是查询“*所有B 对象其中所有相关的A对象有一个variable不是B*”。注意第二个查询中的 all。因此,这意味着对于A 表中的每一行,我们需要“迭代”相关的“B”对象,以检查所有这些variables 是否不是True。这并不是为JOINs 真正“量身定制”的。在BooleanField 的情况下,我们可以使用GROUP BYMAX(..) 来检查是否至少存在一个这样的TRUE,从而限制它是不是TRUE。比如:

SELECT b.*, MAX(a.variable) AS mx
FROM b
LEFT OUTER JOIN b ON a.b_id = b.id
GROUP BY a.id
HAVING mx = FALSE OR mx IS NULL

但是这个“trick”需要 Django ORM 查询构建器进行一些“高级”tiling。这可能会在未来的版本中最终得到支持,但无论如何,效率将大致相同。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 2013-05-19
    • 1970-01-01
    • 1970-01-01
    • 2011-08-27
    • 1970-01-01
    • 2016-03-11
    相关资源
    最近更新 更多