Отправляет email-рассылки с помощью сервиса Sendsay

Открыто о СУБД Oracle на русском

  Все выпуски  

Открыто о СУБД Oracle на русском : особенности использования представлений...


Информационный Канал Subscribe.Ru

Выпуск 83

Уважаемые подписчики рассылки!

Этот выпуск посвящен особенностям использования представлений, в частности, содержащих аналитические функции. По мотивам интересного, с моей точки зрения, обсуждения на сайте Тома Кайта.

Проталкивание условия в представлениях, содержащих аналитические функции

У меня в базе данных есть представление, содержащее аналитическую функцию. Аналитическая функция проходит окном по набору строк таблицы и возвращает их подмножество. Начальное тестирование использования этого представления дало весьма многообещающие результаты, поскольку при использовании аналитической функции серверу для получения ответа потребовалось выполнить НАМНОГО меньше действий, чем при соединении таблицы с самой собой (ранее использовался такой подход). При этом использовались запросы к представлению, отбирающие данные по тому же столбцу, по которому выполнялось секционирование таблицы в аналитической функции. Однако, когда в запросе к представлению я задал условие по другому столбцу в конструкции WHERE, производительность существенно снизилась. Мое исследование показало, что условие не было протолкнуто в представление, и в результате пришлось читать всю таблицу, разбивать на секции и запоминать, прежде чем было применено условие.

Нет ли способа принудительно протолкнуть условие в представление? Я пытался использовать подсказки вроде PUSH_PRED, но так и не смог ничего добиться. Нет ли другого способа построить представление для получения необходимых мне данных?

Ниже представлен длинный фрагмент сеанса SQL*Plus, в котором описаны подробности проведенного исследования.

 
SQL>drop table t; 
 
Table dropped. 
 
Elapsed: 00:00:00.41 
SQL>create table t( Group_id    VARCHAR2(32), 
  2                  Group_Key   NUMBER, 
  3                  col2        NUMBER ); 
 
Table created. 
 
Elapsed: 00:00:00.40 
SQL>insert into t 
  2      select object_name, 
  3             ROW_NUMBER() OVER( partition by object_name 
  4                                order by object_id ), 
  5             object_id 
  6      from all_objects 
  7      where rownum<=25000 
  8  / 
 
24862 rows created. 
 
Elapsed: 00:00:10.05 
SQL>insert into t 
  2      select Group_id || '1', 
  3             Group_Key, 
  4             col2 * -1 
  5      from t 
  6  / 
 
24862 rows created. 
 
Elapsed: 00:00:02.03 
SQL>commit; 
 
Commit complete. 
 
Elapsed: 00:00:00.90 
SQL>-- Создаем индексы по 2 столбцам. 
SQL>-- Один - по столбцу, используемому для секционирования в аналитической функции 
SQL>-- Другой - по еще одному столбцу таблицы 
SQL>create index IDX_T_GRPID on T(GROUP_ID); 
 
Index created. 
 
Elapsed: 00:00:08.63 
SQL>create index IDX_T_COL2 on T(COL2); 
 
Index created. 
 
Elapsed: 00:00:09.05 
SQL>-- Вычисляем статистическую информацию для CBO 
SQL>analyze table t compute statistics; 
 
Table analyzed. 
 
Elapsed: 00:00:14.51 
SQL>-- Создаем пакет, который будет использоваться для отслеживания 
SQL>--   количества строк, обработанных аналитической функцией. 
SQL>-- Он также будет использоваться, чтобы отбросить некоторые строки, 
SQL>--   обработанные аналитической функцией. 
SQL>create or replace package test_anafunc is 
  2      function test_func( x NUMBER ) return number; 
  3   
  4      function get_Count return number; 
  5      procedure reset_count; 
  6  end; 
  7  / 
 
Package created. 
 
Elapsed: 00:00:00.70 
SQL>create or replace package body test_anafunc is 
  2   
  3      p_Count     NUMBER(38); 
  4   
  5      function test_func( x NUMBER ) return number is 
  6      begin 
  7          p_Count := p_Count + 1; 
  8   
  9          if( x > 1 ) then 
 10              return 1; 
 11          end if; 
 12   
 13          return 0; 
 14      end test_func; 
 15   
 16      function get_Count return number is 
 17      begin 
 18          return p_Count; 
 19      end get_Count; 
 20   
 21      procedure reset_count is 
 22      begin 
 23          p_Count := 0; 
 24      end reset_Count; 
 25   
 26  begin 
 27      p_Count := 0; 
 28  end; 
 29  / 
 
