Подтвердить что ты не робот

Оценка короткого замыкания CASE и COALESCE работает с последовательностями в PL/SQL, но не в SQL

Используется ли оценка короткого замыкания, описанная в документации для CASE и COALESCE() к последовательностям при использовании в SQL? Это, похоже, не происходит.

Документация Oracle на CASE гласит:

База данных Oracle использует оценку короткого замыкания. Для простого выражения CASE... Oracle никогда не оценивает сравнение_expr, если предыдущее compare_expr равно expr. Для найденного выражения CASE база данных... никогда не оценивает условие, если предыдущее условие было истинным.

Аналогично для COALESCE() документации указано, что:

База данных Oracle использует оценку короткого замыкания. База данных оценивает каждое значение expr и определяет, является ли она NULL, а не оценивает все значения expr перед определением, является ли любой из них NULL.

При вызове последовательности из SQL это, похоже, не так; поскольку вы можете видеть, что короткое замыкание не происходит, и последовательность увеличивается.

SQL> create sequence tmp_test_seq start with 1 increment by 1;
SQL> select tmp_test_seq.nextval from dual;

   NEXTVAL
----------
         1
SQL> select tmp_test_seq.currval from dual;

   CURRVAL
----------
         1
SQL> select coalesce(1, tmp_test_seq.nextval) from dual;

COALESCE(1,TMP_TEST_SEQ.NEXTVAL)
--------------------------------
                               1
SQL> select tmp_test_seq.currval from dual;

   CURRVAL
----------
         2
SQL> select case when 1 = 1 then 1 else tmp_test_seq.nextval end as s from dual;


         S
----------
         1
SQL> select tmp_test_seq.currval from dual;

   CURRVAL
----------
         3

SQL Fiddle.

Однако при вызове из PL/SQL последовательность не увеличивается:

SQL> create sequence tmp_test_seq start with 1 increment by 1;
SQL> declare
  2     i number;
  3  begin
  4     i := tmp_test_seq.nextval;
  5     dbms_output.put_line(tmp_test_seq.currval);
  6     i := coalesce(1, tmp_test_seq.nextval);
  7     dbms_output.put_line(i);
  8     dbms_output.put_line(tmp_test_seq.currval);
  9     i := case when 1 = 1 then 1 else tmp_test_seq.nextval end;
 10     dbms_output.put_line(i);
 11     dbms_output.put_line(tmp_test_seq.currval);
 12  end;
 13  /
1
1
1
1
1
SQL> select tmp_test_seq.nextval from dual;

   NEXTVAL
----------
         2

Вызов последовательности в SQL из PL/SQL те же результаты, что и в SQL:

SQL> create sequence tmp_test_seq start with 1 increment by 1;
SQL> declare
  2     i number;
  3  begin
  4     select tmp_test_seq.nextval into i from dual;
  5     dbms_output.put_line(tmp_test_seq.currval);
  6     select coalesce(1, tmp_test_seq.nextval) into i from dual;
  7     dbms_output.put_line(i);
  8     dbms_output.put_line(tmp_test_seq.currval);
  9     select case when 1 = 1 then 1 else tmp_test_seq.nextval end into i
 10       from dual;
 11     dbms_output.put_line(i);
 12     dbms_output.put_line(tmp_test_seq.currval);
 13  end;
 14  /
1
1
2
1
3

В документации, похоже, ничего нет; руководство администратора для управления последовательностями, ссылка на SQL-язык в последовательности psuedocolumns, ссылку на язык PL/SQL на CURRVAL и NEXTVAL или обзор концепций баз данных последовательностей.

Существует ли оценка короткого замыкания CASE и COALESCE() для последовательностей при использовании в SQL? Является ли это документированным?

Мы находимся на 11.2.0.3.5, если это интересно.

4b9b3361

Ответ 1

Объяснение того, почему оценка короткого замыкания не применяется к последовательностям, может быть следующей. Что такое последовательность? Отложив внутреннюю часть в сторону, это комбинация определения последовательности (запись в таблице данных словаря seq$) и некоторого внутреннего компонента SGA, это не функция и может быть рассмотрена, хотя документация не указывает его непосредственно (но план выполнения) в качестве источника строки. И каждый раз, когда последовательность ссылается непосредственно в списке выбора запроса, она должна оцениваться оптимизатором при поиске оптимального плана выполнения. Во время формирования оптимального плана выполнения последовательность увеличивается, если ссылается на nextval псевдоколономер:

