pl/sql programming 15 数据提取

数据提取 -- 游标

image

image

image

image

游标只是一个指向某个结果集的指针.

声明游标: cursor employee_cur IS select * from employees;

打开游标: open employee_cur;

提取数据: fetch employee_cur into employee_rec;  -- employee_rec 是employees%rowtype类型

关闭游标: close employee_cur;

也可以多表 join 作为结果集.

declare

  cursor joke_feedback_cur

is

  select j.name, r.laugh_volume, c.name

    from joke J, response R, comedian C

   where j.joke_id = r.joke.id

      and r.joker_id = c.joker_id;

begin

  ...

end;

image

image

image

image

image

image

image

image

不管是哪一种游标类型, pl/sql 执行一个 sql 语句使用的都是相同的操作方式. 有时, plsql 会替我们执行这些步骤

执行步骤

image

image

image

游标的属性

image

image

 

-- 隐式游标举例 --
Function book_title (isbn_in IN books.isbn%TYPE)
return books.title%type
IS
    return_value    book.title%type;
BEGIN
    SELECT     title      -- 隐式游标
    into    return_value
    from    books
   where    isbn = isbn_in;
  
    return    return_value;
   
    exception    -- 隐式游标的异常处理
        when no_date_found
        then
            return null;
        when too_many_rows  -- 隐式游标异常
        then
            errpkg.record_and_stop ('Data integrity error for:' || isbn_in);
            raise;       
END;

隐式游标有可能自动抛出 2 种异常

  • 查询没有找到能够匹配我们条件的任何一行. 这种情况下, 数据库会抛出异常 NO_DATA_FOUND
  • select 语句返回的结果已经超过了一行. 这种情况下, 数据库会抛出 TOO_MANY_ROWS 异常.

隐式游标属性

   1:  -- chap15_01.sql
   2:  create or replace procedure remove_from_circulation( isbn_in in books.isbn%type)
   3:  is
   4:  begin
   5:      delete from book where isbn = isbn_in;
   6:  end;
   7:  /
   8:  show errors;
   9:   
  10:  -- chap15_02.sql
  11:  create or replace procedure show_boo_count 
  12:  is
  13:      l_count    integer;
  14:  begin
  15:      select count(*)
  16:        into l_count
  17:        from books;
  18:        
  19:        remove_from_circulation('0-000-0000-0');
  20:        dbms_output.put_line(sql%rowcount);
  21:  end;

上例中, 无论book表中有多少条记录, 我从输出窗口看到的总是”0”, 这是因为我在SELECT INTO 语句之后又调用了 remove_from_circulation, 因此 SQL%ROWCOUNT 反映的是这个愚蠢的, 不可能有结果的 delete 语句的属性, 而不是查询语句的结果.

要想确保检查的确实是正确的SQL语句的属性值, 我们应该在该SQL语句执行之后立即把属性值保存到局部变量中, 否则SQL%ROWCOUNT等属性值, 永远保存的是最后执行的隐性游标的属性.

   1:  -- chap15_03.sql 正确方法
   2:  create or replace procedure show_boo_count 
   3:  is
   4:      l_count    integer;
   5:      l_numfound pls_integer;
   6:  begin
   7:      select count(*)
   8:        into l_count
   9:        from books;
  10:        l_num_found := SQL%ROWCOUNT;  -- 将游标属性保存在局部变量
  11:        remove_from_circulation('0-000-0000-0');
  12:        dbms_output.put_line(sql%rowcount);
  13:  end;

 

-- 显示游标举例 --
Function jealousy_level (
    NAME_IN    IN    friends.NAME%TYPE)    RETURN    NUMBER
AS
    cursor jealousy_cur
    IS
        select location from friends
        where name = UPPER(name_in);
       
    jealousy_rec    jealousy_cur%ROWTYPE;    
    retval    NUMBER;
BEGIN
    OPEN jealousy_cur;
   
    fetch jealousy_cur into    jealousy_rec;
   
    IF jealousy_cur%found then
        if jealousy_rec.location = 'PUERTO RICO' THEN
            retval := 10;
        elsif jealousy_rec.location = 'CHICAGO' THEN
            retval := 1;
        end if;
    end if;
   
    close jealousy_cur;
   
    return retval;
EXCEPTION
    when others then
        if jealousy_cur%isopen then
            close jealousy_cur;
        end if;           
END;

带参数化的游标, 游标的参数只能是 IN 类型的.

使用游标变量的好处是它提供了一种可以在不同的PL/SQL程序间传递查询结构集(从游标获取记录行)的机制.

声明显示游标

image