Package body created. 
 
Elapsed: 00:00:00.70 
SQL>-- Создаем представление, содержащее аналитическую функцию. 
SQL>-- Оно имитирует представление, которое будет открыто приложением 
SQL>--   конченым пользователям. 
SQL>create or replace view test_view as 
  2  select a.group_id, a.group_key, a.col2 
  3  from (select t.group_id, t.group_key, t.col2, 
  4               ROW_NUMBER() OVER( PARTITION BY GROUP_ID 
  5                                  ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM 
  6        from t 
  7        where test_anafunc.test_func(GROUP_KEY) = 1 ) a 
  8  where a.RNUM = 1 
  9  / 
 
View created. 
 
Elapsed: 00:00:00.90 
SQL>-- Давайте посмотрим, как выполняются запросы к этому представлению. 
SQL> 
SQL>-- Запрашиваем представление по group_id. 
SQL>--   Выбираем такое значение из таблицы t, для которого есть более одной строки. 
SQL>-- Мы должны увидеть вторую строку группы (Group_key=2). 
SQL>-- План выполнения должен показывать использование индекса. 
SQL>-- Вызов test_anafunc.get_Count должен вернуть количество строк для 
SQL>--   соответствующего значения group_id. 
SQL>-- Это показывает, что условие протолкнуто во вложенное представление. 
SQL>execute test_anafunc.reset_Count 
 
PL/SQL procedure successfully completed. 
 
Elapsed: 00:00:00.10 
SQL>set autotrace on explain statistics 
SQL>select * from test_View where group_id = 'TRACE'; 
 
GROUP_ID                          GROUP_KEY       COL2 
-------------------------------- ---------- ---------- 
TRACE                                     2       7942 
 
Elapsed: 00:00:00.21 
 
Execution Plan 
---------------------------------------------------------- 
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1 Bytes=57) 
   1    0   VIEW (Cost=5 Card=1 Bytes=57) 
   2    1     WINDOW (SORT) 
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=1 Bytes=27) 
   4    3         INDEX (RANGE SCAN) OF 'IDX_T_GRPID' (NON-UNIQUE) (Cost=1 Card=1) 
 
Statistics 
---------------------------------------------------------- 
         42  recursive calls 
          0  db block gets 
          9  consistent gets 
          0  physical reads 
          0  redo size 
        486  bytes sent via SQL*Net to client 
        425  bytes received via SQL*Net from client 
          2  SQL*Net roundtrips to/from client 
          3  sorts (memory) 
          0  sorts (disk) 
          1  rows processed 
 
SQL>set autotrace off 
SQL>select test_anafunc.get_Count from sys.dual; 
 
 GET_COUNT 
---------- 
         7 
 
Elapsed: 00:00:00.20 
SQL>-- Запрос по столбцу, не использованному для секционирования. 
SQL>--   Выбираем object_id, существующий в таблице t в столбце col2. 
SQL>-- Должны получить одну строку. 
SQL>-- Однако, план выполнения не покажет использования индекса. Вместо 
SQL>--   этого вы увидите полный просмотр таблицы. 
SQL>-- Вызов test_anafunc.get_Count вернет значение, равное общему 
SQL>--    количеству строк. 
SQL>-- Это показывает, что условие НЕ протолкнуто во вложенное представление 
SQL>--   и что мы обрабатываем всю таблицу, а не только те строки, 
SQL>--   которые удовлетворяют условиям конструкции where. 
SQL>execute test_anafunc.reset_Count 
 
PL/SQL procedure successfully completed. 
 
Elapsed: 00:00:00.10 
SQL>/ 
 
 GET_COUNT 
---------- 
         0 
 
Elapsed: 00:00:00.10 
SQL>set autotrace on explain statistics 
SQL>select * from test_view where col2 = 10816; 
 
GROUP_ID                          GROUP_KEY       COL2 
-------------------------------- ---------- ---------- 
ADDRESSLOCATION_SDOGEOM                   2      10816 
 
