Как обрабатывать необязательные параметры в запросе SQL? - PullRequest
7 голосов
/ 06 сентября 2010

Скажите, у меня есть образец таблицы:

 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 ]

13 голосов
/ 06 сентября 2010

Да, используя любое из следующего:

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);

... не могут быть sargable . Они будут работать, но выполнять худший из доступных вариантов.

Если у вас есть только один параметр, IF / ELSE и отдельные специализированные операторы - лучшая альтернатива.

Следующий параметр после этого - динамический SQL . Но кодирование динамического SQL бесполезно, если в первом примере вы переносите неискажаемые предикаты. Динамический SQL позволяет адаптировать запрос, приспосабливая многочисленные пути. Но это также сопряжено с риском внедрения SQL, поэтому его следует выполнять за параметризованными запросами (предпочтительно внутри хранимых процедур / функций в пакетах.

4 голосов
/ 07 сентября 2010

Ответы OMG_Ponies 'и Роба ван Вейка полностью правильные, это просто дополнение.

Есть хорошая хитрость, позволяющая упростить использование переменных связывания и при этом использовать динамический SQL.Если вы поместите все привязки в предложении with в начале, вы всегда можете связать один и тот же набор переменных, независимо от того, собираетесь ли вы их использовать.

Например, скажем, у вас есть три параметра, представляющий диапазон дат и идентификатор.Если вы хотите просто выполнить поиск по идентификатору, вы можете составить запрос следующим образом:

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

2 голосов
/ 03 февраля 2015

Подход 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'));

Плохие планы на COALESCE и НУЛЬ ИЛИ

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...