1. 不带参数的游标

  CURSOR company_cur IS

     SELECT company_id FROM company;

2. 带参数的游标

  CURSOR name_cur ( company_id_in IN number)

  IS

    SELECT  name FROM company

     WHERE company_id = company_id_in;

3. 带 return 语句的游标

  CURSOR emp_cur RETURN employee%ROWTYPE

  IS

    SELECT * FROM employees

     WHERE department_id = 10;

在包中声明游标

   1:  PACKAGE book_info
   2:  IS
   3:    CURSOR titles_cur
   4:    IS 
   5:      select title
   6:        from books;
   7:      
   8:    cursor books_cur(title_filter_in IN books.title%type)
   9:      return books%rowtype
  10:    IS
  11:      select *
  12:        from books
  13:       where title like title_filter_in;
  14:  end;

为什么要把游标定义在包中呢?

答案很简单, 通过在包中定义游标, 我们重用这些查询就更加容易, 也避免了在应用程序中一遍遍的编写相同的数据提取语句.

image

为什么要使用 return 语句 ?

这个包有点类似 object-c, 实际内容在包体中, 包头只是一个声明, 我们应该尽可能的隐藏包体中的内容, 只给用户看到包头的内容, 那么 return 语句的用处是.

1. 隐藏信息, 隐藏包体信息, 别人只要读包头, 就知道游标的一切, 包括这个游标返回的结果集的类型.

2. 最小化编译, 我们可以随意的修改包体中的 select 语句而不会影响到包头的游标, 这样就可以改进, 重编译代码, 而不用重新编译包头部分, 这也就意味着所有依赖于这个包的程序都不会被置成无效状态, 自然也就不必重新编译.

打开显示游标

OPEN cursor_name[argument, argument …];

image

image

也就是说, 当你游标打开时, 如果没有指定 for update 语句, 那么不会为游标的记录集加锁, 这时候, 会保存打开游标时刻的SCN, 如果游标操作过程中, 修改了记录集(游标虚拟表中的记录), 当提交时, 如果SCN 比实际数据块的SCN小, 那么这个游标的修改操作将是不成功的.

从显示游标中提取数据

select 语句构建了一个虚拟数据表, 它的返回集是由 where 语句(或者没有)确定的一系列行, 因此 plsql程序中, 游标就代表着这个虚拟的数据表.

FETCH cursor_name INTO record_or variable_list;

image

image

显示游标中的列别名

别名的作用只是为了在结果集中使用方便, 比如你有个计算函数 sum(a, b), 如果不起别名的话, 这列很难引用

   1:  declare
   2:      cursor comp_cur is
   3:          select c.name, sum(inv_amt) total_sales  -- 列别名
   4:            from company C, invoice I
   5:           where C.company_id = I.company_id
   6:             and I.invoice_date BETWEEN '01-jan-2001' AND '31-dec-2001';
   7:      comp_rec comp_cur%rowtype;  -- 这里定义的是结果集的rowtype类型
   8:  begin
   9:      open comp_cur;
  10:      fetch comp_cur into comp_rec;
  11:      ...
  12:  end;
  13:  -- 然后可以在结果集中使用如下:
  14:  IF comp_rec.total_sales > 5000 THEN
  15:      dbms_output.put_line('You have exceeded your credit limit of $5000 by ' ||
  16:                          to_char(comp_rec.total_sales - 5000, '$9999'));
  17:  END IF;
 

关闭显示游标

CLOSE cursor_name;

如果我们在程序中声明并打开了一个游标, 就一定要确保在程序结束时关闭这个游标.

这里要特别注意在包中的游标, 包中的游标, 在异常处理单元都要有关闭游标的确认.

   1:  begin
   2:      open my_package.my_cursor;
   3:      ...
   4:      close my_package.my_cursor;
   5:  exception
   6:      when others then
   7:          IF mypackage.my_cursor%ISOPEN THEN
   8:              CLOSE my_package.my_cursor;
   9:          END IF;
  10:  END;

SELECT … FOR UPDATE

当使用 select 语句从数据库查询记录时, 数据库不会被选择的行添加任何锁. 通常来说, 这是一个非常不错的特性, 因为任何时候被锁定的记录数量都要尽可能的最小, 只有那些已经被修改了但是还没有提交的记录才需要被锁定. 即使这样, 其他人还是可以看到这些记录被修改之前的状态.

