Как обрабатывать необязательные параметры в SQL-запросе?
скажем, у меня есть образец таблицы:
id_pk value
------------
1 a
2 b
3 c
и у меня есть образец блока PL / SQL, который имеет запрос, который в настоящее время выбирает одну строку в массив:
declare
type t_table is table of myTable%rowtype;
n_RequiredId myTable.id_pk%type := 1;
t_Output t_table := t_table();
begin
select m.id_pk, m.value
bulk collect into t_Output
from myTable m
where m.id_pk = n_RequiredId;
end;
что мне нужно сделать, это реализовать возможность выбора одной строки в массив, как показано в блоке выше, или выбрать все строки в массив, если n_RequiredID
, который на самом деле является параметром пользовательского ввода, имеет значение null
.
и, вопрос в том, что самое лучшее тренируетесь справляться с такой ситуацией?
Я могу подумать об изменении where
предложение моего запроса к чему-то вроде этого:
where m.id_pk = nvl(n_RequiredId, m.id_pk);
но я полагаю, что это замедлит запрос, если параметр не будет нулевым, и я помню, что Кайт сказал что-то очень плохое об этом подходе.
Я также могу подумать о реализации следующей логики PL / SQL:
if n_RequiredId is null then
select m.id_pk, m.value bulk collect into t_Output from myTable m;
else
select m.id_pk, m.value bulk collect
into t_Output
from myTable m
where m.id_pk = n_RequiredId;
end if;
но станет слишком сложным, если я столкнусь с более чем одним параметром этого добрый.
что бы вы посоветовали мне?
3 ответов
Да, используя любое из следующего:
WHERE m.id_pk = NVL(n_RequiredId, m.id_pk);
WHERE m.id_pk = COALESCE(n_RequiredId, m.id_pk);
WHERE (n_RequiredId IS NULL OR m.id_pk = n_RequiredId);
...are не sargable. Они будут работать, но выполнять худшие из доступных вариантов.
если у вас есть только один параметр, то if/ELSE и отдельные, адаптированные операторы являются лучшей альтернативой.
следующая опция после этого -динамический SQL. Но кодирование динамический SQL-это бесполезно, если вы переносить не sargable в первом примере сказуемое. Динамический SQL позволяет адаптировать запрос с учетом множества путей. Но он также рискует SQL-инъекцией, поэтому он должен выполняться за параметризованными запросами (предпочтительно в рамках хранимых процедур/функций в пакетах.
ответы OMG_Ponies и Роба Ван Вейка полностью верны, это просто дополнение.
есть хороший трюк, чтобы упростить использование переменных привязки и по-прежнему использовать динамический SQL. Если вы поместите все привязки в предложение with в начале, вы всегда можете привязать один и тот же набор переменных, независимо от того, собираетесь ли вы их использовать.
например, скажем, у вас есть три параметра, представляющие диапазон дат и идентификатор. Если вы хотите просто искать по ID, вы можно было бы объединить запрос следующим образом:
with parameters as (
select :start_date as start_date,
:end_date as end_date,
:search_id as search_id
from dual)
select *
from your_table
inner join parameters
on parameters.search_id = your_table.id;
С другой стороны, если вам нужно искать по идентификатору и диапазону дат, это может выглядеть так:
with parameters as (
select :start_date as start_date,
:end_date as end_date,
:search_id as search_id
from dual)
select *
from your_table
inner join parameters
on parameters.search_id = your_table.id
and your_table.create_date between (parameters.start_date
and parameters.end_date);
Это может показаться круглым способом обработки этого, но конечный результат заключается в том, что независимо от того, как вы усложнили свой динамический SQL, пока ему нужны только эти три параметра, вызов PL/SQL всегда что-то вроде:
execute immediate v_SQL using v_start_date, v_end_date, v_search_id;
по моему опыту, лучше сделать конструкцию SQL немного сложнее, чтобы гарантировать, что есть только одна строка, где она фактически выполняется.
подход NVL обычно будет работать нормально. Оптимизатор распознает этот шаблон и построит динамический план. План использует индекс для одного значения и полное сканирование таблицы для NULL.
пример таблицы и данных
drop table myTable;
create table myTable(
id_pk number,
value varchar2(100),
constraint myTable_pk primary key (id_pk)
);
insert into myTable select level, level from dual connect by level <= 100000;
commit;
выполнить с различными предикатами
--Execute predicates that return one row if the ID is set, or all rows if ID is null.
declare
type t_table is table of myTable%rowtype;
n_RequiredId myTable.id_pk%type := 1;
t_Output t_table := t_table();
begin
select /*+ SO_QUERY_1 */ m.id_pk, m.value
bulk collect into t_Output
from myTable m
where m.id_pk = nvl(n_RequiredId, m.id_pk);
select /*+ SO_QUERY_2 */ m.id_pk, m.value
bulk collect into t_Output
from myTable m
where m.id_pk = COALESCE(n_RequiredId, m.id_pk);
select /*+ SO_QUERY_3 */ m.id_pk, m.value
bulk collect into t_Output
from myTable m
where (n_RequiredId IS NULL OR m.id_pk = n_RequiredId);
end;
/
получить планы выполнения
select sql_id, child_number
from gv$sql
where lower(sql_text) like '%so_query_%'
and sql_text not like '%QUINE%'
and sql_text not like 'declare%';
select * from table(dbms_xplan.display_cursor(sql_id => '76ucq3bkgt0qa', cursor_child_no => 1, format => 'basic'));
select * from table(dbms_xplan.display_cursor(sql_id => '4vxf8yy5xd6qv', cursor_child_no => 1, format => 'basic'));
select * from table(dbms_xplan.display_cursor(sql_id => '457ypz0jpk3np', cursor_child_no => 1, format => 'basic'));
плохие планы для объединения и NULL Или
EXPLAINED SQL STATEMENT:
------------------------
SELECT /*+ SO_QUERY_2 */ M.ID_PK, M.VALUE FROM MYTABLE M WHERE M.ID_PK
= COALESCE(:B1 , M.ID_PK)
Plan hash value: 1229213413
-------------------------------------
| Id | Operation | Name |
-------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS FULL| MYTABLE |
-------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
SELECT /*+ SO_QUERY_3 */ M.ID_PK, M.VALUE FROM MYTABLE M WHERE (:B1 IS
NULL OR M.ID_PK = :B1 )
Plan hash value: 1229213413
-------------------------------------
| Id | Operation | Name |
-------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS FULL| MYTABLE |
-------------------------------------
хороший план для NVL
на FILTER
операции позволяют оптимизатору выбрать другой план во время выполнения, в зависимости от входных значений.
EXPLAINED SQL STATEMENT:
------------------------
SELECT /*+ SO_QUERY_1 */ M.ID_PK, M.VALUE FROM MYTABLE M WHERE M.ID_PK
= NVL(:B1 , M.ID_PK)
Plan hash value: 730481884
----------------------------------------------------
| Id | Operation | Name |
----------------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | CONCATENATION | |
| 2 | FILTER | |
| 3 | TABLE ACCESS FULL | MYTABLE |
| 4 | FILTER | |
| 5 | TABLE ACCESS BY INDEX ROWID| MYTABLE |
| 6 | INDEX UNIQUE SCAN | MYTABLE_PK |
----------------------------------------------------
предупреждения
FILTER
операции и этот NVL
трюк не хорошо документированы. Я не уверен, какая версия представила эти функции, но она работает с 11g. У меня были проблемы с получением FILTER
для правильной работы с некоторыми сложные запросы, но для простых запросов, подобных этим, он надежен.