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

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

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 ]

12 голосов
/ 11 ноября 2009

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

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

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

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

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), когда переменная связывания равна нулю, было предложено в комментарии Михалом Правды, так как это позволяет оптимизатору исключить предложение.

5 голосов
/ 11 ноября 2009

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

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

Короче - это:

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

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

Параметры контекста - это функция, представленная в Oracle 9i . Они привязаны к пакету и могут использоваться для установки значений атрибутов (только для пользователей с разрешением EXECUTE на пакет, и вам придется предоставить CREATE CONTEXT для схемы). Переменные контекста могут использоваться для настройки динамического SQL, поэтому он включает в себя только то, что необходимо для запроса на основе критериев фильтра / поиска. Для сравнения, переменные Bind (также поддерживаемые в динамическом 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;

В примере все еще используется переменная связывания для 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 равно нулю, имеет , чтобы быть :variable в запросе. IE: AND :variable IS NULL

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

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

3 голосов
/ 13 ноября 2009

Я остановился на решении, которое генерирует динамический запрос 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 и type были)

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

Ссылка, найденная нашим администратором базы данных, с подробностями этого решения:

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

0 голосов
/ 23 марта 2016

где (столбец A = пройденное значение или пройденное значение = -1)

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

0 голосов
/ 16 марта 2011

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

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

Нужно только гарантировать, что тип переменной равен нулю, когда фильтрация q.TYPE должна игнорироваться.

...