powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Странности CASE при использовании подзапроса с функциями
10 сообщений из 10, страница 1 из 1
Странности CASE при использовании подзапроса с функциями
    #39281538
NLK
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
NLK
Гость
Была задача сгенерировать случайные пары чисел (s, e) такие, что s <= e . Для этого решил выбирать пары значений dbms_random.value в подзапросе, и при необходимости переставлять значения в паре, чтобы соблюсти условие s <= e . Перестановку можно написать с помощью least и greatest , но можно и вручную с помощью case . Однако выясняется, что вариант с case совсем не прост, я не могу понять логику его работы.
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
with
  rnd as
   (select /*+ materialize */
           trunc(dbms_random.value(0, 11)) as s,
           trunc(dbms_random.value(0, 11)) as e
      from dual
--1    union all
--2    select 1 as s, 2 as e from dual
   )
--3  rnd as (select 0 as s, 1 as e from dual)
select case when s > e then e else s end as "case_start",
       case when s > e then s else e end as "case_end",
       least(s, e) as "least_start",
       greatest(s, e) as "greatest_end",
       case when s > e then 'fired' else 'no need' end as "swap",
       'derived' as "source"
  from rnd
union all
select r.s, r.e, r.s, r.e, '', 'original' from rnd r;


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

Код: plaintext
1.
2.
 case_start  case_end  least_start  greatest_end  swap     source 
    7           1         1            7        no need   derived
    1           7         1            7                  original

Обратите внимание, перестановка не требовалась, она не была заложена в логике case -операторов, но она произошла.
Почему?
Почему least и greatest сработали правильно, а case - нет? Последние делают перестановку независимо от исходных данных. Причём, если заменить именованный запрос rnd на тот, что закомментирован в строке №3, то всё заработает:

Код: plaintext
1.
2.
 case_start  case_end  least_start  greatest_end  swap     source 
    0           1         0            1        no need   derived
    0           1         0            1                  original

Если не заменять выборку, а добавить к случайным числам фиксированные, раскомментировав строчки 1 и 2,
то всё будет работать правильно:

Код: plaintext
1.
2.
3.
4.
 case_start  case_end  least_start  greatest_end  swap     source 
    2           5         2            5        no need   derived
    1           2         1            2        no need   derived
    2           5         2            5                  original
    1           2         1            2                  original

Также всё будет работать, если в первый и второй case вставить дополнительное условие, например
Код: plsql
1.
2.
3.
case when s > e then e else s end as "case: start"
=>
case when s > e then e when 1 = 2 then null else s end as "case: start"


или если в case произвести с возвращаемыми s или e математические действия, не меняющие их значений:
Код: plsql
1.
2.
3.
4.
case when s > e then e else s end as "case: start"
=>
case when s > e then e*1 else s end as "case: start"
case when s > e then e else s*1 end as "case: start"


Я подменял обращения к dbms_random на свою функцию, генерирующую последовательные номера,
и проверял, что она вызывается лишь дважды.

План выглядит так:

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
 | Id  | Operation                  | Name                        | Rows  | Bytes | Cost (%CPU)| Time     | 
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |                             |     2 |    52 |     4  (50)| 00:00:01 |
|   1 |  TEMP TABLE TRANSFORMATION |                             |       |       |            |          |
|   2 |   LOAD AS SELECT           | SYS_TEMP_0FD9D6670_EF0739EE |       |       |            |          |
|   3 |    FAST DUAL               |                             |     1 |       |     2   (0)| 00:00:01 |
|   4 |   UNION-ALL                |                             |       |       |            |          |
|   5 |    VIEW                    |                             |     1 |    26 |     2   (0)| 00:00:01 |
|   6 |     TABLE ACCESS FULL      | SYS_TEMP_0FD9D6670_EF0739EE |     1 |    26 |     2   (0)| 00:00:01 |
|   7 |    VIEW                    |                             |     1 |    26 |     2   (0)| 00:00:01 |
|   8 |     TABLE ACCESS FULL      | SYS_TEMP_0FD9D6670_EF0739EE |     1 |    26 |     2   (0)| 00:00:01 |

Подскажите, пожалуйста, в чём подвох?
...
Рейтинг: 0 / 0
Странности CASE при использовании подзапроса с функциями
    #39281545
Фотография -2-
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
NLK,

План с проекцией. И версию.
...
Рейтинг: 0 / 0
Странности CASE при использовании подзапроса с функциями
    #39281549
