Oracle 隐式游标:for-else

Posted

技术标签:

【中文标题】Oracle 隐式游标:for-else【英文标题】:Oracle implicit cursor: for-else 【发布时间】:2013-12-02 14:29:29 【问题描述】:

这是一个非常基本的问题,但是在 Oracle PL/SQL 中将FORELSE 写入隐式游标的最佳方法是什么?一些编程语言允许这种语法,当游标没有返回行时执行FORELSE块。

我想要实现的是以下几行(伪代码):

DECLARE
    CURSOR test_cur IS
        SELECT 'a'
        FROM   table_with_zero_or_more_data;
BEGIN
    FOR r_ IN test_cur LOOP
        Dbms_Output.Put_Line ('One extra row found');
    FORELSE
        Dbms_Output.Put_Line ('No data found');
    END LOOP;
END;

我尝试使用如下异常,但 NO_DATA_FOUND 异常不会被隐式游标触发。

DECLARE 
    CURSOR test_cur IS
        SELECT 'a'
        FROM   table_with_zero_or_more_data;
BEGIN
    BEGIN
        FOR r_ IN test_cur LOOP
            Dbms_Output.Put_Line ('One extra row found');
        END LOOP;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            Dbms_Output.Put_Line ('No data found');
    END;
END;

以下内容当然有效(我毫不怀疑,也有类似的变化)。但我只是觉得这种方式有点笨拙。当代码变得更加真实和冗长时,IF 语句链接到FOR 循环就不是那么明显/直观了。它还强制引入了一个虚拟变量,而且在语法上并不美观。

DECLARE 
    CURSOR test_cur IS
        SELECT 'a'
        FROM   table_with_zero_or_more_data;
    i NUMBER := 0;
BEGIN
    FOR r_ IN test_cur LOOP
        i := i + 1;
        Dbms_Output.Put_Line ('One extra row found');
    END LOOP;
    IF i = 0 THEN
        Dbms_Output.Put_Line ('No data found');
    END IF;
END;

我只是想知道是否有更好的方法,更直观并且集成了FORIF条件?

编辑

如果我不够清楚,这里的重点是在隐式游标中执行此操作。我知道我可以使用 EXplicit 游标并检查 %NOTFOUND 等(实际上 %ROWCOUNT 更好)。

【问题讨论】:

当您使用游标进行循环时,如果显式或隐式游标为 empty,则永远不会引发 no_data_found 异常 - 它将被隐式打开和隐式关闭,就是这样。你想达到的目标有点模糊,想。您是否只想在为不返回行的查询打开游标时打印no data found 消息,并且仅在这种情况下? the point of this is to do it inside an IMPLICIT cursor。可能你的意思是在一个显式的游标中说循环(你有你的游标显式定义)。如果是这样,那么您将无法实现这一目标。 【参考方案1】:

AFAIK 因为 PL/SQL 缺少 forelse 构造,并且无法滚动您自己的构造,您希望最好的方法是使用额外的状态变量来满足隐式游标 for-loop 未处理的情况行。

但是,您可以以更明显的方式构建代码:

declare
  cursor test_c is
    with data_ as (
      select 1 as id, 'foo' as str from dual union all
      select 2 as id, 'bar' as str from dual
    )
    select str from data_ where id > 2;

  -- isolate the data processing into a dedicated subroutine/package
  -- pass all required information as parameters
  procedure process_data(p_data in test_c%rowtype) is
  begin
    dbms_output.put_line('processing: ' || p_data.str);
  end;
begin
  -- more things can take place here ...

  -- isolate the data processing into a dedicated block or subroutine
  -- with a block level comment like: processing all foos and bars to
  -- conform business rule car. (or even better: name the subroutine 
  -- accordingly !)
  declare
    v_has_data boolean := false;
  begin
    for d in test_c loop
      v_has_data := true;
      process_data(d);
    end loop;

    if not v_has_data then
      dbms_output.put_line('no data processed');
    end if;
  end;

  -- more things can take place here ...
end;
/

即使我非常同意这种方法的错误...... 笨拙 请记住,所有编程语言都有权衡。

【讨论】:

这不是我真正想要的答案。我想听到的是存在一个 PL/SQL 结构,到目前为止我一直不知道,它可以满足我的需求。但你的答案是最诚实的,你肯定在尝试回答之前阅读了这个问题!为此,您会获得 +1,如果我在几天内没有听到更好的答案,我会将其标记为已接受的答案。 为了清楚起见,我会提出一个NO_DATA_FOUND 异常来处理我的其他异常情况。我会使用这个异常来处理我的空游标而不用担心,但在我的情况下,包含聚合函数调用的隐式游标永远不会引发它。谢谢!【参考方案2】:

