Группировка и проверка условия внутри нее до первого удовлетворяющего


На очередной коротенький quiz, натолкнул такой вопрос:

Здравствуйте!
Столкнулся с запросом вида
SELECT   ...
   MAX
   (CASE
      WHEN <большое условие>
      THEN 1
      ELSE 0
   END) AS FLAG
   ...
FROM  <большая таблица>
GROUP BY ...
В котором, как вы видите, надо просто определить, есть ли в рамках "окна группировки" хотя бы одна строка удовлетворяющая <большое условие>.
Можно ли сделать так, чтобы после нахождения первой такой строки никаких вычислений в рамках окна больше не производилось?

На самом деле можно несколькими способами, но условие должно быть действительно тяжелое и никак иначе не решаемое, чтобы получить какой-то профит.

Например, я попробую с помощью создания функции с result_cache, но вместо "relies on" + "какая-то таблица для хранения значения" буду использовать пакетную переменную(можно, конечно, и sys_context) и функцию DBMS_RESULT_CACHE.INVALIDATE.

Возьмем за основу запрос:

select
 a
,max(
      case when b=10 then 1 end
     )
from test_single
group by a;


И неожиданно захотим, чтобы условие проверялось ровно до момента первого срабатывания.
Создадим тестовую табличку:
create table test_single(a integer,b integer);
insert into test_single
select 1, level from dual connect by level<1e5;
commit;

Создадим пакет с переменной:
create or replace package single_exec is
 b integer:=0;
end single_exec;
/


Создадим функцию для получения этой переменной с result_cache:
create or replace function test(param integer) return integer result_cache is
begin
    dbms_output.put_line('TEST, param='||param||', value='||single_exec.b);
    return single_exec.b;
end;


Вывод "dbms_output.put_line(param);" нам нужен лишь для показа сколько раз функция выполнилась.

Теперь создадим функцию, которая будет устанавливать нужное значение в пакетную переменную и осуществлять сброс кэша для функции:
create or replace function single_set(param integer,val integer) return integer is
begin
 single_exec.b:=val;
 dbms_result_cache.invalidate(user, 'TEST');
 dbms_output.put_line('SINGLE_SET: a='||param||',b='||val);
 return 1;
end;


Ну выполним первый тест - запросим 15 записей и посмотрим, что вернуло и сколько раз выполнялись функции:
begin -- Сбросим значения
 DBMS_RESULT_CACHE.invalidate(user, 'TEST');
 single_exec.b:=0; 
end;
/
---- Тестовый запрос:
select
 a
,b
, decode(
         test(a)
         ,1,1
         ,case when b=10 then single_set(a,1) end
         )
 as test_val
from test_single
where b<15
order by a,b;


Получим:
         A          B   TEST_VAL
---------- ---------- ----------
         1          1
         1          2
         1          3
         1          4
         1          5
         1          6
         1          7
         1          8
         1          9
         1         10          1
         1         11          1
         1         12          1
         1         13          1
         1         14          1

14 rows selected.

TEST, param=1, value=0
SINGLE_SET: a=1,b=1
TEST, param=1, value=1
Elapsed: 00:00:00.02

Как видим, все прекрасно - функция Test сработала ровно 2 раза - инициализировала кэш с 0, и затем только когда получила значение равное 1, а условие проверялось только до того момента пока не изменилось значение test(a) и не сбросился кэш - то есть именно когда условие сработало!

Ну и теперь само решение:
begin -- не забываем сбросить значение вначале...
 DBMS_RESULT_CACHE.invalidate(user, 'TEST');
 single_exec.b:=0; 
end;
/
select
 a
,max(
     decode(
             test(a)
             ,1,1
             ,case when b=10 then single_set(a,1) end
             )
     )
from test_single
group by a;

Comments

Отправить комментарий