NLK
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
NLK
Гость
-2-,

Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
| Id  | Operation                  | Name                        | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |                             |     2 |    52 |     4  (50)| 00:00:01 |
|   1 |  TEMP TABLE TRANSFORMATION |                             |       |       |            |          |
|   2 |   LOAD AS SELECT           | SYS_TEMP_0FD9D6672_EF0739EE |       |       |            |          |
|   3 |    FAST DUAL               |                             |     1 |       |     2   (0)| 00:00:01 |
|   4 |   UNION-ALL                |                             |       |       |            |          |
|   5 |    VIEW                    |                             |     1 |    26 |     2   (0)| 00:00:01 |
|   6 |     TABLE ACCESS FULL      | SYS_TEMP_0FD9D6672_EF0739EE |     1 |    26 |     2   (0)| 00:00:01 |
|   7 |    VIEW                    |                             |     1 |    26 |     2   (0)| 00:00:01 |
|   8 |     TABLE ACCESS FULL      | SYS_TEMP_0FD9D6672_EF0739EE |     1 |    26 |     2   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------
 
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
 
   1 - SET$1       
   2 - SEL$2       
   3 - SEL$2        / DUAL@SEL$2
   5 - SEL$EC770CBB / RND@SEL$1
   6 - SEL$EC770CBB / T1@SEL$EC770CBB
   7 - SEL$EC770CBA / R@SEL$3
   8 - SEL$EC770CBA / T1@SEL$EC770CBA
 
Column Projection Information (identified by operation id):
-----------------------------------------------------------
	 
   1 - STRDEF[22], STRDEF[22], STRDEF[22], STRDEF[22], STRDEF[7], STRDEF[8]
   2 - SYSDEF[4], SYSDEF[0], SYSDEF[1], SYSDEF[96], SYSDEF[0]
   4 - STRDEF[22], STRDEF[22], STRDEF[22], STRDEF[22], STRDEF[7], STRDEF[8]
   5 - "S"[NUMBER,22], "E"[NUMBER,22]
   6 - "C0"[NUMBER,22], "C1"[NUMBER,22]
   7 - "R"."S"[NUMBER,22], "R"."E"[NUMBER,22]
   8 - "C0"[NUMBER,22], "C1"[NUMBER,22]



Версия

Код: plaintext
1.
2.
3.
4.
Oracle Database 11g Release 11.2.0.3.0 - 64bit Production
PL/SQL Release 11.2.0.3.0 - Production
"CORE	11.2.0.3.0	Production"
TNS for Linux: Version 11.2.0.3.0 - Production
NLSRTL Version 11.2.0.3.0 - Production
...
Рейтинг: 0 / 0
Странности CASE при использовании подзапроса с функциями
    #39281550
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
NLK,

Лишнее из тест кейса лучше выкидывать и выполнять dbms_random.seed для воспроизводимости.

Но баг конечно эпичен.
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
SQL> exec dbms_random.seed(1);

PL/SQL procedure successfully completed.

SQL>
SQL> select case
  2           when s > e then
  3            e
  4           else
  5            s
  6         end as l,
  7         s,
  8         e
  9    from (select trunc(dbms_random.value(0, 11)) as s,
 10                 trunc(dbms_random.value(0, 11)) as e
 11            from dual
 12           where rownum >= 1);

         L          S          E
---------- ---------- ----------
         9          8          9

10053 final query
Код: plsql
1.
2.
3.
4.
5.
6.
7.
select "from$_subquery$_001"."E" "L",
       "from$_subquery$_001"."S" "S",
       "from$_subquery$_001"."E" "E"
  from (select trunc("SYS"."DBMS_RANDOM"."VALUE"(0, 11)) "S",
               trunc("SYS"."DBMS_RANDOM"."VALUE"(0, 11)) "E"
          from "SYS"."DUAL" "DUAL"
         where rownum >= 1) "from$_subquery$_001"
...
Рейтинг: 0 / 0
Странности CASE при использовании подзапроса с функциями
    #39281559
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Это клиника, конечно... (и дело не в dual)
workaround
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
exec dbms_random.seed(1);

PL/SQL procedure successfully completed.


with rnd as
 (select trunc(dbms_random.value(0, 11)) as s,
         trunc(dbms_random.value(0, 11)) as e
    from dual
  connect by rownum <= 10)
select case
         when s * s > e * e then
          e
         else
          s
       end as l,
       s,
       e
  from rnd;

         L          S          E
---------- ---------- ----------
         9          8          9
         7          9          7
         2          7          2
         4          6          4
         9          6          9
        10          4         10
         5         10          5
         0          7          0
         6          9          6
         2          6          2

10 rows selected.


exec dbms_random.seed(1);

PL/SQL procedure successfully completed.


with rnd as
 (select trunc(dbms_random.value(0, 11)) as s,
         trunc(dbms_random.value(0, 11)) as e
    from dual
  connect by rownum <= 10),
rnd0 as
 (select * from rnd)
select case
         when s * s > e * e then
          e
         else
          s
       end as l,
       s,
       e
  from rnd0;

         L          S          E
