Oracle 游标在最后一项中运行两次

Posted

技术标签:

【中文标题】Oracle 游标在最后一项中运行两次【英文标题】:Oracle cursor running through the last item twice 【发布时间】:2009-05-22 04:14:16 【问题描述】:

我有一个游标循环,它通过将表格的内容连接在一起来构建一个字符串,使用以下代码:

OPEN cur_t;
LOOP
    FETCH cur_t INTO v_texttoadd;

    v_string := v_string || v_texttoadd;
EXIT WHEN cur_t%notfound;
END LOOP;

当然,问题是最后一项被添加了两次,因为系统再次运行它,然后才意识到没有更多的东西可以找到。

我试着玩弄类似的东西

OPEN cur_t;
WHILE cur_t%found;
LOOP
    FETCH cur_t INTO v_texttoadd;

    v_string := v_string || v_texttoadd;
END LOOP;

但这似乎根本没有返回任何东西。

我应该使用什么样的语法,以便每一行只在结果字符串中出现一次?

【问题讨论】:

【参考方案1】:

你可以试试这个:

OPEN cur_t;
LOOP
  FETCH cur_t INTO v_texttoadd;
  EXIT WHEN cur_t%notfound;
  v_string := v_string || v_texttoadd;
END LOOP;

这是可行的,因为 %notfound 在执行 FETCH 时设置,并且没有更多行可获取。在您的示例中,您在连接后检查了 %notfound,结果,您最终得到了重复项。

【讨论】:

对我没有用。如果你在乎的话,看看我的回答。 美丽。就我而言,光标仍处于打开状态,并且不断迭代最后一项,永无止境。【参考方案2】:

已经给出了正确的答案,只是稍微阐述一下。

模拟你目前的情况:

SQL> declare
  2    cursor cur_t
  3    is
  4    select ename
  5      from emp
  6     where deptno = 10
  7    ;
  8    v_texttoadd emp.ename%type;
  9    v_string    varchar2(100);
 10  begin
 11    open cur_t;
 12    loop
 13      fetch cur_t into v_texttoadd;
 14      v_string := v_string || v_texttoadd;
 15      exit when cur_t%notfound;
 16    end loop
 17    ;
 18    dbms_output.put_line(v_string);
 19  end;
 20  /
CLARKKINGMILLERMILLER

PL/SQL-procedure is geslaagd.

这里 MILLER 被打印了两次。只需切换 EXIT 语句和 v_string 赋值,就可以得到想要的结果:

SQL> declare
  2    cursor cur_t
  3    is
  4    select ename
  5      from emp
  6     where deptno = 10
  7    ;
  8    v_texttoadd emp.ename%type;
  9    v_string    varchar2(100);
 10  begin
 11    open cur_t;
 12    loop
 13      fetch cur_t into v_texttoadd;
 14      exit when cur_t%notfound;
 15      v_string := v_string || v_texttoadd;
 16    end loop
 17    ;
 18    dbms_output.put_line(v_string);
 19  end;
 20  /
CLARKKINGMILLER

PL/SQL-procedure is geslaagd.

但是,当使用 cursor-for-loop 时,您的 PL/SQL 代码会变得更容易。然后您可以跳过 v_texttoadd 变量,循环中的行数会减少:

SQL> declare
  2    cursor cur_t
  3    is
  4    select ename
  5      from emp
  6     where deptno = 10
  7    ;
  8    v_string    varchar2(100);
  9  begin
 10    for r in cur_t
 11    loop
 12      v_string := v_string || r.ename;
 13    end loop
 14    ;
 15    dbms_output.put_line(v_string);
 16  end;
 17  /
CLARKKINGMILLER

PL/SQL-procedure is geslaagd.

您也可以使用直接 SQL 来完成这项工作。如果您使用的是 10g 或更高版本,则使用 SQL 模型子句的示例:

SQL> select string
  2    from ( select string
  3                , rn
  4             from emp
  5            where deptno = 10
  6            model
  7                  dimension by (rownum rn)
  8                  measures (ename, cast(null as varchar2(100)) string)
  9                  ( string[any] order by rn desc = ename[cv()] || string[cv()+1]
 10                  )
 11         )
 12   where rn = 1
 13  /

STRING
-----------------------------------------------------------------------------------
CLARKKINGMILLER

1 rij is geselecteerd.

问候, 抢。

【讨论】:

+1 FOR .. IN ... LOOP 建议和示例。光标 FOR 循环是您的朋友 ;-) 额外的好处:您通常可以在 FOR 循环中嵌入光标选择语句: FOR Rec IN (SELECT...) LOOP【参考方案3】:

%notfound 在 fetch 未能检索到新行时设置。

另一种可能的方式(这个避免了“if”和“exit when”):

OPEN cur_t;
FETCH cur_t INTO v_texttoadd;
WHILE cur_t%found LOOP
    v_string := v_string || v_texttoadd;
    FETCH cur_t INTO v_texttoadd;
END LOOP;

【讨论】:

【参考方案4】:

简单的答案,虽然可能不是最好的:

OPEN cur_t;
LOOP
    FETCH cur_t INTO v_texttoadd;
    IF cur_t%found THEN
        v_string := v_string || v_texttoadd;
    END IF;
EXIT WHEN cur_t%notfound;
END LOOP;

【讨论】:

我认为,根据您的缩进,您假设 EXIT 语句是 LOOP 语法的一部分,并且必须出现在循环的末尾。这不是真的; EXIT 只是一个语句,就像循环体中的任何其他语句一样。这就是为什么正如其他人所展示的那样,您可以将其放在 FETCH 之后立即解决您的问题。 对我没有用。如果你在乎的话,看看我的回答。【参考方案5】:

我在 Microsoft SQL 中制作了许多基于游标的解决方案,这些解决方案总是运行良好(过去的好日子!我非常希望我的 SQL Server 回来!)但是这个问题的所有答案对我来说都是错误的,最后一个游标的行总是执行两次,无论我多么严格地遵循他们建议的方法。

忘记非while loop 并忘记exit,这是您必须做的以避免重复执行(几乎就是您在T-SQL 中所做的!)。

  cursor c_mm is select a, b, c, d from mytable;

  begin
    open c_mm;
    fetch c_mm into a, b, c, d;
    while (not c_mm%notfound) loop 
        -- do what you have to do here

        fetch c_mm into a, b, c, d;
    end loop;
    close c_mm;
  end;

我真的不明白为什么所有 Oracle 知识库文章和论坛帖子(以及 ***)都提倡这种明显错误的退出循环解决方案!

【讨论】:

以上是关于Oracle 游标在最后一项中运行两次的主要内容,如果未能解决你的问题,请参考以下文章

Oracle游标和游标变量的区别

oracle存储过程怎么写循环

oracle调优 浅析有效的游标管理

MYSQL游标循环,多跑一圈,为啥?

Oracle 游标简介

oracle游标的使用