Как обрабатывать необязательные параметры в 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 для правильной работы с некоторыми сложные запросы, но для простых запросов, подобных этим, он надежен.