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

发布于 2024-12-06 03:41:16 字数 1923 浏览 0 评论 0原文

我在执行一个存储过程时遇到了麻烦,该过程需要永远执行。它相当大,我可以理解我需要一些时间,但这个持续了将近 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)进行了全表扫描。关于如何避免这种情况的任何想法?

I´m having trouble with a Stored Procedure that takes about forever to execute. It is quite large and I can understand that I´ll take some time but this one continues for almost 20 minutes.

After some debugging and researching I noticed that replacing this part of the WHERE clause;

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

made a HUGE difference. So the Procedure works just fine as long as p_DrumNo is NULL or I modify the above to not check if p_DrumNo is NULL;

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

The goal with this WHERE clause is to filter the result set on p_DrumNo if it´s passed in to the Stored Procedure. The WHERE clause then continues with further conditions but this specific one halts the query.

ORDERDELIVERY is just a ~temporary table containing ORDER_IDs related to the parameter p_DrumNo.

How can this simple IS NULL-check cause such big impact? It´s probably related to the use of OR together with the subquery but I don´t understand why as the subquery itself works just fine.

Thanks in advance!

UPDATE [2011-09-23 10:13]

I´ve broken down the problem into this small query that show the same behaviour;

Example A

SQL query

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

Execution plan

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

Example B

SQL query

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

Execution plan

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

As you all can see the first query (example A) makes a full table scan. Any ideas on how I can avoid this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

睫毛上残留的泪 2024-12-13 03:41:16

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

Instead of evaluating your procedure's parameter state in the SQL statement itself, move that evaulation to the containing PL/SQL block so it's executed only once before the ideal SQL statement is submitted. For example:

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;

I've also had some success in tuning SQL statements with an OR by breaking the statement into two mutually exclusive statements with a 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;
梦亿 2024-12-13 03:41:16

您遇到了这样的问题,因为如果您在查询中包含 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 似乎是一个变量并且它往往为 null 或在特定时刻不为空。

但您也可以尝试在存储的内部使用 动态 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%');

You came across such an issue due to the fact your index doesn't work if you include OR to your query. To get the same info I'd rather do this to make the index work (basing on updated query):

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%'));

It will return the same result since 290427 seem to be a variable and it tends to be null or not null at the particular moment.

But also you can try using dynamic sql inside you stored procedure for such purposes:

%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%

And I wanted to say that I don't see sence of that IN, it'd be quite the same:

SELECT * FROM T_ORDER WHERE ('290427' IS NULL OR ORDERNO LIKE '290427%');
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文