如何以不同的方式处理 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 immediateintobulk 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 实际上永远无法访问,是吗? 如前所述,CONTINUEWHEN OTHERS... 处理程序的一部分。缩进有点误导。 啊!那是因为我认为——来自 C 或 Java 等语言——只有一个语句可以跟随THEN(除非它是BEGIN .... END)。这就是为什么我认为,CONTINUE 将在处理任何一个 WHENs 后被击中......谢谢!

以上是关于如何以不同的方式处理 PL/SQL 中的不同异常?的主要内容,如果未能解决你的问题,请参考以下文章

如何修改我的 PL/SQL 过程以进行异常处理?

包括编写在不同文件中的过程以打包在 PL/SQL 中

处理过程 PL/SQL 的异常

如何在 Spring MVC 中针对 HTML 和 JSON 请求以不同方式处理异常

如何在 Spring MVC 中针对 HTML 和 JSON 请求以不同方式处理异常

PL/SQL 在 for 循环中执行即时异常处理