带有WHERE子句的UNION

时间:2011-03-25 19:59:35

标签: sql oracle union

我在Oracle数据库上进行了两次UNION次查询。它们都有一个WHERE子句。如果我WHERE UNION UNION与执行WHERE 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 相比,性能是否存在差异?

例如:

{{1}}

与之相比:

{{1}}

我相信在第二种情况下,它会对影响性能的两个表执行全表扫描。这是对的吗?

9 个答案:

答案 0 :(得分:19)

根据我的经验,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约束,可能会有,但显然存在局限性:)

答案 1 :(得分:9)

注意:虽然多年前我的建议是正确的,但Oracle的优化器已经改进,因此这里的位置绝对不再重要。但是,首选UNION ALL vs UNION始终是正确的,并且可移植SQL应避免依赖可能不在所有数据库中的优化。

简短回答,您希望在WHERE之前使用UNION,并且如果可能的话,您想要使用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,所以你需要自己测试一下。

答案 2 :(得分:9)

小心点

如果你试过

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,使其成为一个非常不同的查询。如果列以这种方式存在别名,则可能会让人感到困惑。

答案 3 :(得分:7)

您需要查看解释计划,但除非COL_A上有INDEX或PARTITION,否则您正在查看两个表上的FULL TABLE SCAN。

考虑到这一点,你的第一个例子是抛出一些数据,就像它完成FULL TABLE SCAN一样。该结果由UNION排序,然后删除重复数据。这为您提供了结果集。

在第二个示例中,您将提取两个表的全部内容。结果可能会更大。所以UNION正在排序更多数据,然后丢弃重复的东西。然后应用过滤器为您提供您所追求的结果集。

作为一般规则,越早过滤数据,数据集越小,获得结果的速度就越快。与往常一样,您的milage可能会有所不同。

答案 4 :(得分:1)

我会确保你有一个关于ColA的索引,然后运行它们并计时。这会给你最好的答案。

答案 5 :(得分:1)

我认为这将取决于许多事情 - 在每个事件上运行EXPLAIN PLAN以查看优化程序选择的内容。否则 - 正如@rayman建议的那样 - 同时运行它们并计时。

答案 6 :(得分:0)

SELECT * FROM (SELECT colA, colB FROM tableA UNION SELECT colA, colB FROM tableB) as tableC WHERE tableC.colA > 1

如果我们在2个表中使用包含相同字段名称的union,那么我们需要将子查询的名称命名为tableC(在上面的查询中)。最后,WHERE条件应为WHERE tableC.colA > 1

答案 7 :(得分:-4)

SELECT colA, colB FROM tableA  WHERE colA > 1
UNION
SELECT colX, colA FROM tableB

答案 8 :(得分:-5)

SELECT * 
FROM (SELECT * FROM can
    UNION
    SELECT * FROM employee) as e
WHERE e.id = 1;