Elapsed: 00:00:05.58 
 
Execution Plan 
---------------------------------------------------------- 
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=40 Card=498 Bytes=28386) 
   1    0   VIEW (Cost=40 Card=498 Bytes=28386) 
   2    1     WINDOW (SORT) 
   3    2       TABLE ACCESS (FULL) OF 'T' (Cost=36 Card=498 Bytes=13446) 
 
Statistics 
---------------------------------------------------------- 
         50  recursive calls 
         11  db block gets 
        242  consistent gets 
        251  physical reads 
          0  redo size 
        505  bytes sent via SQL*Net to client 
        425  bytes received via SQL*Net from client 
          2  SQL*Net roundtrips to/from client 
          2  sorts (memory) 
          1  sorts (disk) 
          1  rows processed 
 
SQL>set autotrace off 
SQL>select test_anafunc.get_Count from sys.dual; 
 
 GET_COUNT 
---------- 
     49724 
 
Elapsed: 00:00:00.10 
SQL>-- Сравните это с тем, что я хотел бы получить для запроса по col2. 
SQL>execute test_anafunc.reset_Count 
 
PL/SQL procedure successfully completed. 
 
Elapsed: 00:00:00.10 
SQL>/ 
 
 GET_COUNT 
---------- 
         0 
 
Elapsed: 00:00:00.10 
SQL>set autotrace on explain statistics 
SQL>select a.group_id, a.group_key, a.col2 
  2  from (select t.group_id, t.group_key, t.col2, 
  3               ROW_NUMBER() OVER( PARTITION BY GROUP_ID 
  4                                  ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM 
  5        from t 
  6        where test_anafunc.test_func(GROUP_KEY) = 1 
  7            and col2 = 10816 ) a 
  8  where a.RNUM = 1 
  9  / 
 
GROUP_ID                          GROUP_KEY       COL2 
-------------------------------- ---------- ---------- 
ADDRESSLOCATION_SDOGEOM                   2      10816 
 
Elapsed: 00:00:00.11 
 
Execution Plan 
---------------------------------------------------------- 
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=57) 
   1    0   VIEW (Cost=4 Card=1 Bytes=57) 
   2    1     WINDOW (SORT) 
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=1 Bytes=27) 
   4    3         INDEX (RANGE SCAN) OF 'IDX_T_COL2' (NON-UNIQUE) (Cost=1 Card=1) 
 
Statistics 
---------------------------------------------------------- 
         35  recursive calls 
          0  db block gets 
          7  consistent gets 
          0  physical reads 
          0  redo size 
        505  bytes sent via SQL*Net to client 
        425  bytes received via SQL*Net from client 
          2  SQL*Net roundtrips to/from client 
          3  sorts (memory) 
          0  sorts (disk) 
          1  rows processed 
 
SQL>set autotrace off 
SQL>select test_anafunc.get_Count from sys.dual 
  2  / 
 
 GET_COUNT 
---------- 
         1 
 
Elapsed: 00:00:00.10 

Ответ Тома Кайта

Этого нельзя сделать — при этом ИЗМЕНИТСЯ ответ. Вы думаете, что ваши два запроса эквивалентны, но это НЕ ТАК.

Есть ОГРОМНАЯ разница между

 
select analytic function 
  from t 
 where <условие> 

и

 
select *  
  from ( select analytic function 
           from t ) 
 where <условие> 

Они даже и близко НЕ СРАВНИМЫ. Вообще.

