您可以从立即执行语句中退出 PL-SQL 循环吗?

Posted

技术标签:

【中文标题】您可以从立即执行语句中退出 PL-SQL 循环吗?【英文标题】:Can you exit a PL-SQL loop from within an execute immediate statement? 【发布时间】:2015-07-30 17:04:35 【问题描述】:

我有以下 sn-p(简化以排除无关细节):

<<cursor_loop>> 
LOOP 
  fetch c1 into somerecord;
  EXECUTE IMMEDIATE 'begin EXIT cursor_loop WHEN 1 = 1; end;';
END LOOP cursor_loop;

当我运行它时,它会因为 PLS-00201: identifier 'CURSOR_LOOP' must be declared 错误而失败。

如果我将循环标签留在立即执行之外,我会得到PLS-00376: illegal EXIT/CONTINUE statement; it must appear inside a loop

显然后者是错误的,但不清楚为什么前者是。

这个循环可以从execute-immediate内的语句中退出吗?

【问题讨论】:

A hack:在execute immediate 语句中引发异常,这将退出循环。 为什么需要?也许有更好的方法。 【参考方案1】:

正如其他人已经说过的那样,由于范围的原因,您不能直接在动态 SQL 中引用 cursor_loop。如果您坚持使用这种模式,那么您可以使用绑定变量标志将状态信息从动态代码传递回静态代码;类似:

DECLARE
  break_loop pls_integer;
...
  break_loop := 0;
  <<cursor_loop>> 
  LOOP 
    fetch c1 into somerecord;
    EXECUTE IMMEDIATE 'begin if 1 = 1 then :break_loop := 1; end if; end;'
      USING OUT break_loop;
    EXIT cursor_loop WHEN break_loop = 1;
  END LOOP cursor_loop;
...

稍微更完整一些,但显然仍然非常做作,例如:

DECLARE
  break_loop pls_integer;
  somevar number;
  c1 sys_refcursor;
BEGIN
  OPEN c1 FOR
    select 1 from dual
    union all select 2 from dual
    union all select 3 from dual;

  break_loop := 0;
  dbms_output.put_line('before loop, break_loop is ' || break_loop);
  <<cursor_loop>> 
  LOOP 
    fetch c1 into somevar;
    exit when c1%notfound;
    dbms_output.put_line('got ' || somevar);
    EXECUTE IMMEDIATE 'begin if :somevar = 2 then :break_loop := 1; end if; end;'
      USING somevar, OUT break_loop;
    EXIT cursor_loop WHEN break_loop = 1;
  END LOOP cursor_loop;
  dbms_output.put_line('after loop, break_loop is ' || break_loop);
END;
/

PL/SQL procedure successfully completed.

before loop, break_loop is 0
got 1
got 2
after loop, break_loop is 1

在获取值“3”之前,由于动态检查而退出循环。

【讨论】:

请注意,无需使用此方法命名循环,但我想知道是否应该开始将循环命名为标准做法,因为我知道可以这样做。 @JohnO - 我不确定你是否有嵌套循环并且想要跳出不止一个级别,所以我把它留在了。如果你在块中只有一个循环,那么你也可以在声明部分初始化标志,但如果您(可能)有多个标志,请确保在第一个不伤害之前正确设置它。 您也可以按照 ShannonSeverance 的建议抛出异常,但是您需要在某个地方使用另一个块和异常处理程序来阻止它退出整个过程。【参考方案2】:

不,你不能。

动态 SQL 语句在单独的范围内运行——它不能引用在调用块中定义的变量或操作它们的值(当然,除非您的动态语句具有允许您显式地在两个使用EXECUTE IMMEDIATEUSINGINTO 子句传入和返回值)。同样,它不能引用循环名称,因为执行动态语句时该名称不在范围内。

在这种情况下,不清楚为什么首先要使用 EXECUTE IMMEDIATE 而不是将 EXIT 编码为循环的正常部分。

【讨论】:

我在第三方模式下定义了几十个游标,我需要能够按名称引用它们。我一直在尝试通过立即执行来打开/关闭它们。虽然还有其他(更好的)解决方案,但这些都涉及将那些定义的游标更改为可以在动态游标中使用的字符串,但我不能这样做。我也不想定义它们两次,一次作为游标,一次作为字符串。我可以使用立即执行来打开、获取和关闭游标...但是找不到时无法设置退出。 @JohnO - 不确定我是否完全遵循您的流程或限制;但是为什么不能在动态语句中设置一个标志(通过绑定变量),然后静态代码使用该标志来决定是否应该退出循环? @Alex Poole 隧道愿景。我从来没有想过。现在尝试...如果您想将其写为答案,看起来它正在工作,我会接受它。【参考方案3】:

我认为不会。 EXECUTE IMMEDIATE 用于运行 SQL 语句,而不是任何块。来自文档:

EXECUTE IMMEDIATE 语句构建并执行动态 SQL 在单个操作中声明。它是本地人的手段 动态 SQL 处理大多数动态 SQL 语句。

https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/executeimmediate_statement.htm

【讨论】:

虽然你确实可以动态执行匿名块,但这里的问题是EXECUTE IMMEDIATE中的动态语句创建了自己的执行上下文,它对调用者上下文一无所知,包括其中的变量或块.

以上是关于您可以从立即执行语句中退出 PL-SQL 循环吗?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个 PL-SQL 循环只有在与 DDL 语句分开执行时才起作用?

break,continue和label语句

break,continue和label语句

从bash循环执行gdb立即退出

退出循环break,在whilefordo...while循环中使用break语句退出当前循环,直接执行后面的代码。

pythonfor循环break可以出现多次