Let Oracle transform OR-connected predicates into UNION ALL operations
optimization, oracle, sql, union, union-all
Solution
I believe this may have something to do with indexes existing on the columns you use in the `OR` predicate.
I tested using the following in 11gR2.
create table scott.test as
select level l,
decode(mod(level,2), 1, 1, 2) x,
decode(mod(level,2), 1, 2, 1) y,
dbms_random.value(1, 3) z from dual
connect by level < 1000;
/
begin
dbms_stats.gather_table_stats('scott', 'test');
end;
/
I then explained the following queries in TOAD, (`EXPLAIN PLAN FOR`)
select x, y, z from scott.test
where (floor(z) = 1 and x = 1) or (floor(z) = 2 and y = 1)
;
SELECT STATEMENT Optimizer Mode=ALL_ROWS 10 4
TABLE ACCESS FULL COS_DM.TEST 10 280 4
select /*+ USE_CONCAT */ x, y, z from scott.test
where (floor(z) = 1 and x = 1) or (floor(z) = 2 and y = 1)
;
SELECT STATEMENT Optimizer Mode=ALL_ROWS 10 4
TABLE ACCESS FULL COS_DM.TEST 10 280 4
select x, y, z from test where (floor(z) = 1 and x = 1)
union all
select x, y, z from test where (floor(z) = 2 and y = 1)
;
SELECT STATEMENT Optimizer Mode=ALL_ROWS 10 8
UNION-ALL
TABLE ACCESS FULL COS_DM.TEST 5 140 4
TABLE ACCESS FULL COS_DM.TEST 5 140 4
So it appears the hint's not working. I then added an index to the x & y columns:
create index test_x on test (x, y);
begin
dbms_stats.gather_table_stats('scott', 'test');
end;
/
Rerunning the queries now:
select x, y, z from scott.test
where (floor(z) = 1 and x = 1) or (floor(z) = 2 and y = 1)
;
SELECT STATEMENT Optimizer Mode=ALL_ROWS 10 4
TABLE ACCESS FULL COS_DM.TEST 10 280 4
select /*+ USE_CONCAT */ x, y, z from scott.test
where (floor(z) = 1 and x = 1) or (floor(z) = 2 and y = 1)
;
SELECT STATEMENT Optimizer Mode=ALL_ROWS 10 8
CONCATENATION
TABLE ACCESS FULL COS_DM.TEST 5 140 4
TABLE ACCESS FULL COS_DM.TEST 5 140 4
select x, y, z from test where (floor(z) = 1 and x = 1)
union all
select x, y, z from test where (floor(z) = 2 and y = 1)
;
SELECT STATEMENT Optimizer Mode=ALL_ROWS 10 8
UNION-ALL
TABLE ACCESS FULL COS_DM.TEST 5 140 4
TABLE ACCESS FULL COS_DM.TEST 5 140 4
It appears that after adding the index (even though it's not being used) the optimizer decided to use the hint after all!
Perhaps you could try this?
Problem
`UNION` and `UNION ALL` queries can outperform equivalent queries using `OR`-connected predicates under certain circumstances. To my knowledge, this is partially because `UNION` subselects can be executed in parallel and they can thus have their own "sub-plan" specific to each part of the `OR`-connected predicate, which is probably far more optimal due to simpler applicable query transformations. But writing `OR`-connected predicates is usually much more readable and concise, even if subquery factoring were applied to a `UNION ALL` solution. My question is: Is there a way to indicate to Oracle, that a single, costly `OR`-connected predicate should be transformed into a `UNION ALL` operation? If there is such a hint/method, under what circumstances can it be applied (e.g. do any constraints need to be present on the columns involved in the predicates, etc)? An example: ``` CREATE TABLE a AS SELECT 1 x, 2 y FROM DUAL UNION ALL SELECT 2 x, 1 y FROM DUAL; -- This query... SELECT * FROM a WHERE x = 1 OR y = 1 -- Is sometimes outperformed by this one, for more complex table sources... -- Note: in my case, I can safely apply UNION ALL. I know the two predicates to -- be mutually exclusive. SELECT * FROM a WHERE x = 1 UNION ALL SELECT * FROM a WHERE y = 1 ``` Note, I'm aware of the `/*+ USE_CONCAT */` hint: ``` SELECT /*+ USE_CONCAT */ * FROM a WHERE x = 1 OR y = 1 ``` But it doesn't seem to produce what I need (no forced `UNION ALL` operation in the execution plan): ``` ------------------------------------------- | Id | Operation | Name | E-Rows | ------------------------------------------- | 0 | SELECT STATEMENT | | | |* 1 | TABLE ACCESS FULL| A | 2 | ------------------------------------------- ``` Maybe, there is some restriction to this hint? I have Oracle 11g2 available for this.