尝试使用简单的循环并使用光标的%NOTFOUND 属性进行测试,如下所示:

DECLARE 
    CURSOR test_cur IS
        SELECT 'a'
        FROM   table_with_zero_or_more_data;
    r_test test_cur%ROWTYPE;
BEGIN
    OPEN test_cur;

    FETCH test_cur INTO r_test;

    IF test_cur%NOTFOUND THEN
        dbms_output.put_line('No data found');
    ELSE
        LOOP
            FETCH test_cur INTO r_test;
            EXIT WHEN test_cur%NOTFOUND;
            dbms_output.put_line('One extra row found');
            -- 
            -- other routine code if there are rows found.
            -- 
        END LOOP;
    END IF;
END;
/

有关FETCH 的更多详细信息,请参阅Oracle Docs。

还可以在下面的评论中查看 Nicholas Krasnov 的 SQL Fiddle。很有帮助。

【讨论】:

其实这样不行。无论表中是否有数据,您总是会在最后一行数据上点击%NOTFOUND 条件。换句话说,您将始终打印No data found 我已经稍微编辑了我的答案并验证了它。它确实运行。 %NOTFOUND 不是一个条件,而是一个游标属性,如果您在获取最后一行后尝试获取或从空游标中获取,则该属性设置为 true。将查询中的 table_with_zero_or_more_data 替换为 dual 并在最后验证这一点。 我认为可以是simplified 一点点。 IF 语句的数量可以减少到一个。 @Rachcha 我做到了。我在之前的评论中包含了一个 sqlfiddle 链接。 好的,所以在第三次编辑之后,这个答案仍然不能根据定义的规范工作。在处理数据之前,您执行 2 个FETCH 语句。这意味着如果表包含任何数据,您总是会错过第一行。解决方案(顺便说一句)是将您的 One extra row foundbefore FETCHEXIT 语句放在循环内。 。请现在放弃,不要把我当作 PL/SQL 新手。即使经过第四次更正,您仍然无法回答最初的问题,即如何使用隐式光标进行此操作。【参考方案3】:

类似的方法是使用游标的%ROWCOUNT 属性。其他游标属性参考请使用链接http://docs.oracle.com/cd/B12037_01/appdev.101/b10807/13_elems011.htm 下面给出了使用 ROWCOUNT 的实现。

DECLARE 
CURSOR test_cur IS
    SELECT 'a'
    FROM   table_with_zero_or_more_data;
i NUMBER := 0;
r_test test_cur%ROWTYPE;
BEGIN
   OPEN test_cur;

LOOP
    FETCH test_cur INTO r_test;
    IF NOT test_cur%ROWCOUNT > 0 THEN
        dbms_output.put_line("No data found");
        EXIT;
    ELSE
        dbms_output.put_line("extra row found. Row count is now "||test_cur%ROWCOUNT);
    END IF;
    ...
    ... -- other routine code if there are rows found.
    ...
END LOOP;
END;
/

【讨论】:

这确实有效(ish)。但是,您需要在您的业务逻辑之前放置一个EXIT WHEN test_cur%NOTFOUND 条件(即在您的“找到额外行”语句之前)。您也不需要其中的ELSE 语句。只需END IF;(在第一个EXIT; 条件之后),然后是%NOTFOUND EXIT 条件,然后是您的业务逻辑。但是,此答案因使用显式游标而失败。对不起! PS:你不觉得cur%ROWCOUNT = 0NOT cur%ROWCOUNT > 0简单吗?您似乎在使用编程的双重否定!【参考方案4】:
 DECLARE
     CURSOR CEMP 
     IS 
     SELECT *FROM EMP ;
     V_EMP CEMP%ROWTYPE;
 BEGIN
     OPEN CEMP;
     LOOP
         FETCH CEMP INTO V_EMP;
         IF CEMP%ROWCOUNT=0 or  CEMP%ROWCOUNT IS NULL  
         THEN
             DBMS_OUTPUT.PUT_LINE('No Data Found');
         END IF;
         EXIT WHEN CEMP%NOTFOUND;
         DBMS_OUTPUT.PUT_LINE(V_EMP.ENAME);
     END LOOP;
     CLOSE CEMP;
 END;
/

【讨论】:

这是一个糟糕的答案。您必须检查游标的每个循环上是否存在数据,如果您的表包含大量行,这会浪费大量资源。我只想检查一次!

以上是关于Oracle 隐式游标:for-else的主要内容,如果未能解决你的问题,请参考以下文章

oracle游标的使用

Oracle 游标简介

oracle的隐式游标

oracle 隐式游标,显示游标,游标循环,动态SELECT语句和动态游标,异常处理,自定义异常

oracle中游标的使用?

Oracle游标使用总结