Проверьте, является ли параметр NULL в предложении WHERE - PullRequest
5 голосов
/ 22 сентября 2011

У меня проблемы с хранимой процедурой, выполнение которой занимает вечность. Он довольно большой, и я могу понять, что мне потребуется некоторое время, но это продолжается почти 20 минут.

После некоторой отладки и исследования я заметил, что замена этой части предложения WHERE;

((p_DrumNo IS NULL) OR T_ORDER.ORDER_ID IN (SELECT ORDER_ID FROM ORDERDELIVERY))

сделал огромную разницу. Таким образом, процедура работает просто отлично, если p_DrumNo равен NULL или я изменяю вышеописанное, чтобы не проверять, равно ли p_DrumNo NULL;

(T_ORDER.ORDER_ID IN (SELECT ORDER_ID FROM ORDERDELIVERY))

Цель этого условия WHERE состоит в том, чтобы отфильтровать набор результатов в p_DrumNo, если он передан в хранимую процедуру. Затем предложение WHERE продолжается с дополнительными условиями, но это конкретное условие останавливает запрос.

ORDERDELIVERY - это просто временная таблица, содержащая ORDER_ID, связанные с параметром p_DrumNo.

Как эта простая проверка IS NULL может оказать такое большое влияние? Вероятно, это связано с использованием OR вместе с подзапросом, но я не понимаю, почему сам подзапрос работает просто отлично.

Заранее спасибо!

ОБНОВЛЕНИЕ [2011-09-23 10:13]

Я разбил проблему на этот маленький запрос, который показывает то же поведение;

Пример A

SQL-запрос

SELECT * FROM T_ORDER WHERE
('290427' IS NULL OR ORDER_ID IN (SELECT ORDER_ID FROM T_ORDER WHERE ORDERNO LIKE '290427%') );

План выполнения

OPERATION   OBJECT_NAME     OPTIONS     COST
------------------------------------------------------------
SELECT STATEMENT                    97
FILTER
TABLE ACCESS    T_ORDER         FULL        95
TABLE ACCESS    T_ORDER         BY INDEX ROWID  2
INDEX       PK_ORDER        UNIQUE SCAN 1

Пример B

SQL-запрос

SELECT * FROM T_ORDER WHERE
( ORDER_ID IN (SELECT ORDER_ID FROM T_ORDER WHERE ORDERNO LIKE '290427%') );

План выполнения

OPERATION   OBJECT_NAME     OPTIONS     COST
------------------------------------------------------------
SELECT STATEMENT                    4
NESTED LOOPS                        4
TABLE ACCESS    T_ORDER         BY INDEX ROWID  3
INDEX       IX_T_ORDER_ORDERNO  RANGE SCAN   2  
TABLE ACCESS    T_ORDER         BY INDEX ROWID  1  
INDEX       PK_ORDER        UNIQUE SCAN 0

Как вы все видите, первый запрос (пример A) выполняет полное сканирование таблицы. Любые идеи о том, как я могу избежать этого?

Ответы [ 2 ]

4 голосов
/ 23 сентября 2011

Вместо оценки состояния параметра вашей процедуры в самом операторе SQL переместите это выражение в содержащий блок PL / SQL, чтобы оно выполнялось только один раз, прежде чем будет представлен идеальный оператор SQL.Например:

CREATE OR REPLACE PROCEDURE my_sp (p_DrumNo VARCHAR2)
IS
BEGIN
    IF p_DrumNo IS NULL THEN
        SELECT ...
        INTO ... -- Assumed
        FROM ...
        WHERE my_column = p_DrumNo;
    ELSE
        SELECT ...
        INTO ... -- Assumed
        FROM ...
        WHERE ORDER_ID IN (SELECT ORDER_ID FROM ORDERDELIVERY);
    END;
END;

Я также добился определенного успеха в настройке операторов SQL с OR, разбив оператор на два взаимоисключающих оператора с UNION ALL:

SELECT ...
FROM ...
WHERE p_DrumNo IS NULL
AND ORDER_ID IN (SELECT ORDER_ID FROM ORDERDELIVERY)
UNION ALL
SELECT ...
FROM ...
WHERE p_DrumNo IS NOT NULL
AND my_column = p_DrumNo;
3 голосов
/ 17 мая 2013

Вы столкнулись с такой проблемой из-за того, что ваш индекс не работает, если вы включили OR в свой запрос. Чтобы получить ту же информацию, я бы предпочел сделать это, чтобы индекс работал (на основе обновленного запроса):

SELECT * FROM T_ORDER WHERE '290427' IS NULL
UNION ALL
SELECT * FROM T_ORDER WHERE ORDER_ID IN (SELECT ORDER_ID FROM T_ORDER WHERE ORDERNO LIKE '290427%'));

Он вернет тот же результат, поскольку 290427 представляется переменной, и он имеет тенденцию быть нулевым или не нулевым в конкретный момент.

Но вы также можете попробовать использовать dynamic sql внутри хранимой процедуры для таких целей:

%begin_of_the_procedure%

query_ := 'SELECT * FROM T_ORDER WHERE 1=1';
if var_ is not null then
  query_ := query_||' AND ORDER_ID IN (SELECT ORDER_ID FROM T_ORDER WHERE ORDERNO LIKE '''||to_char(var_)||'%'')';
end if;
open cursor_ query_;

%fetching cursor loop%
%end_of_the_procedure%

И я хотел сказать, что не вижу смысла в этом IN, это было бы совершенно то же самое:

SELECT * FROM T_ORDER WHERE ('290427' IS NULL OR ORDERNO LIKE '290427%');
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...