带有 WHERE 子句的 UNION
Posted
技术标签:
【中文标题】带有 WHERE 子句的 UNION【英文标题】:UNION with WHERE clause 【发布时间】:2011-07-23 04:34:54 【问题描述】:我正在对 Oracle 数据库执行两个查询的UNION
。它们都有一个WHERE
子句。如果我在UNION
查询之后执行WHERE
与在WHERE
子句之后执行UNION
相比,性能是否有差异?
例如:
SELECT colA, colB FROM tableA WHERE colA > 1
UNION
SELECT colA, colB FROM tableB WHERE colA > 1
相比:
SELECT *
FROM (SELECT colA, colB FROM tableA
UNION
SELECT colA, colB FROM tableB)
WHERE colA > 1
我相信在第二种情况下,它会对影响性能的两个表执行全表扫描。对吗?
【问题讨论】:
获取解释计划并证明您的信念。然后在您的环境中运行测试并计时,看看哪一个获胜。 对于这样一个简单的查询,可能没有区别,因为 Oracle 可能会将谓词(WHERE 子句)推送到派生表/内联视图中。 了解您正在运行的 Oracle 版本可能很有价值。 【参考方案1】:根据我的经验,Oracle 非常擅长推动简单 谓词。以下测试是在 Oracle 11.2 上进行的。我相当肯定它也会在所有 10g 版本上产生相同的执行计划。
(请大家,如果您运行早期版本并尝试以下操作,请随时发表评论)
create table table1(a number, b number);
create table table2(a number, b number);
explain plan for
select *
from (select a,b from table1
union
select a,b from table2
)
where a > 1;
select *
from table(dbms_xplan.display(format=>'basic +predicate'));
PLAN_TABLE_OUTPUT
---------------------------------------
| Id | Operation | Name |
---------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | VIEW | |
| 2 | SORT UNIQUE | |
| 3 | UNION-ALL | |
|* 4 | TABLE ACCESS FULL| TABLE1 |
|* 5 | TABLE ACCESS FULL| TABLE2 |
---------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter("A">1)
5 - filter("A">1)
正如您在步骤 (4,5) 中看到的,谓词被下推并在排序(联合)之前应用。
我无法让优化器下推整个子查询,例如
where a = (select max(a) from empty_table)
或加入。有了适当的 PK/FK 约束,这可能是可能的,但显然存在限制:)
【讨论】:
【参考方案2】:注意:虽然我的建议在多年前是正确的,但 Oracle 的优化器已经改进,因此 where 的位置在这里绝对不再重要。然而,更喜欢 UNION ALL
与 UNION
总是正确的,并且可移植 SQL 应避免依赖于可能并非所有数据库中的优化。
简短的回答,您希望在UNION
之前使用WHERE
,并且尽可能使用UNION ALL
。如果您使用的是UNION ALL
,然后检查 EXPLAIN 输出,Oracle 可能足够聪明,可以优化 WHERE
条件(如果它留在后面)。
原因如下。 UNION
的定义表示如果两个数据集中有重复项,则必须将其删除。因此,该操作中有一个隐含的GROUP BY
,它往往很慢。更糟糕的是,Oracle 的优化器(至少在 3 年前,我认为它没有改变)不会尝试通过 GROUP BY
(隐式或显式)推动条件。因此,Oracle 必须构建比必要更大的数据集,对它们进行分组,然后才能进行过滤。因此,在可能的情况下进行预过滤正式是一个好主意。 (顺便说一句,这就是为什么尽可能将条件放在WHERE
中而不是将它们放在HAVING
子句中很重要的原因。)
此外,如果您碰巧知道两个数据集之间不会重复,请使用UNION ALL
。就像UNION
一样,它连接数据集,但不会尝试删除重复数据。这节省了昂贵的分组操作。根据我的经验,能够利用此操作是很常见的。
由于UNION ALL
中没有隐含的GROUP BY
,Oracle 的优化器可能知道如何通过它推送条件。我没有 Oracle 坐下来测试,所以你需要自己测试。
【讨论】:
这是不正确的,至少对于过去 10 年创建的数据库而言。 @JonHeller 你在 2016 年回复了一篇写于 2011 年的帖子,该帖子被明确标记为基于我几年前的经验。很多事情都可能发生变化,简明扼要地说明什么比毯子更有帮助,“那是错误的”。 11.2 Performance Tuning Guide 中的示例显示了这种情况以及即使在UNION
之后使用WHERE
时它是如何工作的。我在 10g 手册中没有看到相同的内容,所以它要么是 11g 中的新内容,要么是 10g 中没有记录的。根据新信息判断答案可能是不公平的,但答案不再对人们有帮助。我知道不该投的反对票很糟糕,但总比向成千上万的人提供误导性建议更糟糕。
@JonHeller 我知道它在 8 中不存在,而且我很确定它在 10 中也不存在。但是,如果您要访问不同的数据库,它是不是可以依赖的行为。根据 SQL 标准,更喜欢 UNION ALL 而不是 UNION 将永远是正确的。但我会更新答案。【参考方案3】:
请注意
如果你尝试过
SELECT colA, colB FROM tableA WHERE colA > 1
UNION
SELECT colX, colA FROM tableB WHERE colA > 1
相比:
SELECT *
FROM (SELECT colA, colB FROM tableA
UNION
SELECT colX, colA FROM tableB)
WHERE colA > 1
然后在第二个查询中,where 子句中的 colA 实际上将具有来自 tableB 的 colX,使其成为一个非常不同的查询。如果以这种方式对列进行别名,可能会让人感到困惑。
【讨论】:
【参考方案4】:您需要查看说明计划,但除非 COL_A 上存在 INDEX 或 PARTITION,否则您正在查看两个表上的 FULL TABLE SCAN。
考虑到这一点,您的第一个示例是在执行 FULL TABLE SCAN 时丢弃一些数据。该结果由 UNION 排序,然后删除重复数据。这将为您提供结果集。
在第二个示例中,您将提取两个表的全部内容。这个结果可能会更大。所以 UNION 正在对更多数据进行排序,然后删除重复的内容。然后应用过滤器为您提供所需的结果集。
作为一般规则,您越早过滤掉数据,数据集越小,您获得结果的速度就越快。与往常一样,您的里程可能会有所不同。
【讨论】:
【参考方案5】:我会确保你在 ColA 上有一个索引,然后运行它们并为它们计时。这会给你最好的答案。
【讨论】:
我没有投反对票,但这可能是对普遍存在的“添加索引以解决任何性能问题”心态的反应。【参考方案6】:SELECT * FROM (SELECT colA, colB FROM tableA UNION SELECT colA, colB FROM tableB) as tableC WHERE tableC.colA > 1
如果我们在 2 个表中使用包含相同字段名称的联合,那么我们需要为子查询命名为 tableC(在上面的查询中)。最后,WHERE
条件应该是WHERE tableC.colA > 1
【讨论】:
嗨,欢迎来到 Stack Overflow。考虑为您的答案添加一点解释。你可以通过点击“编辑”来做到这一点【参考方案7】:我认为这将取决于很多事情 - 在每个事情上运行 EXPLAIN PLAN
以查看您的优化器选择了什么。否则 - 正如@rayman 建议的那样 - 运行它们并计时。
【讨论】:
【参考方案8】:SELECT colA, colB FROM tableA WHERE colA > 1
UNION
SELECT colX, colA FROM tableB
【讨论】:
【参考方案9】:SELECT *
FROM (SELECT * FROM can
UNION
SELECT * FROM employee) as e
WHERE e.id = 1;
【讨论】:
虽然 SQL 没有为原始问题提供解决方案,但您应该始终考虑为答案提供支持信息。以上是关于带有 WHERE 子句的 UNION的主要内容,如果未能解决你的问题,请参考以下文章
Mysql Unknown column in where 子句 union all
强制 MySQL 从 WHERE IN 子句返回重复项而不使用 JOIN/UNION?