PL/SQL游标

mark一个oracle学习网站   作者:Steven Feuerstein 在O’Reilly Media出版过10本Oracle PL/SQL方面的书,是一个oracleOracle ACE Director

这个是他的个人网站 里面有一个专栏是 learning  PL/SQL

他在oracle magazine上发表的文章汇总地址

参考资料:

    1:WIKI

    2:游标讲解的很详细

    3:介绍oracle游标

   4:教程--cursor

   5:如上教程游标

ORACLE PL/SQL语言是为了尽可能容易和有效的查询和改变数据库中表的内容而存在的。当然在这个过程中必须使用SQL语言,去访问表。

几乎每一次你都这样做,你常常使用游标去做这项工作。

1:游标的定义

游标是一个指针 指向oracle的私有的SQL区域,这个私有的SQL区域存储着查询(select)或者数据操纵语言(DML)语句像(INSERT

,UPDATE,DELETE,or  MERGE)。游标可以管理DML语句的处理在数据库中,但是PL/SQL提供若干种方式定义和操纵游标去执行SELECT语句,这篇博文主要

是为了说明PL/SQL程序员在PL/SQL中如何执行SELECT语句。

通常使用以下方式:

     a):使用SELECT - INTO 语句

     b):Fetch 从一个显式游标中

     c):使用cursor 中 FOR LOOP

     d):使用 EXECUTE IMMEDIATE INTO 为动态查询

     e):使用游标变量


a:SELECT-INTO

  SELECT -INTO 提供了一个最快和最简单的方式去fetch一个单行记录从一个SELECT语句中,语法格式如下:

SELECT select_list INTO variable_list FROM remainder_of_query where ..;

remainder_of_query包含了表或者视图等其他的 where语句是查询的条件,在variable_list中元素的个数和类型必须和select_list中的相匹配。


如果这个SELECT 语句定义了操作一行的记录去fetch,对于以上ORACLE将出现TOO_MANY_ROWS异常,如果这个SELECT语句没有查找到数据,ORACLE数据库将出现

NO_DATA_FOUND异常。

以下是使用SELECT-INTO的一些例子:

例子1:Get the last name for a specific employee ID (the primary key in the employees table): 

DECLARE
  l_last_name  employees.last_name%TYPE;
BEGIN
  SELECT last_name
    INTO l_last_name
    FROM employees
   WHERE employee_id = 138;

  DBMS_OUTPUT.put_line (
     l_last_name);
END; 

以下是解释: 太简单就简单解释以下吧!

如果employments中employee_id只有一个为138的这个语句块能够正常的执行,最后打印出last_name的结果,

如果没有employee_id为138的 以上语句块将出现一个异常 NO_DATA_FOUND,如果这一列中有unique索引,以上语句块将永远不会

出现TOO_MANY_ROWS异常。

PS:  not null + unique = primary key 就是相当于primary key的功能。

例子2: FETCH 一整行 从 employees表中:

DECLARE
   l_employee   employees%ROWTYPE;
BEGIN
   SELECT *
     INTO l_employee
     FROM employees
    WHERE employee_id = 138;

   DBMS_OUTPUT.put_line (
      l_employee.last_name);
END; 
如上所示:  如果 employee存在id为138的行, 这个last_name将显示,以上例子中我声明了一个记录 基于 employees表,然后fetch

所有的列进入这个record 。

例子3:从不同的表中Fetch列。

DECLARE
  l_last_name         
     employees.last_name%TYPE;
  l_department_name   
     departments.department_name%TYPE;
BEGIN
  SELECT last_name, department_name
    INTO l_last_name, l_department_name
    FROM employees e, departments d
   WHERE e.department_id=d.department_id
         AND e.employee_id=138;

  DBMS_OUTPUT.put_line (
     l_last_name || 
     ' in ' || 
     l_department_name);
END;

在这个例子中,我需要超过一列值但是又不是所有列的值在两个表中的,因此,我声明了两个变量然后fetch这两列值到这些变量中。

如果这些查询出的列的值和INTO到的变量的类型值不匹配会如何呢?下面是一个错误的列表:

ORA-00947: not enough values The INTO list contains fewer variables than the SELECT list.
ORA-00913: too many values The INTO list contains more variables than the SELECT list.
ORA-06502: PL/SQL: numeric or value error The number of variables in the INTO and SELECT lists matches, but the datatypes do not match and Oracle Database was unable to convert implicitly from one type to the other.