不过, 有时候我们希望在程序修改之前就锁住它们. 这时就可以使用 SELECT … FOR UPDATE

   1:  CURSOR toys_cur IS
   2:      SELECT name, manufacturer, preference_level
   3:        from my_sons_collection
   4:       where hours_used = 0
   5:         for update;  -- 没有任何限制
   6:         
   7:  CURSOR fall_jobs_cur IS
   8:      SELECT task, expected_hours, tools_required
   9:        from winterize
  10:       where year_of_task = to_char(sysdate, 'yyyy')
  11:         for update of task;  -- 只针对 task 这列限制

 

我们也可以夺标SELECT中使用 for update, 这种情况, 只有自己列出的在 for update of 子句中的记录才会被锁定, 换句话说, 必须要有限制.

WHERE CURRENT OF 语句

plsql 为游标的 update 和 delete 提供了 where current of 语句. 这个语句可以让我们很容易的修改最后取出来的数据行.

   1:  update table_name
   2:     set set_clause
   3:   where current of cursor_name;
   4:   
   5:   delete 
   6:     from table_name
   7:    where current of cursor_name;

例如: 删除一个我刚刚取出的数据, 更新一个我刚刚取出的数据.

   1:  declare
   2:      cursor fall_jobs_cur IS select ... same as before ...;
   3:      job_rec fall_jobs_cur%rowtype;
   4:  begin
   5:      open fall_jobs_cur;
   6:      loop
   7:          fetch fall_jobs_cur into job_rec;
   8:          
   9:          exit when fall_jobs_cur%notfound;
  10:          
  11:          if job_rec.do_it_yourself_flag = 'youcandoit' then
  12:              update winterize 
  13:                 set responsible = 'steven'
  14:               where current of fall_jobs_cur;
  15:              commit;
  16:              exit;
  17:          end if;
  18:      end loop;
  19:      close fall_jobs_cur;
  20:  end;

 

声明 REF CURSOR 类型 游标变量

游标变量是一个指向或者引用底层游标的变量, 是一个指针, 和显示游标不一样, 显示游标已经为结果集的 pl/sql 工作区指定了名字, 而游标变量只是指向这个工作区的引用. 显示游标和隐式游标都绑定到一个专门的查询语句, 从这一点来说, 这两种游标都是静态的, 而游标变量可以用于打开任何一个查询, 甚至在一个程序中执行多个不同的查询.

使用游标变量一个最重要的好处就在于它提供了一种可以在不同的plsql 程序间传递查询的结果集

比如一个session 打开了一个游标, 可以使用游标变量将这个传递给另一个session, 而另一个session 可以关闭这个游标.

image

创建一个游标变量

image

TYPE cursor_type_name IS REF CURSOR [RETURN return type];  -- 创建引用游标类型

TYPE company_curtype IS REF CURSOR RETURN company%ROWTYPE;   -- 强类型

TYPE generic_curtype IS REF CURSOR;                          -- 弱类型

从 oracle 9i 开始, 数据库已经替我们定义好了一个弱类型, 直接用就可以了

declare my_cursor SYS_RECURSOR;  -- 定义了真正的游标变量

声明游标变量的方法
cursor_name cursor_type_name;

image

声明一个游标变量 和 创建一个真正的游标对象, 二者之前的区别很重要, 后者通过游标的sql语句查询出的结果集, 而游标变量指向游标对象. 如图:

image

 

打开游标变量

open cursor_name FOR select_statement;

如果是强类型的, 那么类型一定要匹配

image

从游标变量获取数据, 同静态游标一样
FETCH cursor_variable_name INTO record_name;
FETCH cursor_variable_name INTO varibale_name, variable_name...;

如果是强类型转换, 有可能类型不匹配, 这时候会抛出 rowtype_mistach 异常, 要捕获这个异常

EXCEPTION

  WHEN ROWTYPE_MISMATCH THEN

     --do something

游标变量的使用规则

记住 游标变量时对一个游标对象或者一个数据库中查询的引用, 而不是游标对象本身.

游标对象的作用范围类似 java 中对象的概念, 堆中的对象, 只要有一个引用(指针) 还在引用这个对象, 这个对象就要保持可以被访问状态.

游标变量不能在包中声明, 因为它没有持久状态.

我们不能通过远程过程调用(RPC) 把游标变量从一个服务器传递给另一个服务器.( 游标变量保存的只是引用)

 

游标变量作为参数传递

   1:  declare
   2:      type curvar_type is ref cursor return company%rowtype;
   3:      procedure open_query(curvar_out OUT curvar_type)
   4:      is
   5:          local_cur curvar_type;
   6:      begin
   7:          open local_cur for select * from company;
   8:          curvar_out := local_cur;
   9:      end;

 

游标表达式(用于嵌套游标)

image

image

image

image

image

image

你可能感兴趣的:(programming)