---------- ---------- ----------
         8          8          9
         7          9          7
         2          7          2
         4          6          4
         6          6          9
         4          4         10
         5         10          5
         0          7          0
         6          9          6
         2          6          2

10 rows selected.

...
Рейтинг: 0 / 0
Странности CASE при использовании подзапроса с функциями
    #39281566
booby
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
dbms_photoshop...
[/src]10053 final query
Код: plsql
1.
2.
3.
4.
5.
6.
7.
select "from$_subquery$_001"."E" "L",
       "from$_subquery$_001"."S" "S",
       "from$_subquery$_001"."E" "E"
  from (select trunc("SYS"."DBMS_RANDOM"."VALUE"(0, 11)) "S",
               trunc("SYS"."DBMS_RANDOM"."VALUE"(0, 11)) "E"
          from "SYS"."DUAL" "DUAL"
         where rownum >= 1) "from$_subquery$_001"



не знаю, чем это является, но выглядит как "оптимизация".
Типа, case оптимизирован целиком путем подсмотра исходных значений при построении плана запроса.

А decode при этом не ломают - не оптимизируют. (спасибо)

Код: plsql
1.
decode(sign(s - e),1, e, s) as "decode_start"


вполне работает.

На то, что оптимизация, намекает таблетка вида добавленной в case ветки
Код: plsql
1.
When 1= 2 Then Null



Здесь уже становится совершенно ясно, что case исключать целиком больше нельзя,
т.к. рано или поздно 1 станет равным 2, и тогда...
...
Рейтинг: 0 / 0
Странности CASE при использовании подзапроса с функциями
    #39282133
NLK
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
NLK
Гость
dbms_photoshop,
booby,

Да, видимо, здесь странно поработал оптимизатор.

Нашёл ещё на Ask Tom обсуждение поведения dbms_random в запросах. Пара цитат оттуда:

I've written thousands of times that you cannot rely on how many times or when or if SQL will call your function.

When calling functions from SQL, you had better not rely on how often the functions get called, in what order, or whatever. In short, assume nothing.

And people do need to watch out for constructs like:

Код: plsql
1.
select * from t where dbms_random.value(0,1) <= 0.10;



it won't return 10% of the table (sample does that). It might return no rows or every row

И в моём примере использование least вместо case помогает, если использовать хинт materialize , а вот с inline — нет. Например:
Код: plsql
1.
2.
3.
4.
5.
6.
with
  sq as (select /*+ inline */ pk_sq.next s, pk_sq.next e from dual)
select e, s, least(s, e) as l from sq

E  S  L
1  2  3


Здесь pk_sq.next — генератор последовательных чисел, начиная с 1.

В общем, использование недетерминированных функций с побочными эффектами в запросах в общем случае небезопасно. Сложно гарантировать, порядок и количество вызовов. Каждый раз подбирать workaround и проверять работоспособность в конкретных условиях.
...
Рейтинг: 0 / 0
Странности CASE при использовании подзапроса с функциями
    #39282174
Фотография orawish
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Как тут уже выяснили уже предыдушие ораторы :)
конечно, материализация результата рандома необходима, до начала прочей арифметики.
хинтом ли материализовать - это уже детали. главное, чтобы в последующие арифметики рандом не про-инлайнился

ну и, кстати, баян же
...
Рейтинг: 0 / 0
Странности CASE при использовании подзапроса с функциями
    #39282185
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
orawish,

Если читать не по диагонали, то можно заметить, что материализация здесь не помогла.
В изначальном примере автора материализация имеет место, просто case полностью пропадает в final query.

Ну а то, что автор написал в последнем сообщении - баян, да.
...
Рейтинг: 0 / 0
Странности CASE при использовании подзапроса с функциями
    #39282285
Фотография orawish
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
dbms_photoshoporawish,

Если читать не по диагонали, то можно заметить, что материализация здесь не помогла.
В изначальном примере автора материализация имеет место, просто case полностью пропадает в final query.

Ну а то, что автор написал в последнем сообщении - баян, да.
ну, ок
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
with
  rnd as
   (select /*+ materialize */
           trunc(dbms_random.value(0, 11)) as s,
           trunc(dbms_random.value(0, 11)) as e
      from dual
  order by 1
--1    union all
--2    select 1 as s, 2 as e from dual
   )
--3  rnd as (select 0 as s, 1 as e from dual)
select case when s > e then e else s end as "case_start",
       case when s > e then s else e end as "case_end",
       least(s, e) as "least_start",
       greatest(s, e) as "greatest_end",
       case when s > e then 'fired' else 'no need' end as "swap",
       'derived' as "source"
  from rnd
union all
select r.s, r.e, r.s, r.e, '', 'original' from rnd r;


из результатов коего вывод - привет хинту
(12.1)
...
Рейтинг: 0 / 0
10 сообщений из 10, страница 1 из 1
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Странности CASE при использовании подзапроса с функциями
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]