【问题标题】:Check if parameter is NULL within WHERE clause检查 WHERE 子句中的参数是否为 NULL
【发布时间】:2011-11-22 22:33:20
【问题描述】:

我遇到了一个需要永远执行的存储过程的问题。它非常大,我可以理解我需要一些时间,但这个持续了将近 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 只是一个~临时表,其中包含与参数 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%') );

执行计划

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)进行全表扫描。关于如何避免这种情况的任何想法?

【问题讨论】:

  • 你能把这两种情况下的完整SQL语句和查询计划贴出来吗? OrderDelivery 是全局临时表吗?还是数据是瞬态的永久表?这个谓词的选择性如何?
  • OrderDelivery 是游标内的临时表。我宁愿不包括整个过程,但我已经用一个例子更新了我的问题。谢谢

标签: oracle optimization stored-procedures parameters


【解决方案1】:

您遇到这样的问题是因为如果您在查询中包含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%');

【讨论】:

    【解决方案2】:

    不要在 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;
    

    我在使用OR 调整 SQL 语句方面也取得了一些成功,方法是使用 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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-07
      • 1970-01-01
      • 1970-01-01
      • 2016-01-13
      • 1970-01-01
      • 1970-01-01
      • 2010-09-14
      • 1970-01-01
      相关资源
      最近更新 更多