sql-带WHERE子句的UNION

我正在对一个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

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

MNIK asked 2020-08-11T08:39:17Z
9个解决方案
20 votes

以我的经验,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约束条件下有可能实现,但显然存在局限性:)

Ronnis answered 2020-08-11T08:39:40Z
9 votes

请注意

如果你尝试过

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,这使其成为一个非常不同的查询。 如果以这种方式为列加上别名,则可能会造成混淆。

Gary Myers answered 2020-08-11T08:40:13Z
9 votes

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

简短的答案是,您希望UNION ALLGROUP BY之前,并且您想尽可能使用GROUP BY。 如果使用WHERE,然后检查EXPLAIN输出,则Oracle可能足够聪明,可以优化HAVING的条件(如果将其保留)。

原因如下。 UNION ALL的定义说,如果两个数据集中有重复项,则必须将其删除。 因此,该操作中存在隐式GROUP BY,该速度通常很慢。 更糟糕的是,Oracle的优化器(至少在3年前,并且我认为它没有发生变化)没有尝试通过GROUP BY(隐式或显式)来推动条件。 因此,Oracle必须构造比必要的更大的数据集,对它们进行分组,然后才进行过滤。 因此,在任何可能的情况下进行预过滤都是一个好主意。 (顺便说一下,这就是为什么在任何可能的情况下都将条件放入WHERE而不是将其留在HAVING子句中很重要的原因。)

此外,如果您碰巧知道两个数据集之间不会有重复项,请使用UNION ALL。就像GROUP BY那样,它连接数据集,但不会尝试对数据进行重复数据删除。 这节省了昂贵的分组操作。 以我的经验,能够利用此操作非常普遍。

由于UNION ALL中没有隐式GROUP BY,因此Oracle的优化程序很可能知道如何通过它推动条件。 我没有Oracle来测试,因此您需要自己进行测试。

btilly answered 2020-08-11T08:40:54Z
7 votes

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

考虑到这一点,您的第一个示例就像进行FULL TABLE SCAN一样抛出一些数据。 该结果由UNION排序,然后删除重复的数据。 这给您您的结果集。

在第二个示例中,您将提取两个表的全部内容。 该结果可能更大。 因此,UNION正在排序更多数据,然后删除重复的数据。 然后,将应用过滤器以提供所需的结果集。

通常,过滤掉数据的时间越早,数据集越小,获得结果的速度就越快。 与往常一样,您的里程可能会有所不同。

EvilTeach answered 2020-08-11T08:41:28Z
2 votes

我将确保您在ColA上有一个索引,然后同时运行它们和计时。 那会给你最好的答案。

rayman86 answered 2020-08-11T08:41:49Z
1 votes

我认为这将取决于很多事情-在每一项上运行EXPLAIN PLAN以查看您的优化器选择了什么。 否则,如@rayman所建议的那样,同时运行它们并计时。

Randy answered 2020-08-11T08:42:09Z
0 votes
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

Anbarasi Selvaraj answered 2020-08-11T08:42:29Z
-4 votes
SELECT colA, colB FROM tableA  WHERE colA > 1
UNION
SELECT colX, colA FROM tableB
Michael Andrews answered 2020-08-11T08:42:45Z
-5 votes
SELECT * 
FROM (SELECT * FROM can
    UNION
    SELECT * FROM employee) as e
WHERE e.id = 1;
nandhini answered 2020-08-11T08:43:01Z
translate from https://stackoverflow.com:/questions/5437507/union-with-where-clause