如何以不同的方式处理 PL/SQL 中的不同异常?
Posted
技术标签:
【中文标题】如何以不同的方式处理 PL/SQL 中的不同异常?【英文标题】:How to handle different exceptions in PL/SQL differently? 【发布时间】:2021-05-27 22:40:52 【问题描述】:我的存储过程正在循环执行不同的语句。我要处理以下情况:
-
当语句没有返回任何内容时 (
no_data_found
),我想安静地跳过循环的其余部分 (continue
)。
当语句导致任何类型的错误时,我想报告它,然后跳过循环的其余部分(continue
);
当语句找到行时,我想报告它。
代码如下:
...
LOOP
stmt := 'select * ......';
BEGIN
EXECUTE IMMEDIATE stmt;
EXCEPTION
WHEN NO_DATA_FOUND THEN NULL;
WHEN OTHERS THEN
dbms_out.put_line(stmt || ': ' || SQLCODE);
CONTINUE;
END;
dbms_out.put_line('Found! Use: ' || stmt);
END LOOP;
以上内容不会引发错误,但会为每个循环迭代打印Found
-line,包括for语句,不会产生任何结果...
为什么CONTINUE
-directive 会被忽略——我是否错误地期望在 any 异常情况下遵守该指令——无论是NO_DATA_FOUND
还是其他任何东西?
【问题讨论】:
如果您尝试从中获取数据,您只会知道查询是否返回 0 行。如果您知道查询结果集的结构,您可以使用execute immediate
和into
或bulk collect into
以及可选的limit
,具体取决于查询返回单行还是多行。如果语句是完全动态的,因此您事先不知道结果的结构,则需要在获取数据之前使用dbms_sql
来描述结果,这会变得方式变得更加复杂。
听起来您实际上只关心任何查询返回的行数,而不是实际结果。如果是这样,您应该能够将查询包装在 select count(*) from ( <<query>> )
中,然后将结果提取到单个 l_cnt
变量中。
Khm,所以仅仅做EXECUTE IMMEDIATE stmt
是不够的,它是否会产生任何行?我想,使用NO_DATA_FOUND
看起来会更好——并且对于该程序的未来读者来说更容易理解......好吧,让我试试count(*)
路线。顺便说一句,是count(*)
还是count(1)
——我都见过...
两者都行。我更喜欢count(*)
。根据版本的不同,Oracle 中可能会针对实现 count(*)
与 count(1)
进行轻微的微优化,但这基本上不会产生功能差异。
@MikhailT。 - 如果您只是做execute immediate stmt
(其中stmt
是一个查询)is not even executed,它只会被解析。正如贾斯汀所说,除非您计算它们,否则您必须获取所有行以查看有多少行;而且您仍然必须将该计数结果提取到某些东西中。如果您只关心零或大于零而不是实际的行数,您可以添加 rownum stopcheck,或者使用exists
。
【参考方案1】:
在您的异常块中,NO_DATA_FOUND
处理程序的操作为 NULL - 因此它执行 NULL 语句(即不执行任何操作)并退出 BEGIN-END 块,命中 dbms_out.put_line('Found! Use: ' || stmt);
语句。将执行CONTINUE;
的唯一处理程序是WHEN OTHERS
。
获得您描述的行为的一种方法是将SELECT COUNT(*)...
放入数字变量中,然后检查返回多少行:
DECLARE
csr SYS_REFCURSOR;
nCount NUMBER;
BEGIN
LOOP
stmt := 'SELECT COUNT(*) FROM (SELECT * from ... WHERE ...)';
OPEN csr FOR stmt;
FETCH csr INTO nCount;
CLOSE csr;
IF nCount > 0 THEN
dbms_out.put_line('Found! Use: ' || stmt);
ELSE
dbms_out.put_line(stmt || ': ' || SQLCODE);
END IF;
END LOOP;
END;
当然这不是真的有效,因为stmt
的值无法改变,但我怀疑你的“真实”代码会处理它。
【讨论】:
为什么它没有命中CONTINUE
语句,它在EXCEPTION
-block 中的WHEN
-s 之后?
在 PL/SQL 异常块中,每个 WHEN
块独立于其他块。他们不会从一个跌落到另一个。如果程序在 EXCEPTION 语句中进入 WHEN 陷阱并且没有 CONTINUE 或 RAISE,则在执行完 WHEN 中的最后一条语句之后,下一条要执行的语句将是块的 END
语句之后的任何语句,即使有其他 WHEN
在 EXCEPTION 语句中的陷阱。所以在您的代码中,在执行WHEN NO_DATA_FOUND THEN NULL;
之后,下一条要运行的语句是dbms_out.put_line('Found! Use: ' || stmt);
。这就是 PL/SQL 的工作原理。
所以,正如所写,我的CONTINUE
实际上永远无法访问,是吗?
如前所述,CONTINUE
是WHEN OTHERS...
处理程序的一部分。缩进有点误导。
啊!那是因为我认为——来自 C 或 Java 等语言——只有一个语句可以跟随THEN
(除非它是BEGIN .... END
)。这就是为什么我认为,CONTINUE
将在处理任何一个 WHEN
s 后被击中......谢谢!以上是关于如何以不同的方式处理 PL/SQL 中的不同异常?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Spring MVC 中针对 HTML 和 JSON 请求以不同方式处理异常