SQL> create sequence seq1;
Sequence created

Вот наша последовательность:

SQL> select o.obj#
  2       , o.name
  3       , s.increment$
  4       , s.minvalue
  5       , s.maxvalue
  6       , s.cache
  7    from sys.seq$ s
  8    join sys.obj$ o
  9       on (o.obj# = s.obj#)
 10    where o.name = 'SEQ1'
 11  ;


      OBJ# NAME    INCREMENT$   MINVALUE   MAXVALUE      CACHE
---------- ------- ---------- ---------- ---------- ----------
     94442 SEQ1             1          1       1E28         20

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

SQL> ALTER SESSION SET EVENTS '10046 trace name context forever, level 4';
Session altered

SQL> select case
  2           when 1 = 1 then 1
  3           when 2 = 1 then seq1.nextval
  4         end as res
  5    from dual;

       RES
----------
         1

/* sequence got incremented by 1 */

SQL> select seq1.currval from dual;

   CURRVAL
----------
         3

Информация о файле трассировки:

STAT # 1016171528 id = 1 cnt = 1 pid = 0 pos = 1 obj = 94442 op = 'SEQUENCE SEQ1...
STAT # 1016171528 id = 2 cnt = 1 pid = 1 pos = 1 obj = 0 op = 'FAST DUAL...
CLOSE # 1016171528: c = 0, e = 12, dep = 0, type = 0, tim = 12896600071500/* закрыть курсор */

План выполнения покажет нам в основном то же самое:

SQL> explain plan for select case
  2                            when 1 = 1 then 1
  3                            else seq1.nextval
  4                          end
  5                      from dual
  6  /
Explained
Executed in 0 seconds

SQL> select * from table(dbms_xplan.display());
PLAN_TABLE_OUTPUT
---------------------------------------------------------------
Plan hash value: 51561390
-----------------------------------------------------------------
| Id  | Operation        | Name | Rows  | Cost (%CPU)| Time     |
-----------------------------------------------------------------
|   0 | SELECT STATEMENT |      |     1 |     2   (0)| 00:00:01 |
|   1 |  SEQUENCE        | SEQ1 |       |            |          |
|   2 |   FAST DUAL      |      |     1 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------
9 rows selected
Executed in 0.172 seconds

С точки зрения оценки, ссылка на последовательность непосредственно в запросе, примерно такая же, как и связанный подзапрос. Этот коррелированный подзапрос всегда будет оцениваться оптимизатором:

SQL> explain plan for select case
  2                            when 1 = 1 then 1
  3                            when 2 = 1 then (select 1
  4                                               from dual)
  5                          end as res
  6                      from dual;
Explained
Executed in 0 seconds

SQL> select * from table(dbms_xplan.display());
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------
Plan hash value: 1317351201
-----------------------------------------------------------------
| Id  | Operation        | Name | Rows  | Cost (%CPU)| Time     |
-----------------------------------------------------------------
|   0 | SELECT STATEMENT |      |     1 |     4   (0)| 00:00:01 |
|   1 |  FAST DUAL       |      |     1 |     2   (0)| 00:00:01 |
|   2 |  FAST DUAL       |      |     1 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------
9 rows selected
Executed in 0.063 seconds  

Мы видим, что таблица dual была включена в план выполнения дважды.Забастовкa >

Аналогия с подзапросом была сделана в спешке. Конечно, есть больше различий, чем сходство. Последовательности - абсолютно разные механизмы. Но последовательности рассматриваются оптимизатором как источник строки, и до тех пор, пока он не видит псевдокоманду nextval последовательности, непосредственно ссылающейся в списке select запроса верхнего уровня, он выиграл ' t оценить последовательность, иначе последовательность будет увеличиваться, независимо от того, используется ли логика оценки короткого замыкания или нет. Механизм PL/SQL, очевидно, (начиная с Oracle 11g r1) имеет другой способ доступа к значению последовательности. Следует отметить, что в предыдущих версиях РСУБД 11gR1 мы должны написать запрос для ссылки на последовательность в блоке PL/SQL, механизм PL/SQL отправленный непосредственно в механизм SQL.

Ответ на вопрос "почему последовательность получает приращение во время генерации плана выполнения с помощью оптимизатора", заключается во внутренней реализации последовательностей.

Ответ 2

Для PL/SQL Oracle гарантирует, что он будет использовать оценку короткого замыкания:

При оценке логического выражения PL/SQL использует короткое замыкание оценка. То есть PL/SQL перестает оценивать выражение, как только он может определить результат. Поэтому вы можете писать выражения, которые могут вызвать ошибки.

От: 2 Основы языка PL/SQL

Когда вы используете код nextval в SQL, у нас другая ситуация.

Прежде всего, мы должны иметь в виду, что currval и nextval являются псевдоколониями:

Псевдоколонка ведет себя как столбец таблицы, но на самом деле не хранится в таблице. Вы можете выбрать из псевдокоманд, но вы не можете вставлять, обновлять или удалять их значения. Псевдоколонка также аналогична к функции без аргументов (см. главу 5, "Функция". Однако функции без аргументов обычно возвращают одинаковое значение для каждой строки в результирующем наборе, тогда как псевдоколонны обычно возвращают другое значение для каждой строки.

От: 3 Pseudocolumns.

Теперь возникает вопрос: почему Oracle оценивает nextval? или Это поведение указано где-то?

В одном выражении SQL, содержащем ссылку на NEXTVAL, Oracle увеличивает последовательность один раз:

  • Для каждой строки, возвращаемой внешним блоком запроса инструкции SELECT. Такой блок запроса может отображаться в следующих местах:

    • Оператор SELECT верхнего уровня
    • Оператор INSERT... SELECT (одностолбцовый или многопользовательский). Для многопользовательской вставки ссылка на NEXTVAL должна появляются в предложении VALUES, и последовательность обновляется один раз для каждая строка, возвращаемая подзапросом, хотя NEXTVAL может быть ссылается на несколько ветвей многопозиционной вставки.
    • CREATE TABLE... инструкция SEL SELECT
    • CREATE MATERIALIZED VIEW... AS SELECT
  • Для каждой строки, обновленной в инструкции UPDATE

  • Для каждого оператора INSERT, содержащего предложение VALUES

  • Для каждой строки, объединенной оператором MERGE. Ссылка на NEXTVAL может отображаться в файле merge_insert_clause или merge_update_clause или и то и другое. Значение NEXTVALUE увеличивается для каждой обновленной строки и для каждая вставленная строка, даже если порядковый номер фактически не используется в операции обновления или вставки. Если NEXTVAL указан более одного раза в любом из этих мест, то последовательность увеличивается один раз для каждая строка и возвращает одинаковое значение для всех вхождений NEXTVAL для эту строку.

От: Псевдоположения последовательности

В вашем случае явно "1. Оператор SELECT верхнего уровня", но это не означает, что логика короткого замыкания не установлена, но всегда оценивается только nextval.

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

Подобный запрос не оценивает подзапрос:

select 6 c
  from dual
where  'a' = 'a' or 'a' = (select dummy from dual) 

Но если попытаться сделать что-то подобное с coalesce или case, мы увидим, что Oracle Optimizer решает выполнить подзапросы:

select 6 c
  from dual
where  'a' = coalesce('a', (select dummy from dual) )

Я создал аннотированные тесты в эту демонстрацию в SQLFiddle, чтобы показать это.

Похоже, что Oracle применяет логику короткого замыкания только при условии OR, но с coalesce и case она должна оценивать все ветки.

Я думаю, что ваши первые тесты в PL/SQL показывают, что coalsce и case используют логику короткого замыкания в PL/SQL, как утверждает Oracle. Второй тест, включая последовательность в операторах SQL, показывает, что в этом случае nextval оценивается в любом случае, даже если результат не используется, и Oracle также документирует это.

Совмещение двух вещей выглядит немного странно, потому что поведение coalesce и case тоже кажется мне непоследовательным, но мы также должны иметь в виду, что реализация этой логики зависит от реализации (здесь мой источник)