Здравствуйте!
Столкнулся с запросом вида
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
Все хорошо, если таблица test_single предварительно отсортирована по значению A, а еще лучше - значение 1-но!
Попробуй подать неотсортированнную таблицу с разными значениями столбца - результат получишь в корне неверный. Вероятно, надо хранить в пакете не 1-но значение для результата, а список значений для всех вариантов значения A. Будет ли это быстрее и менее затратно, при миллиардных вариациях - сомневаюсь.
Попробуй подать неотсортированнную таблицу с разными значениями столбца - результат получишь в корне неверный.
Все будет верно, просто пример упрощен, но это же пример - кому-то может понадобится ассоциативная коллекция, кому-то sys_context, а для кого и gtt...
Вероятно, надо хранить в пакете не 1-но значение для результата, а список значений для всех вариантов значения A
Видимо, я зря не пояснил: параметр у функции(param) как раз и должен определять значение для конкретного А и кешируются значения именно соответственно этим параметрам для конкретной группы.
Отправить комментарий