为什么使用显式游标而不是常规循环?


12

我已经编写了一年的基本Web应用程序(针对Oracle数据库),并且由于功能非常简单,因此我们大多数人坚持使用常规的FOR循环来获取数据:

for i in (select * from STUDENTS) loop
      htp.prn(i.student_last_name || ', ' || i.student_first_name || ' ' || i.student_dob);
end loop;

但是,游标似乎是做事的“正确”方法。我可以找到许多有关什么是游标以及如何遍历游标的信息,但是我找不到在常规的FOR循环上使用游标的确凿的理由。是否取决于程序的需求?我应该了解哪些固有优势?


这种类型FOR只是使用游标的另一种方法。请参阅文档:docs.oracle.com/cd/E11882_01/appdev.112/e10472/… 无论如何,htp.prn()有什么作用?
dezso 2012年

那是我们的输出功能之一。那么,您是否偏爱使用哪种方法?
2012年

FOR我认为,对于这类事情,循环更具可读性。我倾向于仅在必须向后退而不仅仅是前进时才使用“真实”光标。我问了另一个问题,因为我可以想象使用表函数而不是htp.prn()
dezso 2012年

值得一提的是,这两种形式的游标在性能上都比纯SQL解决方案差-特别是与DML语句有关。
David Aldridge

Answers:


7

游标可以是显式的或隐式的,并且可以在FOR循环中使用这两种类型。您的问题确实有两个方面。

  1. 为什么在隐式游标FOR循环上使用显式游标FOR循环?

    • 重用查询时,请使用显式游标FOR循环,否则首选隐式游标。
  2. 为什么要使用带有FETCH的循环而不是没有显式FETCH的FOR循环?

    • 需要批量收集或需要动态SQL时,请在循环内使用FETCH。

这是文档中的一些有用信息。

隐式游标FOR LOOP的示例

BEGIN
   FOR vItems IN (
      SELECT last_name
      FROM employees
      WHERE manager_id > 120
      ORDER BY last_name
   ) 
   LOOP
      DBMS_OUTPUT.PUT_LINE ('Name = ' || vItems.last_name);
   END LOOP;
END;
/

显式游标FOR LOOP的示例

DECLARE
   CURSOR c1 IS
      SELECT last_name
      FROM employees
      WHERE manager_id > 120
      ORDER BY last_name;
BEGIN
   FOR vItems IN c1 LOOP
      DBMS_OUTPUT.PUT_LINE ('Name = ' || vItems.last_name);
   END LOOP;
END;
/

隐式游标

隐式游标是由PL / SQL构造和管理的会话游标。每当您运行SELECT或DML语句时,PL / SQL都会打开一个隐式游标。您无法控制隐式游标,但是可以从其属性获取信息。

隐式游标在其关联的语句运行后关闭;但是,其属性值将保持可用,直到另一个SELECT或DML语句运行。

隐式游标属性是:SQL%ISOPEN,SQL%FOUND,SQL%NOTFOUND,SQL%ROWCOUNT,SQL%BULK_ROWCOUNT,SQL%BULK_EXCEPTIONS

显式游标

显式游标是您构造和管理的会话游标。您必须声明并定义一个显式游标,为其指定一个名称并将其与查询关联(通常,该查询返回多行)。然后,您可以通过以下两种方式处理查询结果集:

打开显式游标(使用OPEN语句),从结果集中获取行(使用FETCH语句),然后关闭显式游标(使用CLOSE语句)。

在游标FOR LOOP语句中使用显式游标(请参阅“使用游标FOR LOOP语句进行查询结果集处理”)。

您不能将值分配给显式游标,不能在表达式中使用它或将其用作形式化的子程序参数或宿主变量。您可以使用游标变量执行这些操作(请参见“游标变量”)。

与隐式游标不同,您可以通过其名称引用显式游标或游标变量。因此,显式游标或游标变量称为命名游标。

游标FOR LOOP语句

游标FOR LOOP语句使您可以运行SELECT语句,然后立即循环遍历结果集的行。该语句可以使用隐式或显式游标。


1
隐式游标从10g开始一次获取100行。
David Aldridge

16

您发布的代码使用游标。它使用隐式游标循环。

在某些情况下,使用显式游标循环(即在声明部分声明CURSOR变量)会产生更清晰的代码或更好的性能

  1. 如果您有更复杂的查询,无法将其重构为视图,则在循环迭代时,它可以使代码更易于阅读 student_cursor而不是包含嵌入一堆逻辑的30行SQL语句。例如,如果您要打印出所有已获准毕业并且涉及加入具有学业记录的表格的学生,则其学位课程的要求,带有学术保留信息的表格,带有过期图书馆书籍信息的表格,包含有关未付费用,管理费用等信息的表格。重构代码可能很有意义,这样该查询就不会停留在与向用户显示列表有关的代码中间。这可能涉及创建一个将封装所有逻辑的视图。或者它可能涉及创建一个显式游标,该游标被声明为当前PL / SQL块的一部分或某个更高级别的PL / SQL块(即 在包中声明的游标),以便可重用。或者它可能涉及为封装和可重用性进行其他操作(例如,创建流水线表函数)。
  2. 如果要在PL / SQL中使用批量操作,则通常需要使用显式游标。这是一个StackOverflow线程,讨论显式游标和隐式游标之间性能差异。如果您正在做的只是打电话给htp.prn,那么做一个BULK COLLECT可能不会买任何东西。但是,在其他情况下,它可以显着提高性能。

2

我看到许多开发人员出于旧习惯使用显式游标而不是隐式游标。这是因为在Oracle版本7中,这始终是更有效的方法。如今,通常存在另一种方式。特别是在优化程序中,如果需要,可以将隐式游标重写为用于批量收集的循环。


0

最近,我不得不将一组查询从隐式的FOR循环重写为显式的游标。原因是查询通过链接从外部数据库中获取数据,并且该数据库的编码与本地数据库的编码不同。将数据从隐式游标传输到本地定义的记录类型时,会发生间歇性错误(仅在某些特定行上)。我们的DBA向我们解释了这一点,我们自己无法深入了解这一点。似乎这是已报告的Oracle错误。

建议我们使用显式游标重写所有内容,该错误消失了。

不是您可能想使用显式而不是隐式的主要原因,但值得一提。

编辑:Oracle 12c。


您能否添加错误和/或注释编号,以便阅读本文的人可以了解更多症状以及是否/何时解决此问题?
里菲尔

抱歉,错误报告是由我们的一个DBA完成的,我无权访问该信息。
Robotron
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.