PL / SQL - необязательные условия в предложении where-без динамического sql?

у меня есть запрос где не все условия. Вот пример как это выглядит, когда используются все условия:

select num
from (select distinct q.num
       from cqqv q
       where q.bcode = '1234567' --this is variable
             and q.lb = 'AXCT' --this is variable
             and q.type = 'privt' --this is variable
             and q.edate > sysdate - 30 --this is variable
       order by dbms_random.value()) subq
where rownum <= 10; --this is variable

части, помеченные как --this is variable - это части, которые, ну, меняться! Если условие не указано, то значение по умолчанию отсутствует. Например, если входные данные указывают " * " для q.type (но оставляет все остальное таким же), тогда запрос должен соответствовать всему типу и выполняться как:

select num
from (select distinct q.num
       from cqqv q
       where q.bcode = '1234567' --this is variable
             and q.lb = 'AXCT' --this is variable
             --and q.type = 'privt' --this condition ignored because of "type=*" in input
             and q.edate > sysdate - 30 --this is variable
       order by dbms_random.value()) subq
where rownum <= 10; --this is variable

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

5 ответов


пока вы могли бы это сделать...

select num
from (select distinct q.num
       from cqqv q
       where 1=1
             and (:bcode is null or q.bcode = :bcode)
             and (:lb is null or q.lb = :lb)
             and (:type is null or q.type = :type)
             and (:edate is null or q.edate > :edate - 30)
       order by dbms_random.value()) subq
where rownum <= :numrows

... производительность с использованием динамического SQL обычно будет лучше, так как он будет генерировать более целенаправленный план запроса. В приведенном выше запросе Oracle не может сказать, использовать ли индекс на bcode или lb или type или edate, и, вероятно, будет выполнять полное сканирование таблицы каждый раз.

конечно, вы должны используйте переменные bind в динамическом запросе, а не объединяйте литеральные значения в строку, в противном случае производительность (и масштабируемость, и безопасность) будет очень плохо.

чтобы быть ясным, динамическая версия, которую я имею в виду, будет работать следующим образом:

declare
    rc sys_refcursor;
    q long;
