Пусть Oracle преобразует или-связанные предикаты в UNION все операции

UNION и UNION ALL запросы могут превосходить эквивалентные запросы с помощью OR-связанные предикаты при определенных обстоятельствах. Насколько мне известно, это отчасти потому, что UNION подселекты могут выполняться параллельно, и поэтому они могут иметь свой собственный "подплан", специфичный для каждой части OR-связный предикат, который, вероятно, гораздо более оптимален из-за более простых применимых преобразований запросов.

но писать OR-связанные предикаты обычно много более читаемый и сжатый, даже если подзапрос факторинга были применены к UNION ALL решение. Мой вопрос: есть ли способ указать Oracle, что один, дорогостоящий OR-связный предикат должен быть преобразован в UNION ALL операции? Если есть такой намек / метод, при каких обстоятельствах он может быть применен (например, должны ли какие-либо ограничения присутствовать на столбцах, участвующих в предикатах, и т. д.)? Пример:

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

примечание, Я знаю о /*+ USE_CONCAT */ подсказка:

SELECT /*+ USE_CONCAT */ * FROM a
WHERE x = 1 OR y = 1

но он, похоже, не производит то, что мне нужно (нет принудительного UNION ALL операция в плане выполнения):

-------------------------------------------
| Id  | Operation         | Name | E-Rows |
-------------------------------------------
|   0 | SELECT STATEMENT  |      |        |
|*  1 |  TABLE ACCESS FULL| A    |      2 |
-------------------------------------------

может быть, есть какие-то ограничения на этот намек? Для этого у меня есть Oracle 11g2.

2 ответов


Я считаю, что это может иметь какое-то отношение к индексам, существующим в Столбцах, которые вы используете в OR сказуемое.

Я тестировал, используя следующее в 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;
/

затем я объяснил следующие запросы в 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                                

таким образом, похоже, подсказка не работает. Затем я добавил индекс к столбцам x & y:

create index test_x on test (x, y);

begin
   dbms_stats.gather_table_stats('scott', 'test');
end;
/

повторный запуск запросов сейчас:

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                                

похоже, что после добавления индекса (даже хотя это не используется) оптимизатор решил использовать подсказку в конце концов!

возможно, вы могли бы попробовать это?


в некоторых случаях оптимизатор игнорирует подсказки, и вам нужно написать запрос по-другому. Альтернативой перезаписи всего запроса с помощью UNION ALL, вы можете переписать только предложение:

SELECT * FROM a /* ... you can put here more joins with many tables ... */
WHERE a.rowid in (
    select innerQry.rowid from a innerQry where /*your first clause of OR*/innerQry.x = 1
    union all
    select innerQry.rowid from a innerQry where /*your second clause of OR*/innerQry.y = 1
)