b:从显式游标中Fetch 值(Fetching from Explicit Cursors

SELECT INTO也是叫做隐式的查询,因为Oracle Database为SELECT语句隐式的打开了一个游标,fetch 一行,然后关闭游标,当完场以上

操作的时候,或者当一个异常出现的时候。


相对的,你能够通过显式的声明一个游标,执行open,fetch,和关闭操作手工的。


假设我需要写一个块fetchemployees以一个工资的升序,给它们一些奖金从总基金中通过调用 assign_bonus存储过程来完成,存储国臣管道头如下:

PROCEDURE assign_bonus (
   employee_id_in IN     
      employees.employee_id%TYPE,
   bonus_pool_io  IN OUT INTEGER)

每次assign_bonus存储过程都会被调用,存储过程执行从total中减去奖金,然后返回减少的total,当奖金pool是耗尽的时候,停止fetching 然后

提交所有的changes

如下过程通过一个显式游标实现这个过程逻辑。

例子如下:

1  DECLARE
 2     l_total       INTEGER := 10000;
 3
 4     CURSOR employee_id_cur
 5     IS
 6          SELECT employee_id
 7            FROM plch_employees
 8        ORDER BY salary ASC;
 9
10     l_employee_id   employee_id_cur%ROWTYPE;
11  BEGIN
12     OPEN employee_id_cur;
13
14     LOOP
15        FETCH employee_id_cur INTO l_employee_id;
16        EXIT WHEN employee_id_cur%NOTFOUND;
17
18        assign_bonus (l_employee_id, l_total);
19        EXIT WHEN l_total <= 0;
20     END LOOP;
21
22     CLOSE employees_cur;
23  END; 

下面详细分析以上语句块:

     4-8    
显式游标的声明,从执行区域移动 查询的结果,使用游标keywords声明查询结果       

10 基于查询返回的数据行声明一个record,在这个例子中,有仅仅一个单独的列值,因此你能够容易的声明
l_employee_id 作为employees.employee_id%TYPE类型,但是如果你使用显式cursor,最好声明一个
record 使用 %ROWTYPE,因为如果SELECT 列出的游标有可能在以后改变,这个变量也能跟着改变
12 打开cursor,因为rows能够fetched 从查询中,注意 这一步在oracle database执行 SELECT-INTO语句中也执行
14 开始一个loop循环到fetch 行
15 fetch 游标的下一行,存放行信息到这个record中 ,注意 这一步oracle database执行 SELECT-INTO语句中也执行
16 如果FETCH 不能够找到一行,退出循环
18 调用 assign_bonus存储过程
19 如果所有的bonus(奖金)是用完了,退出循环
22 关闭游标, 注意 这一步Oralce数据库在SELECT-INTO中也执行
下面是一些使用显式游标的注意事项:

1:如果查询不能够得到一些行,ORACLE数据库将不出现 NO_DATA_FOUND,作为代替,这个cursor_name%NOTFOUND属性将返回TRUE

2:查询超过一行,ORACLE DATABASE也不将出现TOO_MANY-ROWS

3:当你在包中声明一个游标(注意不是在包的子程序中)这个游标会打开,它将一直保持打开状态,直到显式的关闭或者session终止。

4:当cursor是声明在声明区(注意不是在一个包中),oracle database 将自动关闭它,当这个声明块结束的时候,但是即使这样,一个良好的编程习惯,

还是建议显式的关闭游标,如果游标是移动到包内,你将有必关闭游标手动的,如果是在局部,关闭游标也能让别的开发者能够很好的读懂你的代码。


c:使用游标循环

在PL/SQL中数值型的FOR LOOP是一个很好的扩展,对于数值型FOR LOOP循环体每次递增1从低到高,对于cursor FOR LOOP 是执行查询出来的每一行。
下面的例子是使用游标 FOR 循环显示部门为10 的所有员工的last name。
BEGIN
   FOR employee_rec IN (
        SELECT *
          FROM employees
         WHERE department_id = 10)
   LOOP
      DBMS_OUTPUT.put_line (
         employee_rec.last_name);
   END LOOP;
END;

也可以使用显式的游标 FOR循环 如下:
DECLARE
   CURSOR employees_in_10_cur
   IS
      SELECT *
        FROM employees
       WHERE department_id = 10;
BEGIN
   FOR employee_rec 
   IN employees_in_10_cur
   LOOP
      DBMS_OUTPUT.put_line (
         employee_rec.last_name);
   END LOOP;
END;

关于游标for循环的使用:首先数据库打开游标,使用 %ROWTYPE 在游标中,fetch每一行进入record,然后当所有的行被fetched或者
由于一些其他的原因循环终止了游标会关闭。

oracle数据库将自动优化 游标for循环通过和 BULK COLLECT(这篇文章也是那位大牛写的,下一步准备翻译学习)执行相似的循环操作,因此即使你的代码看起来好像
一次只fetch一条数据,实际上oracle数据库将自动fetch 100行数据每次,你能够独立的使用每一条数据。



d :Dynamic Queries with EXECUTE IMMEDIATE

动态SQL的意思是:一次性写和编译你的代码。
Dynamic SQL means that at the time you write (and then compile) your code, you do not have all the information you need for parsing a SQL statement. Instead, you must wait for runtime to complete the SQL statement and then parse and execute it.
oracle数据库动态执行SQL很容易,通过EXECUTE IMMEDIATE语句即可,查询数据时最泳衣的动态SQL执行操作。

你能够fetch 一行或者多行, 下面是一个普通的fetch 一些列的值的例子:

CREATE OR REPLACE FUNCTION 
single_number_value (
   table_in    IN VARCHAR2,
   column_in   IN VARCHAR2,
   where_in    IN VARCHAR2)
   RETURN NUMBER
IS
   l_return   NUMBER;
BEGIN
   EXECUTE IMMEDIATE
         'SELECT '
      || column_in
      || ' FROM '
      || table_in
      || ' WHERE '
      || where_in
      INTO l_return;
   RETURN l_return;
END; 

如上例子所示:使用了EXECUTE IMMEDIATE-INTO 代替 SELECT -INTO 语句,通过函数的传入的参数构建SELECT 语句,下面是调用函数的一个例子;

BEGIN
   DBMS_OUTPUT.put_line (
      single_number_value (
                'employees',
                'salary',
                'employee_id=138'));
END; 

使用 SELECT-INTO,EXECUTE IMMEDIATE-INTO如果没有查询出数据将出现NO_DATA_FOUND,如果查询出多条数据(>1)将出现
TOO_MANY_ROWS

也能够使用EXECUTE IMMEDIATE  fetch多条数据,如果fetch多条数据,需要构建一个集合,因此必须使用 BULK COLLECT,
下面是一个存储过程,在这个过程中将演示where语句中的所有行的很多列值。相当于一个集合。
CREATE OR REPLACE PROCEDURE 
show_number_values (
   table_in    IN VARCHAR2,
   column_in   IN VARCHAR2,
   where_in    IN VARCHAR2)
IS
   TYPE values_t IS TABLE OF NUMBER;

   l_values   values_t;
BEGIN
   EXECUTE IMMEDIATE
         'SELECT '
      || column_in
      || ' FROM '
      || table_in
      || ' WHERE '
      || where_in
      BULK COLLECT INTO l_values;

   FOR indx IN 1 .. l_values.COUNT
   LOOP
      DBMS_OUTPUT.put_line 
      (l_values (indx));
   END LOOP;
END; 


调用存储过程的情况:
BEGIN
   show_number_values (
      'employees',
      'salary',
      'department_id = 10 
       order by salary desc');
END; 

将会看到如下输出:
4400
3200 

动态SQL的使用中需要注意SQL注入的问题,关于如何避免SQL注入,写更加安全的SQL,可以参见 这篇文章


e: 游标变量( Cursor Variables

从名字中你就能想象游标变量,游标变量就是一个变量指向了一个游标或者结果集。不像一个显式游标,能够
传递游标变量作为一个存储过程或者函数的参数。对于使用游标变量有如下优点:
 1:传递一个游标变量到宿主环境( host environment )相当于调用一个程序单元,结果集能够被显式或进行别的处理。
 2:在函数内部构建一个结果集,然后对于那个结果集返回一个游标变量,当我们需要使用PL/SQL或者SQL构建结果集的时候非常便利。
3:传递游标变量给管道表函数---这是一个非常强大的高级技术,是游标变量的扩展、
游标变量能够用于静态和动态SQL下面的例子包含了names_for函数,这个函数返回一个游标变量, fetch employee 或者department names,
依据传递给函数的参数:

关于  names_for  函数的描述如下(返回一个游标变量):

1  CREATE OR REPLACE FUNCTION names_for (
 2        name_type_in IN VARCHAR2)
 3     RETURN SYS_REFCURSOR
 4  IS
 5     l_return   SYS_REFCURSOR;
 6  BEGIN
 7     CASE name_type_in
 8        WHEN 'EMP'
 9        THEN
10           OPEN l_return FOR
11                SELECT last_name
12                  FROM employees
13              ORDER BY employee_id;
14        WHEN 'DEPT'
15        THEN
16           OPEN l_return FOR
17                SELECT department_name
18                  FROM departments
19              ORDER BY department_id;
20     END CASE;
21
22     RETURN l_return;
23  END names_for;

描述如下  
3 定义返回的数据类型为SYS_REFCURSOR类型
5 声明一个游标变量(cursor variable)被用于函数的返回值
7 使用CASE语句根据name_type_in的值定义查询的内容
10-13 打开一个游标变量指向 employees表的查询结果
16-19 打开一个游标变量指向departments表的查询结果

下面是一个语句块使用name_for函数显示在departments表中的 所有的names
DECLARE
   l_names   SYS_REFCURSOR;
   l_name    VARCHAR2 (32767);
BEGIN
   l_names := names_for ('DEPT');

   LOOP
      FETCH l_names INTO l_name;

      EXIT WHEN l_names%NOTFOUND;
      DBMS_OUTPUT.put_line (l_name);
   END LOOP;

   CLOSE l_names;
END;

如上例所示:关于查询的信息“隐藏”在函数的背后,仅仅通过函数头来调用即可,函数只需要调用合适的SELECT语句,打开游标变量
指向查询结果集,然后返回指向查询结果集的变量即可。
一旦游标变量被打开在多个语句块间传递,我将使用显示游标做这个同样的操作: 这些步骤都如下:
1:从游标或者游标变量中FETCH值到一个或者更多的变量(甚至能够FETCH -BULK COLLECT INTO一个游标变量,操作多行数据的集合)
2:检查%NOTFOUND属性,是否fetch了所有的行
3:当fetch了所有的行,关闭游标变量。
OPEN-FOR语句是只用于游标变量,让你能够在运行时指定,不必转换为动态SQL。

不过也能在动态SELECT语句中使用OPEN-FOR是一个简单的例子:

CREATE OR REPLACE FUNCTION 
numbers_from (
      query_in IN VARCHAR2)
   RETURN SYS_REFCURSOR
IS
   l_return   SYS_REFCURSOR;
BEGIN
   OPEN l_return FOR query_in;

   RETURN l_return;
END numbers_from; 
下面是一个调用以上函数的语句块如下:
DECLARE
  l_salaries   SYS_REFCURSOR;
  l_salary     NUMBER;
BEGIN
  l_salaries :=
    numbers_from (
      'select salary 
        from employees 
       where department_id = 10');

  LOOP
    FETCH l_salaries INTO l_salary;

    EXIT WHEN l_salaries%NOTFOUND;
    DBMS_OUTPUT.put_line (l_salary);
  END LOOP;

  CLOSE l_salaries;
END;
 

查询的时候要选择正确的方式:

以上文章演示了PL/SQL语言提供了很多种方式,从最简单的SELECT-INTO的隐式查询到更复杂的游标变量,
这些 都是为了一个共同的目标:从关系表中FETCH数据到局部变量中。

以下是一些操作指南用于帮助你决定使用哪种技术:

1:当fetch一个单独的行的时候,使用SELECT -INTO 或者IMMEDIATE--INTO(如果你的查询是动态的),不要使用显式游标或者游标FOR循环。

2:当从一个查询中fetch所有的行的时候,使用FOR循环,如果FOR循环中执行体部分使用了一个或者更多的
DML语句(INSERT,UPDATE,DELETE,或者MERGE),不要使用FOR循环了,而是使用BULK COLLECT或者FORALL

3:当你需要Fetch BULK COLLECT的时候使用显式游标,但是使用BULK,COLLECT限制了每一次fetch的行数?

4:当查询的结果在运行时变动的时候使用游标变量(但是没有必要使用动态的SQL)尤其是当你需要传递一个结果集到一个
非PL/SQL的宿主环境中的时候(host environment)。

5:仅仅当你在写代码的时不能够全部构建你的SELECT语句的时候,使用EXECUTE IMMEDIATE




你可能感兴趣的:(PL/SQL游标)