IN子查询的WHERE条件会影响主查询 - 这是一个功能还是一个错误?

时间:2013-12-16 04:17:46

标签: sql postgresql subquery postgresql-9.1 in-subquery

假设有两个表:

Table A: A1, A2, A_Other
Table B: B1, B2, B_Other

在以下示例中, is something 是针对固定值检查的条件,例如 = 'ABC' < 45

我写了一个类似以下(1)

的查询
Select * from A
Where A1 IN (
    Select Distinct B1 from B
    Where B2 is something
    And A2 is something
);

我真正打算写的是(2)

Select * from A
Where A1 IN (
    Select Distinct B1 from B
    Where B2 is something
)
And A2 is something;

奇怪的是,两个查询都返回了相同的结果。查看查询 1 解释计划时,看起来像执行子查询时,因为条件A2 is something不适用于子查询,它是延迟,用作主查询结果的过滤器。

我通常希望查询 1 失败,因为子查询本身会失败:

Select Distinct B1 from B
Where B2 is something
And A2 is something; --- ERROR:  column "A2" does not exist

但我发现情况并非如此,Postgres将不适用的子查询条件推迟到主查询。

这是标准行为还是Postgres异常? 这是哪里记录的,这个功能叫做什么?

此外,我发现如果我在表A2中添加列B,则只有查询 2 才能按预期工作。在这种情况下,查询 2 中的引用A2仍将引用A.A2,但查询 1 中的引用将引用新列{ {1}}因为它现在可以直接应用于子查询。

4 个答案:

答案 0 :(得分:5)

这里有很好的问题,很多人都遇到过这样的事情,但是懒得停下来看看。

您正在做的是在WHERE子句中编写子查询;不是FROM子句中的内联视图。有区别。

SELECTWHERE子句中编写子查询时,可以访问主查询的FROM子句中的表。这不仅仅发生在Postgres中,但它是一种标准行为,可以在所有领先的RDBMS中观察到,包括Oracle,SQL Server和MySQL。

当您运行第一个查询时,优化程序会查看整个查询并确定何时检查哪些条件。优化器的这种行为是您看到主查询的条件延迟,因为优化器发现在主查询本身中评估此条件而不影响最终结果会更快。

如果只运行子查询,注释掉主查询,则必然会在您提到的位置返回错误,因为找不到要引用的列。

在上一段中,您提到已向表A2添加了一列tableB。你观察到的是对的。这是因为隐含的参考现象。如果未提及列的表别名,则数据库引擎将首先在子查询的FROM子句的表中查找该列。仅当在那里找不到列时,才会引用主查询中的表。如果您使用以下查询,它仍将返回相同的结果:

Select * from A aa -- Check the alias
Where A1 IN (
    Select Distinct B1 from B bb
    Where B2 is something
    And aa.A2 is something -- Check the reference
);

也许您可以在Korth的关于关系数据库的书中找到更多信息,但我不确定。我刚刚根据我的观察回答了你的问题。我知道这发生了,为什么。我只是不知道如何为您提供进一步的参考资料。

答案 1 :(得分:2)

相关子查询: - 如果子查询的结果取决于其父查询表的列的值,则Sub查询称为相关子查询。这是标准行为,而不是错误。

父查询的选定列列表中不必包含相关查询所依赖的列。

Select * from A
Where A1 IN (
    Select Distinct B1 from B
    Where B2 is something
    And A2 is something
);

A2是表A的列,父查询在表A上。这意味着A2可以在子查询中引用。上面的查询可能比下面的查询工作得慢。

Select * from A
Where A2 is something And A1 IN (
    Select Distinct B1 from B
    Where B2 is something
);

这是因为来自父查询的A2在循环中被引用。这取决于要获取的数据的条件。如果子查询类似于

Select Distinct B1 from B
Where B2 is A2

我们必须引用父查询列。或者,我们可以使用连接。

答案 2 :(得分:2)

您已经解释了为什么WHERE子句中的相关子查询可以引用FROM列表中表格的所有列。

除此之外,使用JOINEXISTS半连接通常比相关子查询快。我会重写为 100%等效查询:

SELECT a.*
FROM   a
JOIN   (
   SELECT DISTINCT b1
   FROM   b
   WHERE  b2 is something
   ) b ON b.b1 = a.a1 
WHERE  a.a2 is something

或者,更好的是:

SELECT *
FROM   a
WHERE  EXISTS (
   SELECT 1 
   FROM   b
   WHERE  b.b1 = a.a1 
   AND    b.b2 is something
   )
AND    a.a2 is something;

答案 3 :(得分:0)

结果并不奇怪,子查询可以引用PARENT查询。这称为相关子查询,非常常见。在您的示例中,您使用了IN运算符,但通常使用IN操作OPTIMIZE查询是使用相关子查询将IN替换为EXISTS运算符。

详细说明Erwin关于EXISTS更快的评论,这是因为当你使用IN时,“有时”需要查询来查找集合的所有值。而使用EXISTS只需要找到第一次出现以满足条件。但是,查询计划可能优化两者都是相同的。但是使用EXISTS可以明确地帮助Optimizer更快地构建预期的查询计划。