begin
    q := 'select num
    from (select distinct q.num
           from cqqv q
           where 1=1';

    if p_bcode is not null then
        q := q || 'and q.bcode = :bcode';
    else
        q := q || 'and (1=1 or :bcode is null)';
    end if;

    if p_lb is not null then
        q := q || 'and q.lb = :lb';
    else
        q := q || 'and (1=1 or :lb is null)';
    end if;

    if p_type is not null then
        q := q || 'and q.type = :type';
    else
        q := q || 'and (1=1 or :type is null)';
    end if;

    if p_edate is not null then
        q := q || 'and q.edate = :edate';
    else
        q := q || 'and (1=1 or :edate is null)';
    end if;

    q := q || ' order by dbms_random.value()) subq
    where rownum <= :numrows';

    open rc for q using p_bcode, p_lb, p_type, p_edate, p_numrows;
    return rc;
end;

Это означает, что результат запроса будет быть "sargable" (новое слово для меня, я должен признаться!) так как результирующий запуск запроса будет (например):

select num
from (select distinct q.num
       from cqqv q
       where 1=1
             and q.bcode = :bcode
             and q.lb = :lb
             and (1=1 or :type is null)
             and (1=1 or :edate is null)
       order by dbms_random.value()) subq
where rownum <= :numrows

тем не менее, я согласен, что это может потребовать до 16 жестких разборов в этом примере. Предложения" and :bv is null " являются требуется при использовании собственного динамического SQL, но можно избежать с помощью DBMS_SQL.

Примечание: использование (1=1 or :bindvar is null) когда переменная bind имеет значение null, было предложено в комментарии Михал правды, так как это позволяет оптимизатору исключить предложение.


хотя я согласен с Тони, что производительность использования динамического SQL лучше, переменные контекста-лучший подход, чем использование переменных привязки.

используя IN_VARIABLE IS NULL OR table.fieldx = IN_VARIABLE не идеально подходит для обработки необязательных значений. Каждый раз при отправке запроса Oracle сначала проверяет в своем общем пуле, был ли оператор отправлен ранее. Если это так, план выполнения запроса извлекается и выполняется SQL. Если оператор не может быть найден в общем пуле, Oracle имеет чтобы пройти через процесс разбора оператора, разработки различных путей выполнения и придумать оптимальный план доступа (он же "лучший путь"), прежде чем он может быть выполнен. Этот процесс известен как "жесткий разбор", и может занять больше времени, чем сам запрос. Подробнее о жесткий / мягкий разбор в Oracle здесь и AskTom здесь.

короче - это:

and (:bcode is null or q.bcode = :bcode)

...выполнит то же самое, динамическое или иное. Нет преимущество использования переменных привязки в динамическом SQL для необязательных параметров. Установка все еще разрушает SARGability...

параметры контекста-это функция, которая была введена в Oracle 9i. Они привязаны к пакету и могут использоваться для установки значений атрибутов (только для пользователей с разрешением EXECUTE на пакет, и вам придется предоставить контекст CREATE схеме). Переменные контекста могут использоваться для настройки динамического SQL, поэтому он включает только то, что необходимо для запрос, основанный на критериях фильтра / поиска. Для сравнения, переменные привязки (также поддерживаемые в динамическом SQL) требуют указания значения, которое может привести к IN_VARIABLE IS NULL OR table.fieldx = IN_VARIABLE тесты в поисковом запросе. На практике для каждой процедуры или функции следует использовать отдельную контекстную переменную, чтобы исключить риск загрязнения ценности.

вот ваш запрос с использованием переменных контекста:

L_CURSOR SYS_REFCURSOR;
L_QUERY  VARCHAR2(5000) DEFAULT 'SELECT num
                                   FROM (SELECT DISTINCT q.num
                                           FROM CQQV q
                                          WHERE 1 = 1 ';
BEGIN

    IF IN_BCODE IS NOT NULL THEN
      DBMS_SESSION.SET_CONTEXT('THE_CTX',
                               'BCODE',
                               IN_BCODE);
      L_QUERY := L_QUERY || ' AND q.bcode = SYS_CONTEXT(''THE_CTX'', ''BCODE'') ';
    END IF;

    IF IN_LB IS NOT NULL THEN
      DBMS_SESSION.SET_CONTEXT('THE_CTX',
                               'LB',
                               IN_LB);
      L_QUERY := L_QUERY || ' AND q.lb = SYS_CONTEXT(''THE_CTX'', ''LB'') ';
    END IF;

    IF IN_TYPE IS NOT NULL THEN
      DBMS_SESSION.SET_CONTEXT('THE_CTX',
                               'TYPE',
                               IN_TYPE);
      L_QUERY := L_QUERY || ' AND q.type = SYS_CONTEXT(''THE_CTX'', ''TYPE'') ';
    END IF;

    IF IN_EDATE IS NOT NULL THEN
      DBMS_SESSION.SET_CONTEXT('THE_CTX',
                               'EDATE',
                               IN_EDATE);
      L_QUERY := L_QUERY || ' AND q.edate = SYS_CONTEXT(''THE_CTX'', ''EDATE'') - 30 ';
    END IF;

    L_QUERY := L_QUERY || ' ORDER BY dbms_random.value()) subq
           WHERE rownum <= :numrows ';

    FOR I IN 0 .. (TRUNC(LENGTH(L_QUERY) / 255)) LOOP
      DBMS_OUTPUT.PUT_LINE(SUBSTR(L_QUERY, I * 255 + 1, 255));
    END LOOP;

    OPEN L_CURSOR FOR L_QUERY USING IN_ROWNUM;
    RETURN L_CURSOR;

END;

в Примере по-прежнему используется переменная bind для rownum, , потому что значение не необязательно.

DBMS_SESSION.SET_CONTEXT('THE_CTX', 'LB', IN_LB);

параметры SET_CONTEXT следующие:

  1. имя контекстной переменной. Нет никакого создания экземпляра
  2. переменная в переменной контекста. Контекстная переменная похожа на переменную сеанса, предполагая знакомство с веб-приложениями и объектами сеанса.
  3. значение переменной, определенной в параметре #2.

Bind vs Context

привязка переменных означает, что Oracle ожидает, что ссылка на переменную будет заполнена-в противном случае это ошибка ORA. Например:

... L_QUERY USING IN_EXAMPLE_VALUE

...ожидает, что будет заполнена одна ссылка переменной привязки. Если IN_EXAMPLE_VALUE null, нет и на :variable в запросе. Т. е.: AND :variable IS NULL

использование контекстной переменной означает, что не нужно включать постороннюю/избыточную логику, проверяя, значение null.

важно: переменные привязки обрабатываются в порядке возникновения (известном как порядковый номер),не по имени. Вы заметите, что нет объявления типа данных в USING предложения. Ординалы не идеальны - если вы измените их в запросе без обновления USING предложение, оно нарушит запрос, пока он не будет исправлен.


решение, на котором я остановился, генерирует динамический SQL-запрос, который может выглядеть так:

select num
from (select distinct q.NUM
       from cqqv q 
       where  (q.bcode = :bcode) 
                  and  (1=1 or :lb is null) 
                  and  (1=1 or :type is null) 
                  and  (q.edate> :edate) 
                order by dbms_random.value()) subq 
where rownum <= :numrows

(в этом примере условия bcode и edate не были необязательными, но lb и тип были)

Я думаю, что это (или очень похоже на) то, что предлагал Михал правда, и наш DBA здесь предпочитает это решение над решением переменной контекста. Спасибо за все, что помогли и предложили советы!

ссылка Наш DBA нашел, какие детали это решение здесь:

спросите Тома: О популярности и естественном отборе


Я бы просто сделать это

select num
from (select distinct q.num
       from cqqv q
       where q.bcode = '1234567' --this is variable
             and q.lb = 'AXCT' --this is variable
             and q.type = nvl(<variable-type>, q.type)  --this condition ignored because of "type=*" in input
             and q.edate > sysdate - 30 --this is variable
       order by dbms_random.value()) subq
where rownum <= 10; --this is variable

нужно только гарантировать, что переменный тип равен null, когда q.Фильтрация типов игнорируется.


где (columnA = passedValue или passedValue = -1)

когда переданное значение в sql равно -1, columnA может быть чем угодно..