检查WHERE子句中的参数是否为NULL

Ste*_*fan 5 oracle parameters optimization stored-procedures

我遇到了一个需要永久执行的存储过程.这是非常大的,我可以理解,我会花一些时间,但这一次持续将近20分钟.

经过一些调试和研究后,我注意到替换了这部分WHERE条款;

((p_DrumNo IS NULL) OR T_ORDER.ORDER_ID IN (SELECT ORDER_ID FROM ORDERDELIVERY))
Run Code Online (Sandbox Code Playgroud)

发了很大的变化.所以只要p_DrumNo为NULL,或者我修改上面的内容就不检查p_DrumNo是否为NULL,程序就可以正常工作;

(T_ORDER.ORDER_ID IN (SELECT ORDER_ID FROM ORDERDELIVERY))
Run Code Online (Sandbox Code Playgroud)

WHERE子句的目标是在p_DrumNo上传递结果集,如果它传入存储过程.WHERE然后该子句继续进一步的条件,但这个特定的条件停止查询.

ORDERDELIVERY只是一个包含与参数p_DrumNo相关的ORDER_ID的临时表.

这个简单的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%') );
Run Code Online (Sandbox Code Playgroud)

执行计划

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
Run Code Online (Sandbox Code Playgroud)

例B

SQL查询

SELECT * FROM T_ORDER WHERE
( ORDER_ID IN (SELECT ORDER_ID FROM T_ORDER WHERE ORDERNO LIKE '290427%') );
Run Code Online (Sandbox Code Playgroud)

执行计划

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
Run Code Online (Sandbox Code Playgroud)

大家可以看到第一个查询(例子A)进行全表扫描.关于如何避免这种情况的任何想法?

Wol*_*olf 5

不要在 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;
Run Code Online (Sandbox Code Playgroud)

OR通过将语句分解为两个相互排斥的语句,我在使用 UNION ALL调整 SQL 语句方面也取得了一些成功:

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;
Run Code Online (Sandbox Code Playgroud)