Этот запрос говорит:

 
SQL>select a.group_id, a.group_key, a.col2 
  2  from (select t.group_id, t.group_key, t.col2, 
  3               ROW_NUMBER() OVER( PARTITION BY GROUP_ID 
  4                                  ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM 
  5        from t 
  6        where test_anafunc.test_func(GROUP_KEY) = 1 
  7            and col2 = 10816 ) a 
  8  where a.RNUM = 1 
  9  / 

Выбери строки из T, пропуская строки с group_key 1 (это делает test_func), и выбирая те, в которых col2 = 10816.

Затем, для всех таких строк - секционируй по group_id и отсортируй по group_key, присваивая row_number.

Затем, оставь только первую строку.

А этот запрос:

 
select *  
 from (select a.group_id, a.group_key, a.col2 
    from (select t.group_id, t.group_key, t.col2, 
                 ROW_NUMBER() OVER( PARTITION BY GROUP_ID 
                                    ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM 
          from t 
          where test_anafunc.test_func(GROUP_KEY) = 1 ) a 
 where col2 = 10816  
   and a.RNUM = 1 
/ 

говорит:

Найди все строки, в которых group_key не равен 1. Секционируй их по group_id. Отсортируй по group_key и присвой row_number.

Теперь, когда это сделано, оставь только первую строку ПРИ УСЛОВИИ, что в ней col2 = 10816.

Вот доказательство того, что эти запросы отличаются — они ВЕСЬМА, ВООБЩЕ и полностью отличаются:

 
ops$tkyte@ORA817DEV.US.ORACLE.COM> drop table t 
  2  / 
 
Table dropped. 
 
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t( Group_id    VARCHAR2(32), 
  2                   Group_Key   NUMBER, 
  3                   col2        NUMBER ) 
  4  / 
 
Table created. 
 
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'x', 2, 10815 ); 
 
1 row created. 
 
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( 'x', 3, 10816 ); 
 
1 row created. 
 
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace view test_view as 
  2   select a.group_id, a.group_key, a.col2 
  3   from (select t.group_id, t.group_key, t.col2, 
  4                ROW_NUMBER() OVER( PARTITION BY GROUP_ID 
  5                                   ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM 
  6         from t 
  7         where test_anafunc.test_func(GROUP_KEY) = 1 ) a 
  8   where a.RNUM = 1 
  9  / 
 
View created. 
 
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from test_view where col2 = 10816; 
 
no rows selected 
 
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * 
  2    from ( 
  3   select a.group_id, a.group_key, a.col2 
  4   from (select t.group_id, t.group_key, t.col2, 
  5                ROW_NUMBER() OVER( PARTITION BY GROUP_ID 
  6                                   ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM 
  7         from t 
  8         where test_anafunc.test_func(GROUP_KEY) = 1 ) a 
  9   where a.RNUM = 1 
 10   ) 
 11   where col2 = 10816 
 12  / 
 
no rows selected 
 
ops$tkyte@ORA817DEV.US.ORACLE.COM> select a.group_id, a.group_key, a.col2 
  2   from (select t.group_id, t.group_key, t.col2, 
  3                ROW_NUMBER() OVER( PARTITION BY GROUP_ID 
  4                                   ORDER BY GROUP_KEY ASC NULLS LAST ) RNUM 
  5         from t 
  6         where test_anafunc.test_func(GROUP_KEY) = 1 
  7             and col2 = 10816 ) a 
  8   where a.RNUM = 1 
  9  / 
 
GROUP_ID                          GROUP_KEY       COL2 
-------------------------------- ---------- ---------- 
x                                         3      10816 

Делаю ли я запрос к представлению или запрос с вложенным представлением — данных нет (по определению — такой запрос не должен возвращать данных).

Делаем так, как вы хотите — и получаем строку. Но это потому, что задавался абсолютно другой вопрос!

Достаточно ясно :)

Я подозревал, что причина такого поведения — изменение ответа. Спасибо за хороший пример, который это доказал.

Не могли бы вы предложить другой подход для получения того, что мне нужно? Важно, чтобы результаты каждого запроса к таблице обрабатывались аналитической функцией. Т.е. какие бы условия пользователь/приложение не задавали бы для поиска подмножества строк, надо прогнать их через функцию test_func для сокращения количества, а затем, для оставшегося набора строк, надо применить аналитическую функцию с окном, для выбора одной из них (RNUM=1).

Моя проблема лишь в том, что я не могу сформулировать корректный SQL-оператор для такого требования? Или этого просто нельзя сделать, используя представления с аналитическими функциями?

Я вынужден использовать представление, потому что аналитические функции нельзя использовать в конструкции WHERE. Но если бы и не это, я хотел бы, чтобы из приложения запрос шел к представлению, чтобы оно "видело" только часть столбцов таблицы.

Я знаю другие способы решения проблемы, но они не настолько просты, как использование аналитических функций (если только мне удастся их применить). :)

Ответ Тома Кайта

В таком случае, ваши требования нельзя удовлетворить с помощью представления.

Условия всегда будут применяться ПОСЛЕ вычисления аналитических функций в представлении, они должны проверяться именно так. Потому, что:

 
select analytic 
 from t 
 where условие 

СИЛЬНО и ВСЕГДА отличается от

 
select *  
from ( select analytic 
         from t ) 
 where условие 

Проблема, по сути, в том, что из одного нельзя получить другое.

Высказанная вами причина использования представления не связана с требованиями. МОЖНО использовать результат аналитической функции в условии (вы ИСПОЛЬЗУЕТЕ — rnum = 1)!!! Это просто надо делать ПОСЛЕ, а не ПО ХОДУ.

Это аналогично функциям агрегирования. Нельзя сказать: where count(*) = 1, надо говорить HAVING count(*) = 1. Условие проверяется ПОСЛЕ построения результирующего множества. С аналитическими функциями — то же самое.

Я уверен, что вы можете использовать аналитические функции (я знаю это).

Я также знаю, что вам не удастся использовать представление при указанном списке "обязательных требований".

Как заставить работать no_push_pred?

У меня есть запрос:

 
select ename, dname 
from (select * from emp where deptno = 10) a, 
     (select * from dept where deptno = 10) b 
where a.empno = 7934; 
 
Execution Plan 
---------------------------------------------------------- 
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=21) 
   1    0   NESTED LOOPS (Cost=3 Card=1 Bytes=21) 
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (Cost=1 Card=1 Bytes=10) 
   3    2       INDEX (UNIQUE SCAN) OF 'EMP_PK' (UNIQUE) 
   4    1     TABLE ACCESS (FULL) OF 'DEPT' (Cost=2 Card=1 Bytes=11) 

Я думаю, что происходит проталкивание условия (where empno... ) в представление, поэтому я изменил запрос следующим образом:

 
select /*+ NO_PUSH_PRED(a) */ ename, dname 
from (select * from emp where deptno = 10) a, 
     (select * from dept where deptno = 10) b 
where a.empno = 7934; 

но получил тот же план выполнения, тогда как ожидал два полных просмотра таблиц emp и dept...

Как работает эта подсказка?

Ответ Тома Кайта

Она работает в случае соединения. А это не соединение. Вы можете использовать no_merge для получения альтернативного (но безумного) плана — но, по сути, CBO должен иметь возможность перемещать это условие.

Вы можете получить желаемый план, использовав фокус с ROWNUM:

 
scott@ORA920> explain plan for 
  2  select ename, dname 
  3  from (select * from emp where deptno = 10 and rownum > 0 ) a, 
  4       (select * from dept where deptno = 10) b 
  5           where a.empno = 7934 
  6  / 
 
Explained. 
 
scott@ORA920> select * from table(dbms_xplan.display); 
 
PLAN_TABLE_OUTPUT 
--------------------------------------------------------------------------------- 
 
--------------------------------------------------------------------- 
| Id  | Operation             |  Name       | Rows  | Bytes | Cost  | 
--------------------------------------------------------------------- 
|   0 | SELECT STATEMENT      |             |       |       |       | 
|   1 |  NESTED LOOPS         |             |       |       |       | 
|*  2 |   TABLE ACCESS FULL   | DEPT        |       |       |       | 
|*  3 |   VIEW                |             |       |       |       | 
|   4 |    COUNT              |             |       |       |       | 
|*  5 |     FILTER            |             |       |       |       | 
|*  6 |      TABLE ACCESS FULL| EMP         |       |       |       | 
--------------------------------------------------------------------- 
 
Predicate Information (identified by operation id): 
--------------------------------------------------- 
 
   2 - filter("DEPT"."DEPTNO"=10) 
   3 - filter("A"."EMPNO"=7934) 
   5 - filter(ROWNUM>0) 
   6 - filter("EMP"."DEPTNO"=10) 
 
Note: rule based optimization 
 
22 rows selected. 

но при этом уместно спросить: "Зачем"?

Как заставить работать NO_PUSH_PRED?

У меня есть таблица с миллионами "хороших" строк и несколькими "мусорными". Я пытаюсь отфильтровать плохие записи по представлению даты в VARCHAR2 так, чтобы можно было преобразовать в дату только хорошие записи. Я просто использовал:

 
SELECT /*+ NO_PUSH_PRED (a) */ 
       some_columns  
FROM  ( 
       SELECT some_columns 
       FROM   some_table 
       WHERE  SUBSTR(char_date_column,1,1) BETWEEN '0' AND '9' 
      ) a 
,     some_other_table b 
WHERE some_join_condition 
AND   TO_DATE(a.char_date_column, 'YYYY-MM-DD') < TRUNC(SYSDATE, 'YEAR') 

Однако условие проталкивается во вложенное представление "a", поскольку я получаю ошибки в функции TO_DATE для нескольких плохих записей. Если я выношу SUBSTR вне вложенного представления и использую ORDERED_PREDICATES, то все получается. Что посоветуете?

Ответ Тома Кайта

Испольуйте CASE:

 
where some_join_condition 
  and case when substr( char_date_column,1,1) between '0' and '9' 
           then to_date( .... ) 
           else null 
       end < trunc( .... );  

Комметарий к исходному вопросу

Я понимаю, что вы ответили на исходный вопрос более двух лет назад, но мне интересно, помните ли вы, почему условие "group_id = 'TRACE'" (при указании которого индекс использовался) проталкивалось во вложенное представление. Связано ли это с тем, что поле group_id участвовало в аналитической функции, входящей в представление? Не будут ли результаты другими, если вложенное представление сначала строится, а потом к нему применяется условие "group_id = 'TRACE'"? Я собираюсь это проверить, но, к сожалению, сейчас доступа к базе данных у меня нет.

Ответ Тома Кайта

Да, поскольку мы указали столбец group_id в конструкции PARTIONED, условия по group_id можно "безопасно" проталкивать в запрос.

PUSH_PRED и OUTER JOIN

Может ли условие быть протолкнуто в подзапрос, соединяемый с помощью LEFT OUTER JOIN? Например:

 
SELECT /*+ PUSH_PRED */ 
FROM SMALL 
LEFT JOIN BIG ON SMALL.ID = BIG.ID 
WHERE SMALL.ID = 9 

Будет ли условие "ID = 9" протолкнуто в подзапрос?

Ответ Тома Кайта

По возможности, да.

... (Много не особо интересных вопросов пропущено - прим. В.К.)

Фокус с rownum>=0

У меня есть запрос, имеющий удовлетворительную производительность.

Я добавляю к нему условие:

 
and col1 not like '%something%' 

и план запроса полностью меняется, со снижением производительности!

Если я добавлю 'rownum>=0' к существующему запросу, а перед ним еще и мое новое условие, все работает прекрасно. rownum приводит к выполнению внутреннего запроса (с удовлетворительной производительностью) и фильтр применяется только к полученным в результате строкам.

Вопрос: разумно ли добавлять это условие 'rownum>=0' в само представление? Если представление соединяется с другой таблицей, я думаю, это не позволит CBO сгенерировать оптимальный план, поскольку потребует материализовать (построить) представление?

Нет ли других способов добиться того же, что и фокус с 'rownum>=0'?

Ответ Тома Кайта

Добавление такого условия предотвращает слияние представлений и проталкивание услвоий.

Есть подсказки, которые тоже могут предотвратить слияние.

GROUP BY

Как насчет представления/запроса с конструкцией group by?

Когда я выполняю:

 
select * from ( 
select col1,col2,sum(col3) group by col1,col2 having count(*)>5 
) where col1='foo' 

оказывается, что условие col1='foo' проталкивается во внутренний запрос.

Не должна ли конструкция 'having' или даже просто group by предотвращать проталкивание условия?

Ответ Тома Кайта

Нет, не в этом случае, поскольку вы группируете по столбцу col1? - упоминание столбца col1 в конструкции where "безопасно".

Безопасно?

Не уверен, что понял. Так будет условие по столбцу col1 протолкнуто в представление или нет?

Ответ Тома Кайта

Оно может и будет протолкнуто (вы же сами уже это написали?)

А я написал, что это "безопасно", поскольку по этому столбцу выполняется группировка. Происходи полное "слияние" – шага построения представления нет вообще:

 
ops$tkyte@ORA10GR1> create table t ( c1 int, c2 int, c3 int ); 
 
Table created. 
 
ops$tkyte@ORA10GR1> delete from plan_table; 
 
3 rows deleted. 
 
ops$tkyte@ORA10GR1> explain plan for 
  2  select * 
  3   from ( 
  4  select c1, c2, sum(c3) from t 
  5    group by c1, c2 
  6  ) 
  7  where c1 = 5; 
  
Explained. 
 
ops$tkyte@ORA10GR1> select * from table(dbms_xplan.display); 
 
PLAN_TABLE_OUTPUT 
------------------------------------------------------------------------------- 
Plan hash value: 1028120241 
  
--------------------------------------------------------------------------- 
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     | 
--------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT   |      |     1 |    39 |     3  (34)| 00:00:01 | 
|   1 |  SORT GROUP BY     |      |     1 |    39 |     3  (34)| 00:00:01 | 
|*  2 |   TABLE ACCESS FULL| T    |     1 |    39 |     2   (0)| 00:00:01 | 
--------------------------------------------------------------------------- 
 
Predicate Information (identified by operation id): 
--------------------------------------------------- 
 
   2 - filter("C1"=5) 
 
Note 
----- 
   - dynamic sampling used for this statement 
 
18 rows selected. 

Статья в Oracle Magazine

В последнем выпуске Oracle Magazine вы объясняли слияние представлений (view merging) и проталкивание условий (predicate pushing). У меня, однако, есть вопрос по слиянию представлений. В одном из ваших примеров (по-моему, в третьем) используется следующее определение представления:

 
select * from whatever 
order by whatever 

Вы написали, что из-за конструкции order by слияние этого представления невозможно. Как так? В чем отличие

 
select * from view where col = 'X' 

от

 
select *  
from (select * from whatever where col = 'X' order by whatever  ) 

И еще. Что вы имеет ввиду по "СЕМАНТИЧЕСКИМИ" изменениями при указании rownum (для проталкивания условий). Что такое семантика? Результирующее множество?

Ответ Тома Кайта

Мы не можем преобразовывать:

 
select *  
  from t1, (select * from t2 order by x) t2 
 where t1.y = t2.y 
   and t2.z = 5; 

в:

 
select *  
  from t1, t2 
 where t1.y = t2.y 
   and t2.z = 5  
 order by t2.x; 

но мы можем протолкнуть условие во вложенное представление:

 
select *  
  from t1, (select * from t2 where z = 5 order by x) t2 
 where t1.y = t2.y; 

Вот что я имел ввиду (учитывайте сложный общий случай, много таблиц, а не одна-две).

Семантика - это "смысл", "ответ". Изменяется ответ на вопрос... А это означает, что "так делать неправильно".

Запросы не будут семантически эквивалентны.

Почему нельзя выполнить слияние?

Я не понимаю, почему нельзя выполнить слияние, преобразовав

 
select *  
  from t1, (select * from t2 order by x) t2 
 where t1.y = t2.y 
   and t2.z = 5; 
в
 
select *  
  from t1, t2 
 where t1.y = t2.y 
   and t2.z = 5  
 order by t2.x; 

Пусть у нас есть две следующих таблицы:

 
t2 
x  y  z 
-- -- -- 
3  2  5 
1  2  5 
2  3  4 
4  1  8 
 
t1 
y  w 
-- -- 
2  5 

Из-за чего семантическая ошибка?

Ответ Тома Кайта

Из-за конструкции order byorder by "по определению" предотвращает слияние.

Оригинал обсуждения этого вопроса можно найти здесь.


Copyright © 2005 Oracle Corporation


Прежние выпуски

Все вышедшие выпуски рассылки можно найти на сайте рассылки. Там же реализована возможность поиска материалов по ключевым словам (с помощью Google...)

В следующих выпусках

Предполагаю пока опубликовать в нескольких выпусках свои переводы глав 1, 2 и 19 книги Кайта "Expert one-on-one Oracle". Затем выход рассылки станет еще более нерегулярным :). Подошло время оглянуться на сделанное, как-то его проклассифицировать, отредактировать, снабдить единым глоссарием, а затем решить, что делать с этим проектом дальше.

С наилучшими пожеланиями,

  В.К.


http://subscribe.ru/
http://subscribe.ru/feedback/
Подписан адрес:
Код этой рассылки: comp.soft.db.openoracle
Отписаться

В избранное