oracle--11游标


 游标-----内存中的一块区域,存放的是select 的结果
    
    游标用来处理从数据库中检索的多行记录(使用SELECT语句)。利用游标,程序可以逐个地处理和遍历一次检索返回的整个记录集。
     为了处理SQL语句,Oracle将在内存中分配一个区域,这就是上下文区。这个区包含了已经处理完的行数、指向被分析语句的指针,整个区是查询语句返回的数据行集。游标就是指向上下文区句柄或指针。
两种游标:
一、显示游标(需要明确定义!)
    显示游标被用于处理返回多行数据的SELECT 语句,游标名通过CURSOR….IS 语句显示地赋给SELECT 语句。
       在PL/SQL中处理显示游标所必需的四个步骤:
       1)声明游标;CURSOR cursor_name IS select_statement
       2)为查询打开游标;OPEN cursor_name
       3)取得结果放入PL/SQL变量中;
              FETCH cursor_name INTO list_of_variables;
              FETCH cursor_name INTO PL/SQL_record;
       4)关闭游标。CLOSE cursor_name
       注意:在声明游标时,select_statement不能包含INTO子句。当使用显示游标时,INTO子句是FETCH语句的一部分。
   1、 显式游标
      select语句上 使用显式游标
      能明确访问结果集
         for循环游标
         参数游标
           解决多行记录的查询问题
         fetch游标
二、隐式游标 
       所有的隐式游标都被假设为只返回一条记录。
       使用隐式游标时,用户无需进行声明、打开及关闭。PL/SQL隐含地打开、处理,然后关掉游标。
       例如:
       …….
       SELECT studentNo,studentName
       INTO curStudentNo,curStudentName
       FROM StudentRecord
       WHERE name=’gg’;
       上述游标自动打开,并把相关值赋给对应变量,然后关闭。执行完后,PL/SQL变量curStudentNo,curStudentName中已经有了值。
   2、 隐式游标
     单条sql语句所产生的结果集合
       用关键字SQL表示隐式游标
        4个属性 %rowcount  影响的记录的行数  整数
                %found     影响到了记录 true
                %notfound  没有影响到记录 true
                %isopen    是否打开  布尔值 永远是false
         多条sql语句 隐式游标SQL永远指的是最后一条sql语句的结果
         主要使用在update 和 delete语句上     


实际操作和例子:
   (1)FOR循环游标 (常用的一种游标)

 --<1>定义游标
 --<2>定义游标变量
 --<3>使用for循环来使用这个游标

  --前向游标 只能往一个方向走
  --效率很高
      declare
        --类型定义
        cursor cc is select empno,ename,job,sal
         from emp where job = 'MANAGER';
        --定义一个游标变量
        ccrec cc%rowtype;
      begin
         --for循环
         for ccrec in cc loop
            dbms_output.put_line(ccrec.empno||'-'||ccrec.ename||'-'||ccrec.job||'-'||ccrec.sal);
         end loop;       
      end;
   (2) fetch游标
     --使用的时候 必须要明确的打开和关闭
      declare
        --类型定义
        cursor cc is select empno,ename,job,sal
         from emp where job = 'MANAGER';
        --定义一个游标变量
        ccrec cc%rowtype;
      begin
        --打开游标
         open cc;
        --loop循环
         loop
            --提取一行数据到ccrec中
            fetch cc into ccrec;        
            --判断是否提取到值,没取到值就退出
            --取到值cc%notfound 是false
            --取不到值cc%notfound 是true
            exit when cc%notfound;
            dbms_output.put_line(ccrec.empno||'-'||ccrec.ename||'-'||ccrec.job||'-'||ccrec.sal);            
         end loop; 
        --关闭游标
         close cc;  
      end;
  游标的属性4种
       %notfound  fetch是否提到数据 没有true 提到false
       %found      fetch是否提到数据 有true 没提到false
       %rowcount  已经取出的记录的条数
       %isopen    布尔值 游标是否打开
   (3)参数游标
 按部门编号的顺序输出部门经理的名字
     declare
       --部门
       cursor c1 is select deptno from dept;
       --参数游标c2,定义参数的时候
       --只能指定类型,不能指定长度 
       --参数只能出现在select语句=号的右侧
       cursor c2(no number,pjob varchar2) is select emp.* from emp
         where deptno = no and job=pjob;
       c1rec c1%rowtype;
       c2rec c2%rowtype;
       --定义变量的时候要指定长度
       v_job varchar2(20);
     begin
       --部门
        for c1rec in c1 loop
          --参数在游标中使用
          for c2rec in c2(c1rec.deptno,'MANAGER') loop
            dbms_output.put_line(c1rec.deptno||'-'||c2rec.ename);
          end loop;
        end loop;
     end; 
  (4)引用游标/动态游标
       -- select语句是动态的
     declare
       --定义一个类型(ref cursor)弱类型   
       type cur is ref cursor;
         --强类型(返回的结果集有要求)
       type cur1 is ref cursor return emp%rowtype;
       --定义一个ref cursor类型的变量  
       cura cur;
       c1rec emp%rowtype;
       c2rec dept%rowtype;
     begin
  DBMS_output.put_line('输出员工')   ;      
       open cura for select * from emp;
       loop
         fetch cura into c1rec;  
         exit when cura%notfound;
         DBMS_output.put_line(c1rec.ename)   ;
       end loop ;
  DBMS_output.put_line('输出部门')   ;
       open cura for select * from dept;
       loop
         fetch cura into c2rec;  
         exit when cura%notfound;
         DBMS_output.put_line(c2rec.dname)   ;
       end loop; 
       close cura;
    end;


1,什么是游标?
①从表中检索出结果集,从中每次指向一条记录进行交互的机制。

②关系数据库中的操作是在完整的行集合上执行的。
由SELECT 语句返回的行集合包括满足该语句的WHERE 子句所列条件的所有行。由该语句返回完整的行集合叫做结果集。
应用程序,尤其是互动和在线应用程序,把完整的结果集作为一个单元处理并不总是有效的。
这些应用程序需要一种机制来一次处理一行或连续的几行。而游标是对提供这一机制的结果集的扩展。

游标是通过游标库来实现的。游标库是常常作为数据库系统或数据访问API 的一部分而得以实现的软件,
用来管理从数据源返回的数据的属性(结果集)。这些属性包括并发管理、在结果集中的位置、返回的行数,
以及是否能够在结果集中向前和/或向后移动(可滚动性)。

游标跟踪结果集中的位置,并允许对结果集逐行执行多个操作,在这个过程中可能返回至原始表,也可能不返回至原始表。
换句话说,游标从概念上讲基于数据库的表返回结果集。
由于它指示结果集中的当前位置 ,就像计算机屏幕上的光标指示当前位置一样,“游标”由此得名。

2,游标有什么作用?
①指定结果集中特定行的位置。
②基于当前的结果集位置检索一行或连续的几行。
③在结果集的当前位置修改行中的数据。
④对其他用户所做的数据更改定义不同的敏感性级别。
⑤可以以编程的方式访问数据库。

3,为什么避免使用游标?
①在创建游标时,最需要考虑的事情是,“是否有办法避免使用游标?”
因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该改写;
如果使用了游标,就要尽量避免在游标循环中再进行表连接的操作。

4,Oracle游标的类型?
①静态游标:结果集已经确实(静态定义)的游标。分为隐式和显示游标。
⑴隐式游标:所有DML语句为隐式游标,通过隐式游标属性可以获取SQL语句信息。
⑵显示游标:用户显示声明的游标,即指定结果集。当查询返回结果超过一行时,就需要一个显式游标。
②REF游标:动态关联结果集的临时对象。

5,Oracle游标的状态有哪些,怎么使用游标属性?
①游标的状态是通过属性来表示。
%Found :Fetch语句(获取记录)执行情况True or False。
%NotFound : 最后一条记录是否提取出True or False。
%ISOpen : 游标是否打开True or False。
%RowCount :游标当前提取的行数 。
②使用游标的属性。
例子:/* conn scott/tiger */
Begin
Update emp Set SAL = SAL + 0.1 Where JOB = &#39;CLERK&#39;;
If SQL%Found Then
DBMS_OUTPUT.PUT_LINE(&#39;已经更新!&#39;);
Else
DBMS_OUTPUT.PUT_LINE(&#39;更新失败!&#39;);
End If;
End;

6,如何使用显示游标,?如何遍历循环游标?
①使用显示游标
⑴声明游标:划分存储区域,注意此时并没有执行Select 语句。
CURSOR 游标名( 参数 列表) [返回值类型] IS Select 语句;
⑵打开游标:执行Select 语句,获得结果集存储到游标中,此时游标指向结果集头, 而不是第一条记录。
Open 游标名( 参数 列表);
⑶获取记录:移动游标取一条记录
Fetch 游标名InTo 临时记录或属性类型变量;
⑷关闭游标:将游标放入缓冲池中,没有完全释放资源。可重新打开。
Close 游标名;
②遍历循环游标
⑴For 循环游标
循环游标隐式打开游标,自动滚动获取一条记录,并自动创建临时记录类型变量存储记录。处理完后自动关闭游标。
For 变量名 In 游标名
Loop
数据处理语句;
End Loop;
⑵Loop循环游标
。。。
Loop
Fatch 游标名InTo 临时记录或属性类型变量;
Exit When 游标名%NotFound;
End Loop;


共享游标是用户提交SQL或PL/SQL程序块到Oracle的share pool之后,在library cache中生成的一个可执行对象,这个对象我们称之为游标(cursor)。而SQL定义游标则是SELECT语句产生的多行结果集,需要声明、打开、提取、关闭。

游标定义与分类

游标包括shared cursor和session cursor:

shared cursor即是共享游标,是SQL语句在游标解析阶段生成获得的,是位于library cache中的sql或匿名的pl/sql等。其元数据被在视图V$sqlarea与v$sql中具体化。如果library cache中的父游标与子游标能够被共享,此时则为共享游标。父游标能够共享即为共享的父游标,子游标能够共享即为共享的子游标。

session cursor即系统为用户分配缓存区,用于存放SQL语句的执行结果。用户可以通过这个中间缓冲区逐条取出游标中的记录并对其处理,直到所有的游标记录被逐一处理完毕。session cursor指的跟这个session相对应的server process的PGA里(准确的说是UGA)的一块内存区域(或者说内存结构)即其主要特性表现在记录的逐条定位,逐条处理。session cursor的元数据通过v$open_cursor视图来具体化,每一个打开或解析的SQL都将位于该视图。

游标的生命周期

shared cursor生命周期

1)包含vpd的约束条件:SQL语句如果使用的表使用了行级安全控制,安全策略生成的约束条件添加到where子句中。

2)语法、语义、访问权限检查:检查SQL语句书写的正确性,对象存在性,用户的访问权限。

3)父游标缓存:将该游标(SQL语句)的文本进行哈希得到哈希值并在library cache寻找相同的哈希值,如不存在则生存父游标且保存在library cache中,按顺序完成后续步骤。如果此时存在父游标,则进一步判断是否存在子游标。若存在相同的子游标,则直接调用其子游标的执行计划执行该SQL语句,否则转到下一步进行逻辑优化。

4)逻辑优化:使用不同的转换技巧,生成语义上等同的新的SQL语句(SQL语句的改写),一旦该操作完成,则执行计划数量、搜索空间将会相应增长。其主要目的未进行转换的情况下是寻找无法被考虑到的执行计划。

5)物理优化:为逻辑优化阶段的SQL语句产生执行计划,读取数据字典中的统计信息以及动态采样的统计信息,计算开销,开销最低的执行计划将被选中。

6)子游标缓存:分配内存,生成子游标(即最佳执行计划),与父游标关联。可以在v$sqlarea, v$sql得到具体游标信息,父子游标通过sql_id关联。

对于仅仅完成步骤1与2的SQL语句即为软解析,否则即为硬解析。SQL语句在Oracle中的执行机理大概也类似这个,具体可见“Oracle SQL语句执行流程与顺序原理解析”。

共享游标包括父游标和子游标。

父游标是在进行硬解析时产生的,父游标里主要包含两种信息:SQL文本以及优化目标(optimizer goal),首次打开父游标被锁定,直到其他所有的session都关闭该游标后才被解锁。当父游标被锁定的时候是不能被LRU算法置换出library cache,只有在解锁以后才能置换出library cache,此时该父游标对应的所有子游标也同样被置换出library cache。v$sqlarea中的每一行代表了一个parent cursor,address表示其内存地址。

子游标在发生硬解析时,在产生父游标的同时,则跟随父游标会产生相应的子游标,此时V$SQL.CHILD_NUMBER的值为0。如果存在父游标,由于不同的运行环境,此时同样会产生新的子游标,新子游标的CHILD_NUMBER在已有子游标基础上以1为单位累计。子游标包括游标所有相关信息,如具体的执行计划、绑定变、OBJECT、权限、优化器设置等。子游标随时可以被LRU算法置换出library cache,当子游标被置换出library cache时,oracle可以利用父游标的信息重新构建出一个子游标来,这个过程叫reload。v$sql中的每一行表示了一个child cursor,根据hash value和address与parent cursor关联。child cursor有自己的address,即v$sql.child_address。

确定一个游标的三个主要字段:address、hash_value和child_number。sql_id可以唯一确定一个父游标,sql_id、child_number唯一确定一个子游标。

session cursor生命周期

session cursor需要从UGA中分配内存,因此有其生命周期。其生命周期主要包括:

打开游标(根据游标声明的名称在UGA中分配内存区域);
解析游标(将SQL语句与游标关联,并将其执行计划加载到Library Cache);
定义输出变量(仅当游标返回数据时);
绑定输入变量(如果与游标关联的SQL语句使用了绑定变量);
执行游标(即执行SQL语句);
获取游标(即获取SQL语句记录结果,根据需要对记录作相应操作。游标将逐条取出查询的记录,直到取完所有记录);
关闭游标(释放UGA中该游标占有的相关资源,但Library Cache中的游标的执行计划按LRU原则清除,为其游标共享提供可能性);
对于session cursor而言,可以将游标理解为任意的DML,DQL语句(个人理解,有待核实)。即一条SQL语句实际上就是一个游标,只不过session cursor分为显示游标和隐式游标,以及游标指针。由上面游标的生命周期可知,任何的游标(SQL语句)都必须经历内存分配,解析,执行与关闭的过程。故对隐式游标而言,生命周期的所有过程由系统来自动完成。对所有的DML和单行查询(select … into …)而言,系统自动使用隐式游标。多行结果集的DQL则通常使用显示游标。

一个session cursor只能对应一个shared cursor,而一个shared cursor却可能同时对应多个session cursor。

共享游标其他知识点

查看语句共享可以借助两个数据字典:V$SQLAREA和V$SQL。V$SQLAREA保留SQL语句的父游标信息,可以通过SQL_ID标识,其中的VERSION_COUNT列表示子游标的数量。V$SQL保留SQL语句的子游标信息,可以通过SQL_ID和CHILD_NUMBER标识。V$SQL_SHARED_CURSOR可以查看语句产生子游标的原因。关于v$sql和v$sqlarea视图字段及其详解见“Oracle高资源消耗SQL语句定位”。
父游标的关键信息是sql文本,子游标的关键信息是执行计划和执行环境。
硬解析通常是由于不可共享的父游标造成的,如经常变动的SQL语句,或动态SQL或未使用绑定变量等。
解决硬解析的办法则通常是使用绑定变量来解决。
与父游标SQL文本完全一致的情形下,多个相同的SQL语句可以共享一个父游标。
SQL文本、执行环境完全一致的情形下,子游标能够被共享,否则如果执行环境不一致则生成新的子游标。
游标是可以被所有进程共享的,也就是说如果100个进程都执行相同的SQL语句,那么这100个进程都可以同时使用该SQL语句所产生的游标,从而节省了内存。每个游标都是由library cache中的两个或多个对象所体现的,至少两个对象:一个对象叫做父游标(parent cursor),包含游标的名称以及其他独立于提交用户的信息,从v$sqlarea视图里看到的都是有关父游标的信息;另外一个或多个对象叫做子游标(child cursors),如果SQL文本相同,但是可能提交SQL语句的用户不同,或者用户提交的SQL语句所涉及到的对象为同名词等,都有可能生成不同的子游标。因为这些SQL语句的文本虽然完全一样,但是上下文环境却不一样,因此这样的SQL语句不是一个可执行的对象,必须细化为多个子游标后才能够执行。子游标含有执行计划或者PL/SQL对象的程序代码块等。

--1.For 循环游标--(1)定义游标--(2)定义游标变量--(3)使用for循环来使用这个游标  
DECLARE   
       --类型定义  
       CURSOR c_emp  
       IS  
       SELECT empno,ename,job,sal  
       FROM emp  
       WHERE job = 'MANAGER';  
       --定义一个游标变量c_row c_emp%ROWTYPE ,该类型为游标c_emp中的一行数据类型  
       c_row c_emp%ROWTYPE;  
BEGIN  
       FOR c_row IN c_emp LOOP  
       dbms_output.put_line(c_row.empno||'-'||c_row.ename||'-'||c_row.job||'-'||c_row.sal);  
       END LOOP;  
END;  
  
  
--2.Fetch游标--使用的时候必须要明确的打开和关闭  
DECLARE  
       CURSOR c_dept  
       IS  
       SELECT deptno,dname,loc  
       FROM dept;  
       c_row c_dept%ROWTYPE;  
BEGIN  
       --打开游标  
       OPEN c_dept;  
            LOOP  
                    --提取一行数据到c_row  
                    FETCH c_dept INTO c_row;  
                    --判读是否提取到值,没取到值就退出,取到值c_job%notfound 是false   
                    EXIT WHEN c_dept%NOTFOUND;  
                    dbms_output.put_line(c_row.deptno||'--'||c_row.dname||':'||c_row.loc);  
            END LOOP;  
            --关闭游标  
       CLOSE c_dept;  
END;  
  
--3.使用游标和while循环来显示所有部门名称  
DECLARE  
       CURSOR c_deptwhile  
       IS  
       SELECT dname  
       FROM dept;  
       c_row c_deptwhile%ROWTYPE;  
BEGIN  
       OPEN c_deptwhile;  
            FETCH c_deptwhile INTO c_row;  
            ----测试是否有数据,并执行循环  
                  WHILE c_deptwhile%FOUND LOOP  
                        dbms_output.put_line(c_row.dname);  
                        --给下一行填充数据  
                        FETCH c_deptwhile INTO c_row;  
                  END LOOP;  
       CLOSE c_deptwhile;  
END;  
  
  
--4.接收用户输入的部门编号,用for循环和游标,打印出此部门的所有员工的信息(使用循环游标)  
DECLARE  
       CURSOR c_emp(p_deptno NUMBER)  
       IS  
       SELECT ename,sal  
       FROM emp  
       WHERE deptno = p_deptno;  
       c_row c_emp%ROWTYPE;  
BEGIN  
       FOR c_row IN c_emp(20)  LOOP  
           dbms_output.put_line(c_row.ename||':'||c_row.sal);  
       END LOOP;  
END;  
  
-5:编写一个PL/SQL程序块,以提升2个资格最老的职员为MANAGER(工作时间越长,资格越老)  
--(提示:可以定义一个变量作为计数器控制游标只提取两条数据;  
--也可以在声明游标的时候把雇员中资格最老的两个人查出来放到游标中。)  
DECLARE  
       CURSOR c_emp  
       IS  
       SELECT job  
       from emp1  
       ORDER BY hiredate ASC FOR UPDATE OF job;  
       c_row c_emp%ROWTYPE;  
       c_count NUMBER:=2;  
BEGIN  
       OPEN c_emp;  
               FETCH c_emp INTO c_row;  
                     WHILE c_count>0 LOOP  
                           UPDATE emp1 SET job='MANAGER' WHERE CURRENT OF c_emp;  
                           --记得值变化及重新获取数据  
                           c_count := c_count - 1;  
                           FETCH c_emp INTO c_row;  
                     END LOOP;  
       CLOSE c_emp;  
END;  

一.使用游标
1.当在PL/SQL块中执行查询语句SELECT和数据操纵语句DML时,ORACLE会为其分配上下文区(CONTEXT AREA),游标指上下文区指针
对于数据操纵语句和单行SELECT INTO语句来说,ORACLE会为他们分配隐含游标.
使用显示游标处理多行数据,也可使用SELECT..BULK COLLECT INTO 语句处理多行数据.
1.显示游标
定义游标
cursor cursor_name is select_statement;
2.打开游标:执行对应的SELECT语句并将SELECT语句的结果暂时存放到结果集中.
open cursor_name;
3.提取数据
打开游标后,SELECT语句的结果被临时存放到游标结果集中,使用FETCH语句只能提取一行数据
通过使用FETCH..BULK COLLECT INTO语句每次可以提取多行数据
fetch cursor_name into variable1,varibale2,...;
fetch cursor_name bulk collect into collect1,collect2,...[limit rows];
4.关闭游标
close cursor_name;
9.2显示游标属性
用于返回显示游标的执行信息,包括%isopen,%found,%notfound,%rowcount
1.%isopen:确定游标是否打开 if cl%isopen then ... else open c1; end if;
2.%found:检查是否从结果集中提取到了数据
loop
fetch c1 into var1,var2;
if c2%found then ... else exit;
end loop;
3.%notfound
loop
fetch c1 into v ar1,var2;
exit when c2%notfound;
...
end loop;
4.%rowcount:返回当前行为止已经提取到的实际行数
loop
fetch c1 into my_ename,my_deptno;
if c1%rowcount>10 then
...
end if;
...
end loop;
9.3显示游标使用示例
1.在显示游标中使用fetch..into语句:只能处理一行数据,除非用循环语句
declare
cursor emp_cursor is select ename,sal from emp where deptno=10;
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
open emp_cursor;
loop
fetch emp_cursor into v_ename,v_sal;
exit when emp_cursor%notfound;
dbms_output.put_line(v_ename||': '||v_sal);
end loop;
close emp_cursor;
end;
2.在显示游标中,使用FETCH..BALK COLLECT INTO语句提取所有数据
declare
cursor emp_cursor is select ename from emp where deptno=10;
type ename_table_type is table of varchar2(10);
ename_table ename_table_type;
begin
open emp_cursor;
fetch emp_cursor bulk collect into ename_table;
for i in 1..ename_table.count loop
dbms_output.put_line(ename_table(i));
end loop;
close emp_cursor;
end;
3.在显示游标中使用FETCH..BULK COLLECT INTO ..LIMIT语句提取部分数据
declare
type name_array_type is varray(5) of varchar2(10);
name_array name_array_type;
cursor emp_cursor is select ename from emp;
rows int:=5;
v_count int:=0;
begin
open emp_cursor;
loop
fetch emp_cursor bulk collect into name_array limit rows;
dbms_output.pur('雇员名');
for i in 1..(emp_currsor%rowcount-v_count) loop
dbms_output.put(name_array(i)||' ');
end loop;
dbms_output.new_line;
v_count:=emp_cursor%rowcount;
exit when emp_cursor%notfound;
end loop;
close emp_cursor;
end;
4.使用游标属性
declare
cursor emp_cursor is select ename from emp where deptno=10;
type ename_table_type is table of varchar2(10);
ename_table ename_table_type;
begin
if not emp_cursor%isopen then
open emp_cursor;
end if;
fetch emp_cursor bulk collect into ename_table;
dbms_output.put_line('提取的总计行数:'||emp_cursor%rowcount);
close emp_cursor;
end;
5.基于游标定义记录变量
declare
cursor emp_cursor is select ename,sal from emp;
emp_record emp_cursor%rowtype;
begin
open emp_cursor;
loop
fetch emp_cursor into emp_record;
exit when emp_cursor%notfound;
dbms_output.put_line('雇员名:'||emp_record.ename||',雇员工资:'||emp_record.sal);
end loop;
end;
9.4参数游标
定义参数游标时,游标参数只能指定数据类型,而不能指定长度.
cursor cursor_name(parameter_name datatype) is select_statment;
declare
cursor emp_cursor(no number) is select ename from emp where deptno=no;
v_ename emp.ename%type;
begin
open emp_cursor(10);
loop
fetch emp_cursor into v_ename;
exit when emp_cursor%notfound;
dbms_output.put_line(v_ename);
end loop;
close emp_cursor;
end;
9.5使用游标更新或删除数据
要通过游标更新或删除数据,在定义游标时必须要带有FOR UPDATE子句
cursor cursor_name(parameter_name datetype) is select_statement for update [of column_reference] [nowait];
for update子句用于在游标结果集数据上家行共享锁,防止其他用户在相应行执行DML操作
of子句确定哪些表要加锁,没有OF子句,则在所引用的全部表上加锁
nowait子句用于指定不等待锁
必须在UPDATE后DELETE语句中引用WHERE CURRENT OF子句
update table_name set column=.. where current of cursor_name;
delete table_name where current of cursor_name;
1.使用游标更新数据
declare
cursor emp_cursor is select ename,sal from emp for update;
v_ename emp.ename%type;
v_sal emp.sal%tyep;
begin
open emp_cursor;
loop
fetch emp_cursor into v_ename,v_oldsal;
exit when emp_cursor%notfound;
if v_oldsal<2000 then
update emp set sal=sal+100 where current of emp_cursor;
end if;
end loop;
close emp_cursor;
end;
2.使用游标删除数据
declare
cursor emp_cursor is select ename,sal,deptno from emp for update;
v_ename emp.ename%type;
v_oldsal emp.sal%type;
v_deptno emp.deptno%type;
begin
open emp_cursor;
loop
fetch emp_cursor into v_ename,v_oldsal,v_deptno;
exit when emp_cursor%notfound;
if v_deptno=30 then
delete from emp where current of emp_cursor;
end if;
end loop;
close emp_cursor;
end;
3.使用OF子句在特定表上加行共享锁
declare
cursor emp_cursor is select ename,sal,dname,emp.deptno from emp,dept where emp.deptno=dept.deptno
for update of emp.deptno;
emp_record emp_cursor%type;
begin
open emp_cursor;
loop
fetch emp_cursor into emp_record;
exit when emp_cursor%notfound;
if emp_record.deptno=30 then
update emp set sal=sal+100 where current of emp_cursor;
end if;
dbms_output.put_line('雇员名:'||emp_record.ename||',工资:'||emp_record.sal||',部门名:'||emp_record.dname);
end loop;
close emp_cursor;
end;
4.使用nowait子句
通过在FOR UPDATE子句中指定NOWAIT语句,可以避免等待锁.若已经被作用行加锁,则提示错误信息
declare
cursor emp_cursor is select ename,sal from emp for update nowait;
v_ename emp.ename%type;
v_oldsal emp.sal%type;
begin
open emp_cursor;
loop
fetch emp_cursor into v_ename,v_sal;
exit when emp_cursor%notfound;
if v_oldsal<2000 then
update emp set sal=sal+100 where current of emp_cursor;
end if;
end loop;
close emp_cursor;
end;
9.6游标FOR循环
使用FOR循环时,ORACLE会隐含的打开游标,提取游标数据并关闭游标
for record_name in cursor_name loop
statement1;
statement2;
...
end loop;
每循环一次提取一次数据,在提取了所有数据后,自动退出循环并隐含的关闭游标
1.使用游标FOR循环
declare
cursor emp_cursor is select ename,sal from emp;
begin
for emp_record in emp_cursor loop
dbms_output.put_line('第'||emp_curosr%rowcount||'个雇员: '||emp_record.ename);
end loop;
end;
2.在游标FOR循环中直接使用子查询
begin
for emp_record in (select ename,sal from emp) loop
dbms_output.put_line(emp_record.ename);
end loop;
end;
9.7使用游标变量
PL/SQL的游标变量中存放着指向内存地址的指针.
1.游标变量使用步骤
包括定义游标变量,打开游标,提取游标数据,关闭游标等四个阶段
1.1定义ref cursor类型和游标变量
type ref_type_name is ref cursor [return return_type];
cursor_varibale ref_type_name;
当指定RETURN子句时,其数据类型必须是记录类型,不能在包内定义游标变量
1.2打开游标
open cursor_variable for select_statement;
1.3提取游标数据
fetch cursor_varibale into variable1,variable2,...;
fetch cursor_varibale bulk collect into collect1,collect2,...[limit rows]
1.4关闭游标变量
close cursor_varibale;
2.游标变量使用示例
2.1在定义FEF CURSOR类型时不指定RETURN子句
在打开游标时可以指定任何的SELECT语句
declare
type emp_cursor_type is ref cursor;
emp_cursor emp_cursor_type;
emp_record emp%rowtype;
begin
open emp_cursor for select * from emp where deptno=10;
loop
fetch emp_cursor into emp_record;
exit when emp_cursor%notfound;
dbms_output.put_line('第'||emp_curosr%rowcount||'个雇员: '||emp_record.ename);
end loop;
close emp_cursor;
end;
2.2在定义REF CURSOR类型时指定RETURN子句
在打开游标时SELECT语句的返回结果必须与RETURN子句所指定的记录类型相匹配.
declare
type emp_record_type is record(name varchar2(10),salary number(6,2));
type emp_cursor_type is ref cursor return emp_record_type;
emp_cursor emp_cursor_type;
emp_record emp_record_type;
begin
open emp_cursor for select ename,sal from emp where deptno=20;
loop
fetch emp_cursor into emp_record;
exit when emp_cursor%notfound;
dbms_output.put_line('第'||emp_curosr%rowcount||'个雇员: '||emp_record.ename);
end loop;
close emp_cursor;
end;
9.7使用CURSOR表达式
CURSOR表达式用于返回嵌套游标
结果集不仅可以包含普通数据,而且允许包含嵌套游标的数据
cursor(subquery)
declare
type refcursor is ref cursor;
cursor dept_cursor(no number) is select a.dname,cursor(select ename,sal from emp where deptno=a.deptno)
from dept a where a.deptno=no;
empcur refcursor;
v_dname dept.dname%type;
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
open dept_cursor(&no);
loop
fetch dept_cursor into v_danme,empcur;
exit when dept_cursor%notfound;
dbms_output.put_line('部门名:'||v_dname);
loop
fetch empcur into v_ename,v_sal;
exit when empcur%notfound;
dbms_output.put_line('雇员名:'||v_ename||',工资:'||v_sal);
end loop;
end loop;
close dept_cursor;
end;

1.V$SQL
SQL_TEXT:SQL语句的文本
 SQL_FULLTEXT:SQL语句的完全文本
 SQL_ID
 SHARABLE_MEM:游标所占共享内存
 PERSISTENT_MEM:游标持续期所占用的Fixed(固定)内存
 RUNTIME_MEM:游标在运行期所占用的Fixed(固定)内存
 SORTS:游标完成的排序次数
 LOADED_VERSIONS:游标在库缓存所占的内存堆是否被加载
 OPEN_VERSIONS:游标是否被锁定。
 USERS_OPENING:打开游标的会话数。也就是当前正在缓存游标到PGA中的会话数。游标被执行三次后,就会被缓存到PGA中。此数值就加1。
 FETCHES:抓取的次数
 EXECUTIONS:执行次数
 PX_SERVERS_EXECUTIONS:以并行方式执行的总次数
 END_OF_FETCH_COUNT:抓取全部行的次数
 USERS_EXECUTING:当前正在执行此游标的会话数
 LOADS:游标被加载或重新加载到库缓存中的次数。游标只所以被重新加载有可能是游标无效或库缓存内存不足。
 FIRST_LOAD_TIME:游标被第一次被加载的时间。也就是生成执行计划的时间
 INVALIDATIONS:游标的无效次数
 PARSE_CALLS:游标的解析次数,包括硬解析与软解析
 DISK_READS:游标执行了多少次物理读
 DIRECT_WRITES:游标直接写的次数
 BUFFER_GETS:逻辑读的次数
 APPLICATION_WAIT_TIME:应用程序的等待时间,单位微秒
 CONCURRENCY_WAIT_TIME:并行的等待时间,单位微秒
 CLUSTER_WAIT_TIME:Cluster等待时间
 USER_IO_WAIT_TIME:用户I/O等待时间
 PLSQL_EXEC_TIME:PL/SQL执行时间
 JAVA_EXEC_TIME:Java执行时间
 ROWS_PROCESSED:游标一共抓取了多少行。同样的行,每抓取一次此列都会增加
 COMMAND_TYPE:命令类型
 OPTIMIZER_MODE:优化器模式
 OPTIMIZER_COST:执行计划的成本
 OPTIMIZER_ENV:执行时的环境
 OPTIMIZER_ENV_HASH_VALUE:环境的HASH值
 PARSING_USER_ID:最先解析此游标的用户的ID
 PARSING_SCHEMA_ID:最先解析此游标的方案ID
 PARSING_SCHEMA_NAME:最先解析此游标的方案ID
 KEPT_VERSIONS:是否使用DBMS_SHARED_POOL包将游标Pin到库缓存中
 ADDRESS:父游标句柄的地址
 TYPE_CHK_HEAP:
 HASH_VALUE:游标的HASH值
 OLD_HASH_VALUE:老HASH值
 PLAN_HASH_VALUE:执行计划的HASH值。(上述三个HASH值并不相同)
 CHILD_NUMBER:子游标数量
 SERVICE:
 SERVICE_HASH
 MODULE:第一次解析游标的应用程序名。可以在应用程序中通过调用DBMS_APPLICATION_INFO.SET_MODULE设置。
 MODULE_HASH:应用程序名的HASH值
 ACTION:第一次解析时的动作名。可以在应用程序中通过调用DBMS_APPLICATION_INFO.SET_ACTION设置。
 ACTION_HASH:动作名的HASh值
 SERIALIZABLE_ABORTS:每个游标产生ORA-08177 errors错误(事务串行化无效)的次数。
 OUTLINE_CATEGORY:大纲类型
 CPU_TIME:游标解析、执行、抓取时所用的CPU时间。单位是微秒。
 ELAPSED_TIME:游标解析、执行、抓取时所用的总时间。单位是微秒。
 OUTLINE_SID:大纲会话的SID
 CHILD_ADDRESS:游标本身的地址
 SQLTYPE:游标所用的SQL语言的版本
 REMOTE:游标是否是远端映像的
 OBJECT_STATUS:对象状态
 LITERAL_HASH_VALUE:游标文本的HASH值
 LAST_LOAD_TIME:执行计划最后一次被加载到库缓存中的时间。
 IS_OBSOLETE:当子游标太多时,此子游标是否被荒废。
 CHILD_LATCH:保护游标的子闩编号
 SQL_PROFILE:SQL的概要文件
 PROGRAM_ID:过程ID
 PROGRAM_LINE#
 EXACT_MATCHING_SIGNATURE
 FORCE_MATCHING_SIGNATURE
 LAST_ACTIVE_TIME:最后一次使用执行计划的时间。
 BIND_DATA:绑定变量的信息
     这个视图中DISK_READS、BUFFER_GETS、CPU_TIME、ELAPSED_TIME这四个列在调优SQL语句时最为重要。在数据库系统的速度不是太另人满意时,如果你已经确定过了,不是其他方面的原因,而是SQL语句性能的问题,只是无法确定是那条、或那些条语句拖慢了整体的速度。那么此时选择调优物理读、逻辑读最多的,或最耗CPU时间的SQL语句进行调节,往往可以取得今人满意的性能增长。
     我们也可以以EXECUTIONS(执行次数)最多的SQL语句为调优对象。另外,PARSE_CALLS是解析次数,对于此列值最多的SQL语句,我们可以看看是否可以降低语句的解析次数。
     关于SQL调优,和程序的调优是一样的。如果我们从事过代码优化这样的工作,就会知道,对于一个大型的应用程序来说调优的方法也是要从执行次数最多的那部分代码、或从最消耗资源的代码入手。
     还有一个问题,就是文档中关于这个视图会经常提到一个概念:子游标与父游标。如果两个游标的文本一模一样,但由于环境不同,比如,游标所操作的表是不同用户下的同名表,这两个游标是不能共享执行计划的。它们都有各自的执行计划存在库缓存中。这两个游标就是子游标,Oracle还会建立一个父游标,父游标中没有执行计划,它只是文本相同但执行计划不同的所有游标的代表。
     其实在库缓存中,即使没有文本相同的子游标,Oracle会为每个游标都创建父游标。因为父游标是文本相同的子游标的代表吗,所有文本相同的游标共享同一个父游标。
     也就是说,只要你执行SQL语句,Oracle都会在库缓存中保存一父一子两个游标。如果你执行了文本相同但环境不同因而不能共享执行计划的SQL语句,那么一个父游标可能就对应多个子游标。
     父游标没有执行计划,它只有一信息管理性数据,Oracle添加它的目的就是为了管理文本相同的游标。有一个视图是专门针对父游标的,就是V$sqlarea。下面我们说一下这个视图。
 
2. V$SQLAREA
     V$SQLAREA和V$SQL的列几乎是一模一样的。在V$SQLAREA中汇总了子游标的数据。如果有两个语句:语句A和语句B,它们文本一模一样,但是由于环境不同没有共享执行计划,而是有各自的执行计划。也就是语句A和语句B是同一父游标下的子游标。在V$SQL视图中,因为它是显示子游标的,所以语句A和语句B各占一行,假设语句A的DISK_READS(物理读)是100,语句B的物理读是3000。V$SQLAREA是显示父游标信息的,语句A和语句B因为文本相同,它们两个对应同一个父游标,在V$SQLAREA中占一行。在V$SQLAREA中,语句A和语句B父游标行中的DISK_READS就是3100,也就是语句A和语句B的和。V$SQLAREA中的其它列也是如此,都是V$SQL中相应子游标的合计。
     有一个列是V$SQL中没有的,就是:VERSION_COUNT,它是对应同一父游标的子游标的数量。如果这个数字太高,可能代表由于某些原因使本可以共享执行计划的游标没有共享。
 
3.V$open_cursor与Open_cursor参数
 这个视图和参数涉及游标的打开。什么是游标的打开,就是在库缓存中,用户在软、硬解析游标时,会在游标对象的句柄上加一个锁,也就是Library cache lock。在解析并执行完游标后,这个锁并不会马上去掉,而是会一直保留着,直到用户发出了Close命令关闭游标时为止。我们在SQL*Plus命令窗口中发出的命令,在抓取完所有行后,SQL*Plus将自动为我们发出Close命令来关闭游标。
 当游标打开时,Library cache lock将一直保持,这样,即使库缓存内存紧张,需要老化对象,也不会老化这些还正在加锁的对象。因此,如果用户不停的要求数据库服务器打开游标、执行SQL,但却忘了关闭游标,这很容易耗尽共享池的内存。为此,Oracle准备了一个参数,就是Open_cursor,它的默认值在9i下是50,在10g中是300,也就是说,在10g下,每个会话最多只能同时打开300个游标。有了这个限制,就不用害怕用户不停的打开游标但又不关闭它,而耗尽共享池内存了。
     如果会话同时打开的游标数量超出了Open_cursor参数的限制,Oracle将禁止会话打开新的游标。同时报出错误:ORA-01000: 超出打开游标的最大数 。
     在用户断开会话的连接后,会话打开的这些游标将自动关闭。
     V$open_cursor视图专用来查看当前会话打开的游标信息。它只能查看当前会话打开的游标。
4.CURSOR_SHARING参数
如果应用程序中有很多类似下面这样的SQL语句:
select * from 某表 where id=1;
select * from 某表 where id=2;
select * from 某表 where id=50;
     等等,这些SQL语句严格来说是无法共享游标(也就是共享执行计划)的,但是这些语句所需要执行计划其实都是一样的。无论你在表中查询ID为1的行还是查询ID为100的行,执行方式应该是一样的。如果你想让这样的语句共享游标,那么,你可以改变Cursor_sharing参数的值。
此参有三个值:
Ÿ EXACT:这个值是默认值。除非游标文本一模一样,否则不会共享游标。
Ÿ SIMILAR:这个最智能,如果游标只有条件中的数据值部分不同,并且库缓存中原有游标的执行计划对于新执行的SQL语句也是最优的,将不再为SQL语句创建新的游标,而是让它共享库缓存中原有的游标。
Ÿ FORCE :不比较执行计划是否最优,只要游标中除了条件中的数据值部分不同外,其他部分都相同,就会共享游标。
     此参数可以在会话级修改,也就是可以使用Alter session修改它的值,这将只影响某一个会话,而不会影响其他会话。

游标的概念:
    游标是SQL的一个内存工作区,由系统或用户以变量的形式定义。游标的作用就是用于临时存储从数据库中提取的数据块。在某些情况下,需要把数据从存放在磁盘的表中调到计算机内存中进行处理,最后将处理结果显示出来或最终写回数据库。这样数据处理的速度才会提高,否则频繁的磁盘数据交换会降低效率。
游标有两种类型:显式游标和隐式游标。在前述程序中用到的SELECT...INTO...查询语句,一次只能从数据库中提取一行数据,对于这种形式的查询和DML操作,系统都会使用一个隐式游标。但是如果要提取多行数据,就要由程序员定义一个显式游标,并通过与游标有关的语句进行处理。显式游标对应一个返回结果为多行多列的SELECT语句。
游标一旦打开,数据就从数据库中传送到游标变量中,然后应用程序再从游标变量中分解出需要的数据,并进行处理。
隐式游标
如前所述,DML操作和单行SELECT语句会使用隐式游标,它们是:
* 插入操作:INSERT。
* 更新操作:UPDATE。
* 删除操作:DELETE。
* 单行查询操作:SELECT ... INTO ...。
当系统使用一个隐式游标时,可以通过隐式游标的属性来了解操作的状态和结果,进而控制程序的流程。隐式游标可以使用名字SQL来访问,但要注意,通过SQL游标名总是只能访问前一个DML操作或单行SELECT操作的游标属性。所以通常在刚刚执行完操作之后,立即使用SQL游标名来访问属性。游标的属性有四种,如下所示。
Sql代码 
隐式游标的属性 返回值类型   意    义   
SQL%ROWCOUNT    整型  代表DML语句成功执行的数据行数   
SQL%FOUND   布尔型 值为TRUE代表插入、删除、更新或单行查询操作成功   
SQL%NOTFOUND    布尔型 与SQL%FOUND属性返回值相反   
SQL%ISOPEN  布尔型 DML执行过程中为真,结束后为假  

【训练1】 使用隐式游标的属性,判断对雇员工资的修改是否成功。
步骤1:输入和运行以下程序:
Sql代码 
SET SERVEROUTPUT ON    
        BEGIN  
        UPDATE emp SET sal=sal+100 WHERE empno=1234;   
         IF SQL%FOUND THEN    
        DBMS_OUTPUT.PUT_LINE('成功修改雇员工资!');   
        COMMIT;    
        ELSE  
        DBMS_OUTPUT.PUT_LINE('修改雇员工资失败!');   
         END IF;    
        END;  

运行结果为:
Sql代码 
修改雇员工资失败!   
        PL/SQL 过程已成功完成。  

步骤2:将雇员编号1234改为7788,重新执行以上程序:
运行结果为:
Sql代码 
成功修改雇员工资!   
        PL/SQL 过程已成功完成。  

说明:本例中,通过SQL%FOUND属性判断修改是否成功,并给出相应信息。
显式游标
游标的定义和操作
游标的使用分成以下4个步骤。
1.声明游标
在DECLEAR部分按以下格式声明游标:
CURSOR 游标名[(参数1 数据类型[,参数2 数据类型...])]
IS SELECT语句;
参数是可选部分,所定义的参数可以出现在SELECT语句的WHERE子句中。如果定义了参数,则必须在打开游标时传递相应的实际参数。
SELECT语句是对表或视图的查询语句,甚至也可以是联合查询。可以带WHERE条件、ORDER BY或GROUP BY等子句,但不能使用INTO子句。在SELECT语句中可以使用在定义游标之前定义的变量。
2.打开游标
在可执行部分,按以下格式打开游标:
OPEN 游标名[(实际参数1[,实际参数2...])];
打开游标时,SELECT语句的查询结果就被传送到了游标工作区。
3.提取数据
在可执行部分,按以下格式将游标工作区中的数据取到变量中。提取操作必须在打开游标之后进行。
FETCH 游标名 INTO 变量名1[,变量名2...];

FETCH 游标名 INTO 记录变量;
游标打开后有一个指针指向数据区,FETCH语句一次返回指针所指的一行数据,要返回多行需重复执行,可以使用循环语句来实现。控制循环可以通过判断游标的属性来进行。
下面对这两种格式进行说明:
第一种格式中的变量名是用来从游标中接收数据的变量,需要事先定义。变量的个数和类型应与SELECT语句中的字段变量的个数和类型一致。
第二种格式一次将一行数据取到记录变量中,需要使用%ROWTYPE事先定义记录变量,这种形式使用起来比较方便,不必分别定义和使用多个变量。
定义记录变量的方法如下:
变量名 表名|游标名%ROWTYPE;
其中的表必须存在,游标名也必须先定义。
4.关闭游标
CLOSE 游标名;
显式游标打开后,必须显式地关闭。游标一旦关闭,游标占用的资源就被释放,游标变成无效,必须重新打开才能使用。
以下是使用显式游标的一个简单练习。
【训练1】  用游标提取emp表中7788雇员的名称和职务。
Sql代码 
SET SERVEROUTPUT ON  
        DECLARE    
         v_ename VARCHAR2(10);   
         v_job VARCHAR2(10);   
         CURSOR emp_cursor IS    
         SELECT ename,job FROM emp WHERE empno=7788;   
         BEGIN  
     OPEN emp_cursor;   
    FETCH emp_cursor INTO v_ename,v_job;   
        DBMS_OUTPUT.PUT_LINE(v_ename||','||v_job);   
        CLOSE emp_cursor;   
        END;  

执行结果为:
Sql代码 
SCOTT,ANALYST   
        PL/SQL 过程已成功完成。   

说明:该程序通过定义游标emp_cursor,提取并显示雇员7788的名称和职务。
作为对以上例子的改进,在以下训练中采用了记录变量。
【训练2】  用游标提取emp表中7788雇员的姓名、职务和工资。
Sql代码 
SET SERVEROUTPUT ON  
        DECLARE  
         CURSOR emp_cursor IS  SELECT ename,job,sal FROM emp WHERE empno=7788;   
         emp_record emp_cursor%ROWTYPE;   
        BEGIN  
OPEN emp_cursor;       
        FETCH emp_cursor INTO emp_record;   
           DBMS_OUTPUT.PUT_LINE(emp_record.ename||','|| emp_record.job||','|| emp_record.sal);   
         CLOSE emp_cursor;   
        END;  

执行结果为:
Sql代码 
SCOTT,ANALYST,3000   
        PL/SQL 过程已成功完成。   

说明:实例中使用记录变量来接收数据,记录变量由游标变量定义,需要出现在游标定义之后。
注意:可通过以下形式获得记录变量的内容:
记录变量名.字段名。
【训练3】  显示工资最高的前3名雇员的名称和工资。
Sql代码 
SET SERVEROUTPUT ON  
        DECLARE  
         V_ename VARCHAR2(10);   
        V_sal NUMBER(5);   
        CURSOR emp_cursor IS  SELECT ename,sal FROM emp ORDER BY sal DESC;   
        BEGIN  
         OPEN emp_cursor;   
         FOR I IN 1..3 LOOP   
           FETCH emp_cursor INTO v_ename,v_sal;   
         DBMS_OUTPUT.PUT_LINE(v_ename||','||v_sal);   
          END LOOP;   
         CLOSE emp_cursor;   
         END;  

执行结果为:
Sql代码 
KING,5000   
     SCOTT,3000   
     FORD,3000   
     PL/SQL 过程已成功完成。  

  说明:该程序在游标定义中使用了ORDER BY子句进行排序,并使用循环语句来提取多行数据。
游标循环
【训练1】  使用特殊的FOR循环形式显示全部雇员的编号和名称。
Sql代码 
SET SERVEROUTPUT ON  
DECLARE  
  CURSOR emp_cursor IS    
  SELECT empno, ename FROM emp;   
BEGIN  
FOR Emp_record IN emp_cursor LOOP      
    DBMS_OUTPUT.PUT_LINE(Emp_record.empno|| Emp_record.ename);   
    END LOOP;   
    END;  

执行结果为:
Sql代码 
7369SMITH   
7499ALLEN   
7521WARD   
7566JONES   
         PL/SQL 过程已成功完成。  

  说明:可以看到该循环形式非常简单,隐含了记录变量的定义、游标的打开、提取和关闭过程。Emp_record为隐含定义的记录变量,循环的执行次数与游标取得的数据的行数相一致。
【训练2】  另一种形式的游标循环。
Sql代码 
SET SERVEROUTPUT ON    
BEGIN  
 FOR re IN (SELECT ename FROM EMP)  LOOP   
  DBMS_OUTPUT.PUT_LINE(re.ename)   
 END LOOP;   
END;  

执行结果为:
Sql代码 
SMITH   
ALLEN   
WARD   
JONES  

    说明:该种形式更为简单,省略了游标的定义,游标的SELECT查询语句在循环中直接出现。
显式游标属性
虽然可以使用前面的形式获得游标数据,但是在游标定义以后使用它的一些属性来进行结构控制是一种更为灵活的方法。显式游标的属性如下所示。
Sql代码 
游标的属性   返回值类型   意    义   
%ROWCOUNT   整型  获得FETCH语句返回的数据行数   
%FOUND  布尔型 最近的FETCH语句返回一行数据则为真,否则为假   
%NOTFOUND   布尔型 与%FOUND属性返回值相反   
%ISOPEN 布尔型 游标已经打开时值为真,否则为假  

可按照以下形式取得游标的属性:
游标名%属性
要判断游标emp_cursor是否处于打开状态,可以使用属性emp_cursor%ISOPEN。如果游标已经打开,则返回值为“真”,否则为“假”。具体可参照以下的训练。
【训练1】  使用游标的属性练习。
Sql代码 
SET SERVEROUTPUT ON  
DECLARE  
  V_ename VARCHAR2(10);   
  CURSOR emp_cursor IS    
  SELECT ename FROM emp;   
BEGIN  
 OPEN emp_cursor;   
 IF emp_cursor%ISOPEN THEN  
LOOP   
   FETCH emp_cursor INTO v_ename;   
   EXIT WHEN emp_cursor%NOTFOUND;   
   DBMS_OUTPUT.PUT_LINE(to_char(emp_cursor%ROWCOUNT)||'-'||v_ename);   
  END LOOP;   
 ELSE  
  DBMS_OUTPUT.PUT_LINE('用户信息:游标没有打开!');   
 END IF;   
 CLOSE  emp_cursor;   
END;  

执行结果为:
Sql代码 
1-SMITH   
2-ALLEN   
3-WARD   
 PL/SQL 过程已成功完成。  

    说明:本例使用emp_cursor%ISOPEN判断游标是否打开;使用emp_cursor%ROWCOUNT获得到目前为止FETCH语句返回的数据行数并输出;使用循环来获取数据,在循环体中使用FETCH语句;使用emp_cursor%NOTFOUND判断FETCH语句是否成功执行,当FETCH语句失败时说明数据已经取完,退出循环。
【练习1】去掉OPEN emp_cursor;语句,重新执行以上程序。
游标参数的传递
 【训练1】  带参数的游标。
Sql代码 
SET SERVEROUTPUT ON  
        DECLARE  
            V_empno NUMBER(5);   
            V_ename VARCHAR2(10);   
            CURSOR  emp_cursor(p_deptno NUMBER,     p_job VARCHAR2) IS  
            SELECT  empno, ename FROM emp   
            WHERE   deptno = p_deptno AND job = p_job;   
BEGIN  
     OPEN emp_cursor(10, 'CLERK');   
    LOOP   
     FETCH emp_cursor INTO v_empno,v_ename;   
     EXIT WHEN emp_cursor%NOTFOUND;   
     DBMS_OUTPUT.PUT_LINE(v_empno||','||v_ename);   
      END LOOP;   
    END;  

执行结果为:
Sql代码 
7934,MILLER   
        PL/SQL 过程已成功完成。  

说明:游标emp_cursor定义了两个参数:p_deptno代表部门编号,p_job代表职务。语句OPEN emp_cursor(10, 'CLERK')传递了两个参数值给游标,即部门为10、职务为CLERK,所以游标查询的内容是部门10的职务为CLERK的雇员。循环部分用于显示查询的内容。
【练习1】修改Open语句的参数:部门号为20、职务为ANALYST,并重新执行。
也可以通过变量向游标传递参数,但变量需要先于游标定义,并在游标打开之前赋值。对以上例子重新改动如下:
  【训练2】  通过变量传递参数给游标。
Sql代码 
SET SERVEROUTPUT ON  
        DECLARE  
        v_empno NUMBER(5);   
        v_ename VARCHAR2(10);   
        v_deptno NUMBER(5);   
v_job VARCHAR2(10);   
         CURSOR emp_cursor IS  
            SELECT empno, ename FROM emp   
            WHERE   deptno = v_deptno AND job = v_job;   
        BEGIN  
         v_deptno:=10;   
         v_job:='CLERK';   
         OPEN emp_cursor;   
        LOOP   
         FETCH emp_cursor INTO v_empno,v_ename;   
           EXIT WHEN emp_cursor%NOTFOUND;   
DBMS_OUTPUT.PUT_LINE(v_empno||','||v_ename);   
         END LOOP;   
        END;  

执行结果为:
Sql代码 
7934,MILLER   
        PL/SQL 过程已成功完成。  

说明:该程序与前一程序实现相同的功能。
动态SELECT语句和动态游标的用法
Oracle支持动态SELECT语句和动态游标,动态的方法大大扩展了程序设计的能力。
对于查询结果为一行的SELECT语句,可以用动态生成查询语句字符串的方法,在程序执行阶段临时地生成并执行,语法是:
execute immediate 查询语句字符串 into 变量1[,变量2...];
以下是一个动态生成SELECT语句的例子。
【训练1】  动态SELECT查询。
Sql代码 
SET SERVEROUTPUT ON    
        DECLARE    
        str varchar2(100);   
        v_ename varchar2(10);   
        begin  
        str:='select ename from scott.emp where empno=7788';   
        execute immediate str into v_ename;    
        dbms_output.put_line(v_ename);   
        END;   

执行结果为:
Sql代码 
SCOTT   
        PL/SQL 过程已成功完成。  

说明:SELECT...INTO...语句存放在STR字符串中,通过EXECUTE语句执行。
在变量声明部分定义的游标是静态的,不能在程序运行过程中修改。虽然可以通过参数传递来取得不同的数据,但还是有很大的局限性。通过采用动态游标,可以在程序运行阶段随时生成一个查询语句作为游标。要使用动态游标需要先定义一个游标类型,然后声明一个游标变量,游标对应的查询语句可以在程序的执行过程中动态地说明。
定义游标类型的语句如下:
TYPE 游标类型名 REF CURSOR;
声明游标变量的语句如下:
游标变量名 游标类型名;
在可执行部分可以如下形式打开一个动态游标:
OPEN 游标变量名 FOR 查询语句字符串;
【训练2】  按名字中包含的字母顺序分组显示雇员信息。
输入并运行以下程序:
Sql代码 
declare    
 type cur_type is ref cursor;   
 cur cur_type;   
 rec scott.emp%rowtype;   
 str varchar2(50);   
 letter char:= 'A';   
begin  
        loop           
         str:= 'select ename from emp where ename like ''%'||letter||'%''';   
         open cur for str;   
         dbms_output.put_line('包含字母'||letter||'的名字:');   
          loop   
         fetch cur into rec.ename;   
         exit when cur%notfound;   
        dbms_output.put_line(rec.ename);   
end loop;   
  exit when letter='Z';   
  letter:=chr(ascii(letter)+1);   
 end loop;   
end;  

运行结果为:
Sql代码 
包含字母A的名字:   
ALLEN   
WARD   
MARTIN   
BLAKE   
CLARK   
ADAMS   
JAMES   
包含字母B的名字:   
BLAKE   
包含字母C的名字:   
CLARK   
SCOTT  

说明:使用了二重循环,在外循环体中,动态生成游标的SELECT语句,然后打开。通过语句letter:=chr(ascii(letter)+1)可获得字母表中的下一个字母。

异常处理
错误处理
错误处理部分位于程序的可执行部分之后,是由WHEN语句引导的多个分支构成的。错误处理的语法如下:
EXCEPTION
WHEN 错误1[OR 错误2] THEN
语句序列1;
WHEN 错误3[OR 错误4] THEN
语句序列2;
WHEN OTHERS
语句序列n;
END;
其中:
错误是在标准包中由系统预定义的标准错误,或是由用户在程序的说明部分自定义的错误,参见下一节系统预定义的错误类型。
语句序列就是不同分支的错误处理部分。
凡是出现在WHEN后面的错误都是可以捕捉到的错误,其他未被捕捉到的错误,将在WHEN OTHERS部分进行统一处理,OTHENS必须是EXCEPTION部分的最后一个错误处理分支。如要在该分支中进一步判断错误种类,可以通过使用预定义函数SQLCODE( )和SQLERRM( )来获得系统错误号和错误信息。
如果在程序的子块中发生了错误,但子块没有错误处理部分,则错误会传递到主程序中。
下面是由于查询编号错误而引起系统预定义异常的例子。
【训练1】  查询编号为1234的雇员名字。
Sql代码 
SET SERVEROUTPUT ON  
DECLARE  
v_name VARCHAR2(10);   
BEGIN  
   SELECT   ename   
   INTO     v_name   
   FROM     emp   
   WHERE    empno = 1234;   
DBMS_OUTPUT.PUT_LINE('该雇员名字为:'|| v_name);   
EXCEPTION   
  WHEN NO_DATA_FOUND THEN  
    DBMS_OUTPUT.PUT_LINE('编号错误,没有找到相应雇员!');   
  WHEN OTHERS THEN  
    DBMS_OUTPUT.PUT_LINE('发生其他错误!');   
END;  

执行结果为:
Sql代码 
编号错误,没有找到相应雇员!   
        PL/SQL 过程已成功完成。  

说明:在以上查询中,因为编号为1234的雇员不存在,所以将发生类型为“NO_DATA_
FOUND”的异常。“NO_DATA_FOUND”是系统预定义的错误类型,EXCEPTION部分下的WHEN语句将捕捉到该异常,并执行相应代码部分。在本例中,输出用户自定义的错误信息“编号错误,没有找到相应雇员!”。如果发生其他类型的错误,将执行OTHERS条件下的代码部分,显示“发生其他错误!”。
【训练2】  由程序代码显示系统错误。
Sql代码 
SET SERVEROUTPUT ON  
DECLARE  
v_temp NUMBER(5):=1;   
BEGIN  
v_temp:=v_temp/0;   
EXCEPTION   
  WHEN OTHERS THEN  
DBMS_OUTPUT.PUT_LINE('发生系统错误!');   
    DBMS_OUTPUT.PUT_LINE('错误代码:'|| SQLCODE( ));   
    DBMS_OUTPUT.PUT_LINE('错误信息:' ||SQLERRM( ));   
        END;  

执行结果为:
Sql代码 
发生系统错误!   
        错误代码:?1476   
        错误信息:ORA-01476: 除数为 0   
        PL/SQL 过程已成功完成。  

说明:程序运行中发生除零错误,由WHEN OTHERS捕捉到,执行用户自己的输出语句显示错误信息,然后正常结束。在错误处理部分使用了预定义函数SQLCODE( )和SQLERRM( )来进一步获得错误的代码和种类信息。
预定义错误
Oracle的系统错误很多,但只有一部分常见错误在标准包中予以定义。定义的错误可以在EXCEPTION部分通过标准的错误名来进行判断,并进行异常处理。常见的系统预定义异常如下所示。
Sql代码 
错 误 名 称 错误代码    错 误 含 义   
CURSOR_ALREADY_OPEN ORA_06511   试图打开已经打开的游标   
INVALID_CURSOR  ORA_01001   试图使用没有打开的游标   
DUP_VAL_ON_INDEX    ORA_00001   保存重复值到惟一索引约束的列中   
ZERO_DIVIDE ORA_01476   发生除数为零的除法错误   
INVALID_NUMBER  ORA_01722   试图对无效字符进行数值转换   
ROWTYPE_MISMATCH    ORA_06504   主变量和游标的类型不兼容   
VALUE_ERROR ORA_06502   转换、截断或算术运算发生错误   
TOO_MANY_ROWS   ORA_01422   SELECT…INTO…语句返回多于一行的数据   
NO_DATA_FOUND   ORA_01403   SELECT…INTO…语句没有数据返回   
TIMEOUT_ON_RESOURCE ORA_00051   等待资源时发生超时错误   
TRANSACTION_BACKED_OUT  ORA_00060   由于死锁,提交失败   
STORAGE_ERROR   ORA_06500   发生内存错误   
PROGRAM_ERROR   ORA_06501   发生PL/SQL内部错误   
NOT_LOGGED_ON   ORA_01012   试图操作未连接的数据库   
LOGIN_DENIED    ORA_01017   在连接时提供了无效用户名或口令  

比如,如果程序向表的主键列插入重复值,则将发生DUP_VAL_ON_INDEX错误。
如果一个系统错误没有在标准包中定义,则需要在说明部分定义,语法如下:
错误名 EXCEPTION;
定义后使用PRAGMA EXCEPTION_INIT来将一个定义的错误同一个特别的Oracle错误代码相关联,就可以同系统预定义的错误一样使用了。语法如下:
PRAGMA EXCEPTION_INIT(错误名,- 错误代码);
【训练1】  定义新的系统错误类型。
Sql代码 
SET SERVEROUTPUT ON  
        DECLARE  
        V_ENAME VARCHAR2(10);   
        NULL_INSERT_ERROR EXCEPTION;   
        PRAGMA EXCEPTION_INIT(NULL_INSERT_ERROR,-1400);   
        BEGIN  
        INSERT INTO EMP(EMPNO) VALUES(NULL);   
EXCEPTION   
WHEN NULL_INSERT_ERROR THEN  
    DBMS_OUTPUT.PUT_LINE('无法插入NULL值!');   
  WHEN OTHERS  THEN  
    DBMS_OUTPUT.PUT_LINE('发生其他系统错误!');   
END;  

执行结果为:
Sql代码 
无法插入NULL值!   
        PL/SQL 过程已成功完成。  

  说明:NULL_INSERT_ERROR是自定义异常,同系统错误1400相关联。
自定义异常
程序设计者可以利用引发异常的机制来进行程序设计,自己定义异常类型。可以在声明部分定义新的异常类型,定义的语法是:
错误名 EXCEPTION;
用户定义的错误不能由系统来触发,必须由程序显式地触发,触发的语法是:
RAISE 错误名;
RAISE也可以用来引发模拟系统错误,比如,RAISE ZERO_DIVIDE将引发模拟的除零错误。
使用RAISE_APPLICATION_ERROR函数也可以引发异常。该函数要传递两个参数,第一个是用户自定义的错误编号,第二个参数是用户自定义的错误信息。使用该函数引发的异常的编号应该在20 000和20 999之间选择。
自定义异常处理错误的方式同前。
【训练1】  插入新雇员,限定插入雇员的编号在7000~8000之间。
Java代码 
SET SERVEROUTPUT ON   
DECLARE   
new_no NUMBER(10);   
new_excp1 EXCEPTION;   
new_excp2 EXCEPTION;   
BEGIN   
new_no:=6789;   
INSERT INTO emp(empno,ename)   
  VALUES(new_no, '小郑');   
  IF new_no<7000 THEN   
    RAISE new_excp1;   
  END IF;   
  IF new_no>8000 THEN   
    RAISE new_excp2;   
  END IF;   
  COMMIT;   
EXCEPTION   
WHEN new_excp1  THEN   
    ROLLBACK;   
    DBMS_OUTPUT.PUT_LINE('雇员编号小于7000的下限!');   
    WHEN new_excp2  THEN   
    ROLLBACK;   
    DBMS_OUTPUT.PUT_LINE('雇员编号超过8000的上限!');   
    END;  

执行结果为:
雇员编号小于7000的下限!
PL/SQL 过程已成功完成。
说明:在此例中,自定义了两个异常:new_excp1和new_excp2,分别代表编号小于7000和编号大于8000的错误。在程序中通过判断编号大小,产生对应的异常,并在异常处理部分回退插入操作,然后显示相应的错误信息。
【训练2】  使用RAISE_APPLICATION_ERROR函数引发系统异常。
Sql代码 
SET SERVEROUTPUT ON  
DECLARE  
New_no NUMBER(10);   
BEGIN  
  New_no:=6789;   
 INSERT INTO    emp(empno,ename)   
  VALUES(new_no, 'JAMES');   
IF new_no<7000 THEN  
    ROLLBACK;   
    RAISE_APPLICATION_ERROR(-20001, '编号小于7000的下限!');   
  END IF;   
  IF new_no>8000 THEN  
    ROLLBACK;   
    RAISE_APPLICATION_ERROR (-20002, '编号大于8000的下限!');   
  END IF;   
END;  

执行结果为:
Sql代码 
DECLARE  
        *   
        ERROR 位于第 1 行:   
        ORA-20001: 编号小于7000的下限!   
        ORA-06512: 在line 9  

  说明:在本训练中,使用RAISE_APPLICATION_ERROR引发自定义异常,并以系统错误的方式进行显示。错误编号为20001和20002。
注意:同上一个训练比较,此种方法不需要事先定义异常,可直接引发。
可以参考下面的程序片断将出错信息记录到表中,其中,errors为记录错误信息的表,SQLCODE为发生异常的错误编号,SQLERRM为发生异常的错误信息。
DECLARE
  v_error_code      NUMBER;
  v_error_message   VARCHAR2(255);
BEGIN
...
EXCEPTION
...
WHEN OTHERS THEN
    v_error_code := SQLCODE ;
    v_error_message := SQLERRM ;
    INSERT INTO errors
    VALUES(v_error_code, v_error_message);
END;
  【练习1】修改雇员的工资,通过引发异常控制修改范围在600~6000之间。
阶段训练
【训练1】  将雇员从一个表复制到另一个表。
步骤1:创建一个结构同EMP表一样的新表EMP1:
CREATE TABLE emp1 AS SELECT * FROM SCOTT.EMP WHERE 1=2;
步骤2:通过指定雇员编号,将雇员由EMP表移动到EMP1表:
Sql代码 
SET SERVEROUTPUT ON    
DECLARE  
v_empno NUMBER(5):=7788;   
emp_rec emp%ROWTYPE;   
BEGIN  
 SELECT * INTO emp_rec FROM emp WHERE empno=v_empno;   
 DELETE FROM emp WHERE empno=v_empno;   
INSERT INTO emp1 VALUES emp_rec;   
 IF SQL%FOUND THEN  
  COMMIT;   
  DBMS_OUTPUT.PUT_LINE('雇员复制成功!');   
 ELSE    
  ROLLBACK;   
  DBMS_OUTPUT.PUT_LINE('雇员复制失败!');   
 END IF;   
END;  

执行结果为:
雇员复制成功!
PL/SQL 过程已成功完成。
步骤2:显示复制结果:
SELECT empno,ename,job FROM emp1;
执行结果为:
 
Sql代码 
EMPNO ENAME      JOB   
------------- -------------- ----------------   
    7788  SCOTT      ANALYST  

说明:emp_rec变量是根据emp表定义的记录变量,SELECT...INTO...语句将整个记录传给该变量。INSERT语句将整个记录变量插入emp1表,如果插入成功(SQL%FOUND为真),则提交事务,否则回滚撤销事务。试修改雇员编号为7902,重新执行以上程序。
【训练2】  输出雇员工资,雇员工资用不同高度的*表示。
输入并执行以下程序:
Sql代码 
SET SERVEROUTPUT ON    
BEGIN  
 FOR re IN (SELECT ename,sal FROM EMP)  LOOP   
  DBMS_OUTPUT.PUT_LINE(rpad(re.ename,12,' ')||rpad('*',re.sal/100,'*'));   
 END LOOP;   
END;  

输出结果为:
Sql代码 
SMITH       ********   
ALLEN           ****************   
WARD        *************   
JONES           ******************************   
MARTIN      *************   
BLAKE       *****************************   
CLARK           *****************************   
SCOTT           ******************************   
KING            **************************************************   
TURNER      ***************   
ADAMS       ***********   
JAMES           **********   
FORD            ******************************   
MILLER          *************   
         执行结果为:   
        PL/SQL 过程已成功完成。  

  说明:第一个rpad函数产生对齐效果,第二个rpad函数根据工资额产生不同数目的*。该程序采用了隐式的简略游标循环形式。
【训练3】  编写程序,格式化输出部门信息。
输入并执行如下程序:
Sql代码 
SET SERVEROUTPUT ON    
        DECLARE  
         v_count number:=0;   
         CURSOR dept_cursor IS SELECT * FROM dept;   
        BEGIN  
          DBMS_OUTPUT.PUT_LINE('部门列表');   
DBMS_OUTPUT.PUT_LINE('---------------------------------');   
         FOR Dept_record IN dept_cursor LOOP      
         DBMS_OUTPUT.PUT_LINE('部门编号:'|| Dept_record.deptno);   
         DBMS_OUTPUT.PUT_LINE('部门名称:'|| Dept_record.dname);   
            DBMS_OUTPUT.PUT_LINE('所在城市:'|| Dept_record.loc);   
DBMS_OUTPUT.PUT_LINE('---------------------------------');   
      v_count:= v_count+1;   
        END LOOP;   
         DBMS_OUTPUT.PUT_LINE('共有'||to_char(v_count)||'个部门!');   
        END;  

输出结果为:
Sql代码 
部门列表   
------------------------------------   
部门编号:10   
部门名称:ACCOUNTING   
所在城市:NEW YORK   
------------------------------------   
部门编号:20   
部门名称:RESEARCH   
所在城市:DALLAS   
...   
共有4个部门!   
PL/SQL 过程已成功完成。  

  说明:该程序中将字段内容垂直排列。V_count变量记录循环次数,即部门个数。
【训练4】  已知每个部门有一个经理,编写程序,统计输出部门名称、部门总人数、总工资和部门经理。
输入并执行如下程序:
Sql代码 
SET SERVEROUTPUT ON    
DECLARE  
 v_deptno number(8);   
 v_count number(3);   
 v_sumsal number(6);   
 v_dname  varchar2(15);   
v_manager  varchar2(15);   
 CURSOR list_cursor IS  
   SELECT deptno,count(*),sum(sal) FROM emp group by deptno;   
BEGIN  
  OPEN list_cursor;    
  DBMS_OUTPUT.PUT_LINE('----------- 部 门 统 计 表 -----------');   
DBMS_OUTPUT.PUT_LINE('部门名称   总人数  总工资   部门经理');   
  FETCH list_cursor INTO v_deptno,v_count,v_sumsal;    
  WHILE list_cursor%found LOOP     
 SELECT dname INTO v_dname FROM dept   
    WHERE deptno=v_deptno;   
    SELECT ename INTO v_manager FROM emp    
    WHERE deptno=v_deptno and job='MANAGER';   
DBMS_OUTPUT.PUT_LINE(rpad(v_dname,13)||rpad(to_char(v_count),8)   
      ||rpad(to_char(v_sumsal),9)||v_manager);   
    FETCH list_cursor INTO v_deptno,v_count,v_sumsal;    
    END LOOP;   
        DBMS_OUTPUT.PUT_LINE('--------------------------------------');   
        CLOSE list_cursor;   
        END;  

输出结果为:
Sql代码 
-------------------- 部 门 统 计 表 -----------------   
        部门名称     总人数  总工资     部门经理   
        ACCOUNTING    3      8750       CLARK   
        RESEARCH      5     10875       JONES   
        SALES             6      9400       BLAKE   
        -------------------------------------------------------------   
        PL/SQL 过程已成功完成。   

说明:游标中使用到了起分组功能的SELECT语句,统计出各部门的总人数和总工资。再根据部门编号和职务找到部门的经理。该程序假定每个部门有一个经理。
【训练5】  为雇员增加工资,从工资低的雇员开始,为每个人增加原工资的10%,限定所增加的工资总额为800元,显示增加工资的人数和余额。
输入并调试以下程序:
Sql代码 
SET SERVEROUTPUT ON    
DECLARE    
  V_NAME CHAR(10);   
  V_EMPNO NUMBER(5);   
  V_SAL NUMBER(8);   
  V_SAL1 NUMBER(8);   
  V_TOTAL NUMBER(8) := 800;     --增加工资的总额   
V_NUM NUMBER(5):=0;     --增加工资的人数   
         CURSOR emp_cursor IS    
          SELECT EMPNO,ENAME,SAL FROM EMP ORDER BY SAL ASC;   
        BEGIN  
         OPEN emp_cursor;   
        DBMS_OUTPUT.PUT_LINE('姓名      原工资  新工资');    
        DBMS_OUTPUT.PUT_LINE('---------------------------');    
         LOOP   
            FETCH emp_cursor INTO V_EMPNO,V_NAME,V_SAL;   
EXIT WHEN emp_cursor%NOTFOUND;   
         V_SAL1:= V_SAL*0.1;   
            IF V_TOTAL>V_SAL1 THEN  
            V_TOTAL := V_TOTAL - V_SAL1;   
            V_NUM:=V_NUM+1;   
    DBMS_OUTPUT.PUT_LINE(V_NAME||TO_CHAR(V_SAL,'99999')||   
        TO_CHAR(V_SAL+V_SAL1,'99999'));   
             UPDATE EMP SET SAL=SAL+V_SAL1   
             WHERE EMPNO=V_EMPNO;   
         ELSE  
DBMS_OUTPUT.PUT_LINE(V_NAME||TO_CHAR(V_SAL,'99999')||TO_CHAR(V_SAL,'99999'));   
         END IF;   
        END LOOP;   
        DBMS_OUTPUT.PUT_LINE('---------------------------');   
        DBMS_OUTPUT.PUT_LINE('增加工资人数:'||V_NUM||' 剩余工资:'||V_TOTAL);     
         CLOSE emp_cursor;    
         COMMIT;   
         END;  

输出结果为:
Sql代码 
姓名        原工资  新工资   
        ---------------------------------------------   
SMITH       1289   1418   
JAMES       1531   1684   
MARTIN      1664   1830   
MILLER          1730   1903   
ALLEN           1760   1936   
ADAMS       1771   1771   
TURNER      1815   1815   
WARD        1830   1830   
BLAKE       2850   2850   
CLARK       2850   2850   
JONES           2975   2975   
FORD            3000   3000   
KING            5000   5000   
-----------------------------------------------   
增加工资人数:5 剩余工资:3   
PL/SQL 过程已成功完成。  

【练习1】按部门编号从小到大的顺序输出雇员名字、工资以及工资与平均工资的差。
【练习2】为所有雇员增加工资,工资在1000以内的增加30%,工资在1000~2000之间的增加20%,2000以上的增加10%。


declare
  -- Local variables here
  CURSOR C_EVENT is  select t.eventname from T_Event t;
  temp T_Event.EVENTNAME%TYPE;
begin
  -- for 遍历游标
  for temp in C_EVENT
  Loop
      dbms_output.put_line('事件名称:'|| temp.EVENTNAME);
  End Loop;
 
  dbms_output.put_line('------------------分割线--------------');
 
  --while 遍历游标
  OPEN C_EVENT;--打开游标
  FETCH C_EVENT into temp; --取值
  while C_EVENT%found 
  Loop
    dbms_output.put_line('事件名称'||temp); --打印
    FETCH C_EVENT into temp; --取值
  End Loop;
  CLOSE C_EVENT;
 
  dbms_output.put_line('------------------分割线--------------');
  --loog 遍历游标
  OPEN C_EVENT;--打开游标
  Loop
      FETCH C_EVENT into temp; --取值
      Exit when C_EVENT%NOTFOUND;
      dbms_output.put_line('事件名称'||temp); --打印
  End Loop;
  CLOSE C_EVENT;
end;

注意:必须先fetch,再用游标的notfound来判断

---带入参的游标,注意只指定入参类型,不指定入参长度,且只有入参,无出参

-- Created on 2011-10-9 by HUCHANGKUN
declare
  -- Local variables here
  CURSOR C_EVENT(ROW_NUM NUMBER DEFAULT 3) is  (select t.eventname from T_Event t where rownum <ROW_NUM);
  temp T_Event.EVENTNAME%TYPE;
begin
  -- for 遍历游标
  for temp in C_EVENT(ROW_NUM=>2)
  Loop
      dbms_output.put_line('事件名称:'|| temp.EVENTNAME);
      dbms_output.put_line('index: '||C_EVENT%ROWCOUNT);
  End Loop;
 
  dbms_output.put_line('------------------分割线--------------');
 
  --while 遍历游标
  OPEN C_EVENT(ROW_NUM=>3);--打开游标
  FETCH C_EVENT into temp; --取值
  while C_EVENT%found 
  Loop
    dbms_output.put_line('事件名称:'||temp); --打印
    dbms_output.put_line('index: '||C_EVENT%ROWCOUNT);
    FETCH C_EVENT into temp; --取值
  End Loop;
  CLOSE C_EVENT;
 
  dbms_output.put_line('------------------分割线--------------');
  --loog 遍历游标
  OPEN C_EVENT(ROW_NUM=>4);--打开游标
  Loop
      FETCH C_EVENT into temp; --取值
      Exit when C_EVENT%NOTFOUND;
      dbms_output.put_line('事件名称:'||temp); --打印
      dbms_output.put_line('index: '||C_EVENT%ROWCOUNT);
  End Loop;
  CLOSE C_EVENT;
end;


注意捕获异常:
dbms_output.put_line(sqlcode||' '||sqlerrm);
sqlcode是错误代码,sqlerrm是错误信息,不是sql语句本身,我要的是发生错误的sql语句


exception
  when others then
  dbms_output.put_line(sqlcode||' '||sqlerrm);

1、直接定义多个显示游标
CREATE OR REPLACE PROCEDURE ACC.DBP_REALCITYTRAFFICCNT IS
CURSOR cur1 IS SELECT 。。。 --第一个游标

CURSOR cur2 IS SELECT 。。。 --第二个游标

BEGIN


--调用cur1
BEGIN
OPEN cur1 ;  
  LOOP
  FETCH cur1 INTO 。。。
  EXIT WHEN cur1%NOTFOUND;  
  ...业务逻辑
  COMMIT;

  END LOOP;
  CLOSE cur1;

END;

--调用cur2
BEGIN
  OPEN cur2 ;  
  LOOP
  FETCH cur2 INTO 。。。
  EXIT WHEN cur2%NOTFOUND;  
  ...业务逻辑
  COMMIT;

  END LOOP;
  CLOSE cur2;

end;
END DBP_REALCITYTRAFFICCNT;


--------------------------
2、通过定义ref游标来实现
TYPE refcur_t IS REF CURSOR; --声明REF游标类型

cur1 refcur_t; --声明第一个游标REF游标类型的变量

cur2 refcur_t; --声明第二个游标REF游标类型的变量

BEGIN


--调用cur1
BEGIN
OPEN cur1 ;  
  LOOP
  FETCH cur1 INTO 。。。
  EXIT WHEN cur1%NOTFOUND;  
  ...业务逻辑
  COMMIT;

  END LOOP;
  CLOSE cur1;

END;

--调用cur2
BEGIN
  OPEN cur2 ;  
  LOOP
  FETCH cur2 INTO 。。。
  EXIT WHEN cur2%NOTFOUND;  
  ...业务逻辑
  COMMIT;

  END LOOP;
  CLOSE cur2;

end;
END DBP_REALCITYTRAFFICCNT;


完成批量修改user_tables中的所有表的栏位名(从MS SQL导入过来,发现大小写问题,造成很多麻烦)
存储过程见下:
-- Created on 2012/3/14 by FREE
declare
  -- Local variables here
  Cursor tbl_cur is select table_name from user_tables;
  --col_cur Cursor;
  i integer;
  tbl_name varchar2(50);
  col_name varchar2(50);
begin
  -- Test statements here
  for tbl_name in tbl_cur LOOP
      for col_cur in (select COLUMN_NAME from all_tab_columns where table_name=tbl_name.table_name) LOOP
          dbms_output.put_line('alter table '||tbl_name.table_name||' rename column "'||col_cur.column_name||'" to '||col_cur.column_name);
      end LOOP;
  end LOOP;
end;










PL/SQL语句通过游标提供了对一个结果集进行逐行处理的能力。游标可以视为一种特殊的指针,它与某个查询结果相联系,可以指向结果集的任何位置,以便对指定位置的数据进行处理。使用游标可以在查询数据的同时对数据进行处理。游标分为显式游标和隐式游标两种。

一、显式游标
使用显示游标需要遵循声明游标——>打开游标——>读取数据——>关闭游标四个步骤。
1、声明游标
定义的方法如下:
 
Sql代码  
declare cursor cursorName is select_statement  
 
其中cursorName是游标名,select_statement是select语句,它是由该查询产生与所声明的游标像关联的结果集。
例如,以下是一个游标的定义实例:
 
Sql代码  
declare cursor moduleCursor is select name,parent from t_module;  
 

2、打开游标
声明游标后,在使用数据前,必须先打开游标。在PL/SQL中使用open语句打开游标,其格式为:
open cursorName
其中,cursorName是需要打开的游标的名字,打开游标后,可以使用系统变量%rowcount查看游标当前所在的位置。
示例,定义游标moduleCursor,然后打开游标,输出其当前所在的位置。
 
Sql代码  
declare cursor moduleCursor is   
    select name,parent from t_module;  
begin  
    open moduleCursor;  
    dbms_output.put_line(moduleCursor%rowcount);  
end;  
 
3、读取数据
打开游标后,就可以使用fetch语句从中读取数据了。fetch语句的语法格式为:
fetch cursorName [into variable_name,...n]
其中,cursorName表示从中提取数据的游标名,into表示将提取的数据存放到变量variable_name中去。
示例代码:
 
Sql代码  
declare  
  moduleName char(20);  
  moduleId number(3);  
  cursor moduleCursor is   
  select id, name from t_module;  
begin  
  open moduleCursor;  
  dbms_output.put_line(moduleCursor%ROWCOUNT);  
  fetch moduleCursor into moduleId, moduleName; --在进行found操作之前必须先进行一次fetch操作,否则游标不会指向结果集的开始,found操作永远返回false。  
  while moduleCursor%found  
  loop  
    dbms_output.put_line('ModuleName: ' || moduleName || ' , ModuleId: ' || moduleId);  
    fetch moduleCursor into moduleId, moduleName;  
  end loop;  
  close moduleCursor;  
end;  
 
在读取游标的数据的时候,也可以使用游标的for循环进行读取,把上述示例用for循环可以重写如下:
 
Sql代码  
declare   
    moduleId number(3);  
    moduleName char(20);  
    cursor moduleCursor is  
        select id, name from t_module;  
begin  
    for moduleCursorRecord in moduleCursor loop --moduleCursorRecord是一个临时变量,代表当前的一条记录  
        moduleId := moduleCursorRecord.id;  
        moduleName := moduleCursorRecord.name;  
        dbms_output.put_line('ModuleId:' || moduleId || ', ModuleName:' || moduleName);  
    end loop;  
end;  
 
有上述代码可知,在对游标进行循环取数据时,使用for循环可以大大减少代码量,增强代码的可读性。此外,使用for循环的时候,不需要显式的打开和关闭游标。

4、关闭游标
游标使用完以后,要及时关闭,释放所占的内存区。关闭游标使用close语句,格式如下:
close cursorName;

5、几点需要注意的地方
(1)用%found和%notfound检验游标成功与否。该属性表示当前的游标是否指向有效的一行,根据其返回值true或者false检查是否该结束游标的使用。如果游标不是指向结果集的末尾,则返回成功,在第一次fetch的时候,游标指向结果集的最前端,如果此时结果集中含记录,则%found将返回true,下一次执行fetch操作将使游标指向下一条记录的前端,如果该记录存在则%found将返回true,否则返回false。该测试必须在游标关闭前进行。
(2)游标目标变量必须与游标select表中的列的数据类型一致。
(3)如果试图打开一个已打开或者关闭一个已关闭的游标时将会出现错误。因此,在进行打开或者关闭操作前,如果不知道游标的当前状态,可以使用%isopen进行检查,再根据其返回值执行相应的打开或者关闭操作。
(4)可以使用%rowcount获取当前游标所在的位置。


二、隐式游标
如果在PL/SQL程序段中使用select语句进行操作,PL/SQL语言会隐含地处理游标定义,这就是隐式游标。这种游标不需要像显式游标一样的声明,也不需要打开和关闭。
以下是一段在方法中使用了隐式游标的说明:
 
Sql代码  
create or replace function moduleNameFunc(moduleId in number)  
return char  
as  
moduleName char(20);  
begin  
  select name into moduleName from t_module where id = moduleId;  
  return moduleName;  
end;  
 

使用隐式游标需要注意以下几点:
(1)每一个隐式游标必须有一个into语句。
(2)into语句后接收数据的变量的数据类型要与对应的列的数据类型一致。
(3)隐式游标一次只能返回一条数据。

创建存储过程,需要有CREATE PROCEDURE或CREATE ANY PROCEDURE的系统权限。该权限可由系统管理员授予。创建一个存储过程的基本语句如下:
CREATE [OR REPLACE] PROCEDURE 存储过程名
   [(参数[IN|OUT|IN OUT]数据类型…)]
   {AS|IS}
      [说明部分]
   BEGIN
      可执行部分
   [EXCEPTION
      错误处理部分]
   END [过程名];
其中:
    可选关键字OR REPLACE表示如果存储过程已经存在,则用新的存储过程覆盖,通常用于存储过程的重建。
    参数部分用于定义多个参数(如果没有参数,就可以省略)。参数有三种形式:IN、OUT和IN OUT。如果没有指明参数的形式,则默认为IN。
    关键字AS也可以写成IS,后跟过程的说明部分,可以在此定义过程的局部变量。
    编写存储过程可以使用任何文本编辑器或直接在SQL *Plus环境下进行,编写好的存储过程必须要在SQL *Plus环境下进行编译,生成编译代码,原代码和编译代码在编译过程中都会被存入数据库。编译成功的存储过程就可以在Oracle环境下进行调用了。
    一个存储过程在不需要时可以删除。删除存储过程的人是过程的创建者或者拥有DROP ANY PROCEDURE系统权限的人。删除存储过程的语法如下:
DROP PROCEDURE 存储过程名;
    如果要重新编译一个存储过程,则只能是过程的创建者或者拥有ALTER ANY PROCEDURE系统权限的人。语法如下:
ALTER PROCEDURE 存储过程名 COMPILE;
    执行(或调用)存储过程的人是过程的创建者或是拥有EXECUTE ANY PROCEDURE系统权限的人或是被拥有者授予EXECUTE权限的人。执行的方法如下:
    方法1:
EXECUTE 模式名.存储过程名[(参数…)];
    方法2:
BEGIN
   模式名.存储过程名 [(参数…)];
END;
    传递的参数必须与定义的参数类型、个数和顺序一致(如果参数定义了默认值,则调用时可以省略参数)。参数可以是变量、常量或表达式,用法参见下一节。
    如果是调用本账户下的存储过程,则模式名可以省略。要调用其他账户编写的存储过程,则模式名必须要添加。
下面是我自己的实验:
注:游标可以“显式”声明,也可以“隐式”声明。“隐式”声明的意思是,不用声明游标,在for循环中直接用游标的record就行。例一是内外层循环游标都是“隐式”声明的情况(内层循环的游标,需要用到外层循环的游标值,所以不能两个游标都在第一个循环外声明)。(一般:游标名是xxx__cursor,游标中的每行记录名是xxx_record 。并且,内层循环要用游标,内层的游标应该要用“隐式”声明,外层游标可用可不用。)
例一:
CREATE OR REPLACE PROCEDUREmain_mx
           AS
            BEGIN
             FOR mainout_recordIN(  SELECT  /*这里的mainout_record直接是游标的每行记录了。另外IN后面如果是select语句,就要加括号,并且select语句结束不用;结尾,IN后面如果是游标名,就不要括号*/ ID,smscontent,allcode,cjr,pretongdaoid,clientid,shr,pretime,cjsj,shstatus,shsj
from t_busi_main_presend
where shstatus in('1','9') and klstatus='2') LOOP
             /* insert into T_BUSI_PRESEND_MX*/
                /*  CURSOR mainmobile_cursor IS
                       SELECT  * FROM TABLE(fn_split(mainout_record.allcode,','));  */
                
                    FOR mainmobile_record IN (SELECT  column_value cv FROM TABLE(fn_split(mainout_record.allcode,',')))  LOOP /*SELECT  column_value cv FROM TABLE(fn_split(mainout_record.allcode,','),这句话是执行fn_split这个函数,函数功能类似java中的split功能,但是oracle本身没有这个函数,所以要自己写,代码是我在网上拷的,注意因为mainout_record.allcode这个记录集就只有“一列”记录,allcode是外层游标查出来的一个列名,所以前面是selectcolumn_value,后面打印值的时候也是写.column_value:
功能描述:用指定分隔符切割输入的字符串,返回一维数组,每个数组元素为一个子串。
先:
CREATE OR REPLACE TYPE ty_str_split IS TABLE OF VARCHAR2 (4000);
再:
CREATE OR REPLACE FUNCTION fn_split(p_str IN CLOB, p_delimiter IN VARCHAR2)
RETURN ty_str_split
IS
  j INT := 0;
  i INT := 1;
  len INT := 0;
  len1 INT := 0;
  str VARCHAR2 (4000);
  str_split ty_str_split := ty_str_split ();
BEGIN
  len := LENGTH (p_str);
  len1 := LENGTH (p_delimiter);
  WHILE j < len
  LOOP
    j := INSTR (p_str, p_delimiter, i);
    IF j = 0
    THEN
        j := len;
        str := SUBSTR (p_str, i);
        str_split.EXTEND;
        str_split (str_split.COUNT) := str;
        IF i >= len
        THEN
          EXIT;
        END IF;
    ELSE
        str := SUBSTR (p_str, i, j - i);
        i := j + len1;
        str_split.EXTEND;
        str_split (str_split.COUNT) := str;
    END IF;
  END LOOP;
  RETURN str_split;
END fn_split;
*/
                         DBMS_OUTPUT.PUT_LINE(mainmobile_record.cv);/*打印结果的时候,直接反键存储过程名-->测试有时是出不来结果的,这时应该"新建"-->"命令窗口"--SQL>set serveroutput on    -->execute main_mx(这是存储过程名),这样才能打印结果 */
                      END LOOP;
              END LOOP;    
           END;
例二(外层for循环游标是“显式”声明):
CREATE OR REPLACE PROCEDUREmain_mx_1
       as
           CURSOR mainout_cursor IS SELECT ID,smscontent,allcode,cjr,pretongdaoid,clientid,shr,pretime,cjsj,shstatus,shsj from t_busi_main_presend where shstatus in('1','9') and klstatus='2';
            begin
             FOR mainout_record IN  mainout_cursor LOOP
             /*    insert into T_BUSI_PRESEND_MX*/
               /*  CURSOR mainmobile_cursor IS
                       SELECT  * FROM TABLE(fn_split(mainout_record.allcode,','));
                 BEGIN*/
                    FOR mainmobile_record IN (SELECT  column_value cv FROM TABLE(fn_split(mainout_record.allcode,',')))  LOOP
                          DBMS_OUTPUT.PUT_LINE(mainmobile_record.cv);
                      END LOOP;
               END LOOP;
           END;

 
1、  PL/SQL语句块

PL/SQL语句块只适用于Oracle数据库,使用时临时保存在客户端,而不是保存在数据库。

基本语法:
Sql代码  
declare  
  变量声明、初始化  
begin  
  业务处理、逻辑代码  
exception  
  异常捕获  
end;  
 
变量声明:<变量名>  <类型及长度>  [:=<初始值>]
            例:v_name varchar2(20):=’张三’;

2、  循环语句

loop循环语法:
Sql代码  
loop  
  exit  when  表达式  
end loop;  
 
while循环语法:
Sql代码  
while 表达式  
  loop  
  end loop;  
 
for循环语法:
Sql代码  
for  <变量>  in  <变量取值范围(小值..大值,如1..100)> loop  
end loop;  
 
    for循环的变量可不做声明及初始化。

3、  if判断语句

基本语法:
Sql代码  
if  <表达式>  then  
…  
else  if  <表达式>  then  
…  
else  
…  
end  if;  
end  if;  
 
例:
Sql代码  
declare  
  v_identity number(4):=0;  
begin  
  loop  
    if v_identity=1 then  
      dbms_output.put_line('v_identity=1');  
    else if v_identity=3 then  
      dbms_output.put_line('v_identity=3');  
    else if v_identity=6 then  
      exit;  
    else  
      dbms_output.put_line('v_identity is not 1 or 3');  
    end if;  
    end if;  
    end if; -- 注意,有多少个if就要有多少个end if结束标志。  
  
    v_identity:=v_identity+1;  
  
  end loop;  
  
exception  
  when others then dbms_output.put_line('error!');  
end;  
 

4、  分支case

基本语法:
Sql代码  
case  <变量>  
when  常量  then  
…  
when  常量  then  
…  
else  
      …  
end case;  
 
例:
Sql代码  
declare  
  v_number number(4):=3;  
  v_string varchar(20):='abc';  
begin  
  case v_number  
    when 1 then  
      dbms_output.put_line('v_number is '||1);  
    when 2 then  
      dbms_output.put_line('v_number is '||2);  
    when 3 then  
      dbms_output.put_line('v_number is '||3);  
  
  end case;  
  
  case v_string  
    when 'ab' then  
      dbms_output.put_line('v_string is '||'ab');  
    when 'bc' then  
      dbms_output.put_line('v_string is '||'bc');  
    else -- 缺省匹配  
      dbms_output.put_line('v_string is other value');  
  end case;  
  
exception  
  when others then dbms_output.put_line('error!');  
end;  
 
5、  异常(exception)

声明异常语法:<异常名>  exception;
抛出异常语法:raise  <异常名>;
捕获异常语法:when  <异常名>  then  异常处理语句;

例:
Sql代码  
declare  
  v_input varchar2(1):='&throw';-- 动态输入  
  v_exception_1 exception; -- 自定义异常  
  v_exception_2 exception;  
  others exception; -- 系统异常  
  
begin  
  if v_input='1' then  
    raise v_exception_1; -- 抛出异常  
  else if v_input='2' then  
    raise v_exception_2;  
  else  
    raise others;  
  end if;  
  end if;  
  
exception  
  -- 捕获异常  
  
  when v_exception_1 then dbms_output.put_line('throw exception: v_exception_1');  
  when v_exception_2 then dbms_output.put_line('throw exception: v_exception_2');  
  when others then dbms_output.put_line('throw exception: others');  
  
end;  
 
6、  游标(cursor)

声明游标语法:cursor  <游标名>  is  select语句;
声明ref游标语法:<游标名>  is  ref  cursor;
打开游标语法:open  <游标名>;
移动游标并获取数据语法:fetch  <游标名>  into  <用于保存读取的数据的变量的名>;
关闭游标语法:close  <游标名>;
游标属性(游标的属性必须在关闭游标之前):

 %isopen: 判断游标是否打开
 %notfound: 找不到数据时
 %found:
 %rowcount: 返回当前游标已扫描的数据行数量

游标分类:1、显示游标(自定义游标);2、隐示游标(系统游标);3、REF游标

例:
Sql代码  
declare  
  v_row test%rowtype; -- 匹配t_test表中一行所有的数据类型  
  cursor v_cur is  
    select * from test;-- 声明游标  
begin  
  open v_cur;-- 打开游标  
  loop  
    fetch v_cur into v_row;-- 将游标所在行的数据转存到v_row中  
    exit when v_cur%notfound; -- 当游标到最后一行时跳出  
    dbms_output.put_line('id = '||v_row.t_id||'  name = '||v_row.t_name||'  msg = '||v_row.t_msg);  
  end loop;  
  close v_cur;-- 关闭游标  
exception  
  when others then dbms_output.put_line('throw exception: others');  
end;  
 
-- REF游标 --
Sql代码  
create or replace package upk_select_test  
as   
type uc_test is ref cursor; -- 声明ref游标  
end upk_select_test;  
  
-- 存储过程中调用ref游标,并将查询结果以游标的方式返回  
create or replace procedure up_select_test_2  
(uc_result out upk_select_test.uc_test)  
is  
begin  
  open uc_result for select * from t_test;  
end up_select_test_2;  
 
7、  通配类型操作符

%type: 通配某行某列数据类型,如v_name t_test.t_name%type;通配表t_test中的t_name。
%rowtype: 通配一行所有列的数据类型,如 v_row t_test%rowtype;匹配t_test表中一行所有的数据类型。

8、  存储过程(procedure)

基本语法:
Sql代码  
create  procedure  <过程名>(<参数列表,无参时忽略>)  
as|is  
  变量声明、初始化  
begin  
  业务处理、逻辑代码  
exception  
  异常捕获、容错处理  
end  <过程名>;  
 
参数:<参数名> in|out|in out  <参数类型,无长度说明> ,如:v_name  varchar2

in:入参
     out:出参
     in out:出入参

注:as|is表示as或is

调用语法:
1)、exec  <过程名>;
2)、execute  <过程名>;
3)、在PL/SQL语句块中直接调用

例:
Sql代码  
create or replace procedure up_wap(v_param1 in out varchar2,v_param2 in out varchar2)  
is  
v_temp varchar2(20);  
begin  
  dbms_output.put_line('交换前参数1:'||v_param1||'  参数2:'||v_param2);  
  v_temp:=v_param1;  
  v_param1:=v_param2;  
  v_param2:=v_temp;  
  dbms_output.put_line('交换后参数1:'||v_param1||'  参数2:'||v_param2);  
  
exception  
  when others then dbms_output.put_line('There is a error when the procedure up_wap executing!');  
end up_wap;  
 
-- 调用存储过程
Sql代码  
declare  
    v_param1 varchar2(20):='param1';  
    v_param2 varchar2(20):='param2';  
begin  
  up_wap(v_param1 => v_param1,v_param2 => v_param2);  
end;  
 
9、  自定义函数(function)

基本语法:
Sql代码  
create  function  <函数名>(<参数列表,无参时忽略>)  
return  <返回值类型,无长度说明>  
as|is  
  变量声明、初始化  
begin  
  业务处理、逻辑代码  
  return  <返回的值>;  
  
exception  
  异常捕获、容错处理  
end  <函数名>;  
 
参数:in  入参
注:只有入参的类型。

在存储过程和自定义函数中的参数的传递(入参和出参)不能使用%type或%rowtype匹配,不能使用空值null,但是存储过程可以返回空值。

例:
Sql代码  
create function uf_select_name_by_id_test(v_id in number)  
return varchar2  
is  
v_name t_test.t_name%type;  
begin  
  select t_name into v_name from t_test where t_id=v_id;  
  return v_name;  
  
exception  
  when others then  
    dbms_output.put_line('error');  
end uf_select_name_by_id_test;  
  
  
select uf_select_name_by_id_test(1) 姓名 from dual;-- select调用  
  
declare --pl/sql语句块调用  
  v_name varchar2(20);  
begin  
  v_name:=uf_select_name_by_id_test(1);  
  dbms_output.put_line('name = '||v_name);  
end;  
 
10、包(package)

封装,可以封装过程(procedure)、函数(function)和变量。
注意,在包(package)中声明的过程(procedure)和函数(function)必须在包的实现体(package body)中定义实现。

基本语法:
Sql代码  
create  package  <包名>  
as|is  
  变量声明  
  存储过程声明  
  自定义函数声明  
end  <包名>;  
  
  
create  package body <包名,与声明部分一致>  
as|is  
  存储过程的代码实现  
  自定义函数的代码实现  
end  <包名>;  
 
例:
Java代码  
-- 创建包upk_hello  
create or replace package upk_hello  
is  
  v_hello_world varchar2(20):='hello world'; -- 声明变量  
  procedure up_hello_world(v_name in varchar2);-- 声明过程  
  function uf_hello_world(v_name in varchar2) return varchar2;-- 声明函数  
  
end upk_hello;  
  
  
-- 实现包(upk_hello)里声明的方法  
create or replace package body upk_hello  
is  
  procedure up_hello_world(v_name in varchar2)  
  is  
    v_string varchar2(100);  
  begin  
    v_string:=v_name||' say hello world!';  
    dbms_output.put_line(v_string);  
  exception  
    when others then dbms_output.put_line('error');  
  end up_hello_world;  
  
  function uf_hello_world(v_name in varchar2) return varchar2  
  is  
    v_string varchar2(100);  
  begin  
    v_string:=v_name||' say hello world!';  
    return v_string;  
  exception  
    when others then dbms_output.put_line('error');  
  end uf_hello_world;  
  
end upk_hello;  
  
  
-- 包的调用  
  
declare  
  v_msg varchar2(100);  
begin  
  upk_hello.up_hello_world('bing');  
  v_msg:=upk_hello.uf_hello_world('admin');  
  
  dbms_output.put_line(v_msg);  
  dbms_output.put_line(upk_hello.v_hello_world);  
  
end;  
 

存储过程中的3种循环:
1、
Sql代码  
is  
     i int;  
begin  
     i :=1;  
     loop  
         ..  
         exit when i =10;  
         i :=i+1;  
     end loop;  
 
2、
   
Sql代码  
i :=1;  
   while i<=5 loop  
       ..  
       i :=i+1;  
   end loop;  
 
3、
Sql代码  
for i in 1..100 loop  
     ..........  
   end loop;  
 

这是我以前的学习笔记,LZ凑合着看看吧,应该能看懂一些吧
===================================================
55 java跟oracle 调用(存储过程,函数等)
55.1 Java调用无参的函数
1:函数为:
create or replace function MyF1 return varchar2 is
  Result varchar2(20);
begin
  dbms_output.put_line('now in My F1');
  Result := 'Now MyF1 return';
  return(Result);
end MyF1;
 
2:Java程序
/**
     * 演示调用有一个没有参数的函数
     * @throws Exception
     */
    private static void t1() throws Exception {
        Class.forName("oracle.jdbc.driver.OracleDriver");
        Connection conn = DriverManager.getConnection(
                "jdbc:oracle:thin:@127.0.0.1:1521:orcl", "test", "test");
        try {
            CallableStatement stmt = conn
                    .prepareCall("{?=call MyF1()}");
            stmt.registerOutParameter(1, Types.VARCHAR);
            stmt.execute();
            System.out.println(stmt.getString(1));
        } finally {
            conn.close();
        }
    }
 
 
55.2 Java调用无参但有返回值的存储过程
1:存储过程
create or replace procedure MyP1(str out  Varchar2) is
begin
  dbms_output.put_line('Hello Procedure.');
  str :='Haha,Hello Procedure';
end MyP1;
 
 
2:程序
/**
     * 如何调用无参但有返回值的存储过程 测试的存储过程
     * @throws Exception
     */
    private static void t2() throws Exception {
        Class.forName("oracle.jdbc.driver.OracleDriver");
        Connection conn = DriverManager.getConnection(
                "jdbc:oracle:thin:@127.0.0.1:1521:orcl", "test", "test");
        try {
            CallableStatement stmt = conn.prepareCall("{call MyP1(?)}");
            // 注意,这里的stmt.getInt(1)中的数值1并非任意的,而是和存储过程中的out列对应的,
            // 如果out是在第一个位置,那就是 stmt.getInt(1),如果是第三个位置,就是getInt.getInt(3),
            // 当然也可以同时有多个返回值,那就是再多加几个out 参数了。
            stmt.registerOutParameter(1, Types.VARCHAR);
            stmt.execute();
            System.out.println(stmt.getString(1));
        } finally {
            conn.close();
        }
    }
 
 
55.3 Java调用有参的(传入)函数
1:函数
create or replace function MyF2(a number,b varchar2) return varchar2 is
  Result varchar2(50);
begin
  dbms_output.put_line('a==='||a||',b=='||b);
  Result := a||b;
  return(Result);
end MyF2;
 
2:程序
/**
     * 调用有参的函数
     * @throws Exception
     */
    private static void t3() throws Exception {
        Class.forName("oracle.jdbc.driver.OracleDriver");
        Connection conn = DriverManager.getConnection(
                "jdbc:oracle:thin:@127.0.0.1:1521:orcl", "test", "test");
        try {
            CallableStatement stmt = conn
                    .prepareCall("{?=call MyF2(?,?)}");
            stmt.setInt(2, 15);
            stmt.setString(3, "HelloF2");
            stmt.registerOutParameter(1, Types.VARCHAR);
            stmt.execute();
 
            System.out.println(stmt.getString(1));
        } finally {
            conn.close();
        }
    }
 
55.4 Java调用有参的(传入传出)存储过程
1:存储过程
create or replace procedure MyP2(a in number,b in varchar2,c out varchar2) is
begin
       dbms_output.put_line('a=='||a||',b=='||b); 
       c := 'ret=='||a||',b=='||b;
end MyP2;
 
2:程序
    /**
     * 调用有参数和返回值的存储过程
     * @throws Exception
     */
    private static void t4() throws Exception {
        Class.forName("oracle.jdbc.driver.OracleDriver");
        Connection conn = DriverManager.getConnection(
                "jdbc:oracle:thin:@127.0.0.1:1521:orcl", "test", "test");
        try {
            CallableStatement stmt = conn.prepareCall("{call MyP2(?,?,?)}");
            stmt.setInt(1, 5);
            stmt.setString(2, "测试");
            stmt.registerOutParameter(3, Types.VARCHAR);
            stmt.execute();
 
            System.out.println(stmt.getString(3));
        } finally {
            conn.close();
        }
    }
 
 
 
55.5 Java向存储过程传入传出对象的数组
1:在数据中创建对象
create or replace type UserModel as object(
 uuid varchar2(20),
name varchar2(20)
);
 
2:在数据库中建立对象的集合类型
create or replace type userCol as table of UserModel;
 
create or replace type retUserCol as table of UserModel;
 
3:在数据库中建立包
包头:
create or replace package MyTestPackage is
       TYPE dbRs IS REF CURSOR;
       procedure MyP3(a1 in userCol,a2 out dbRs);
      
end MyTestPackage;
 
包体:
create or replace package body MyTestPackage is
 
  procedure MyP3(a1 in userCol,a2 out dbRs) as
            umCol retUserCol := retUserCol();
  begin
            for i in 1.. a1.count loop
                insert into tbl_test values (a1(i).uuid,a1(i).name);
            end loop;
            commit;
                 
            umCol.Extend;
            umCol(1):=UserModel('retUuid11','retName11');
            umCol.Extend;
            umCol(2):=UserModel('retUuid22','retName22');
                 
            open a2 for select * from table(cast(umCol as retUserCol));
  end;
 
begin
  null;
end MyTestPackage;
 
4:程序:
/**
     * 测试向pl/sql传入对象集合,从pl/sql返回任意的对象的集合
     * @param list
     * @throws Exception
     */
    private static void t5(List list) throws Exception {
        CallableStatement stmt = null;
        Connection con = null;
        try {
            Class.forName("oracle.jdbc.driver.OracleDriver");
            con = DriverManager.getConnection(
                    "jdbc:oracle:thin:@127.0.0.1:1521:orcl", "test", "test");
            if (con != null) {
                ARRAY aArray = getArray(con, "USERMODEL", "USERCOL", list);//该函数调用的第二三个参数必须大写
                stmt = con.prepareCall("{call MyTestPackage.MyP3(?,?)}");
                ((OracleCallableStatement) stmt).setARRAY(1, aArray);
                stmt.registerOutParameter(2, OracleTypes.CURSOR);
                stmt.execute();
                ResultSet  rs=(ResultSet)stmt.getObject(2);
               
                while(rs.next()){
                    String uuid = rs.getString("uuid");
                    String name = rs.getString("name");
                    System.out.println("the uuid="+uuid+",name="+name);
                }
               
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    private static ARRAY getArray(Connection con, String OracleObj, String Oraclelist,
            List objlist) throws Exception {
        ARRAY list = null;
        if (objlist != null && objlist.size() > 0) {
            StructDescriptor structdesc = new StructDescriptor(OracleObj, con);
            STRUCT[] structs = new STRUCT[objlist.size()];
            Object[] result = new Object[0];
            for (int i = 0; i < objlist.size(); i++) {
                result = new Object[2];//数组大小应和你定义的数据库对象(UserModel)的属性的个数
                result[0] = ((UserModel)(objlist.get(i))).getUuid(); //将list中元素的数据传入result数组
                result[1] = ((UserModel)(objlist.get(i))).getName(); //
 
                structs[i] = new STRUCT(structdesc, con, result);
            }
            ArrayDescriptor desc = ArrayDescriptor.createDescriptor(Oraclelist,
                    con);
            list = new ARRAY(desc, con, structs);
        }
        return list;
    }
 
 
 
如果使用Tomcat的DBCP的连接池,需要把连接进行转换
public Connection getNativeConnection(Connection con) throws SQLException {
        if (con instanceof DelegatingConnection) {
         Connection nativeCon = ((DelegatingConnection) con).getInnermostDelegate();
         return (nativeCon != null ? nativeCon : con.getMetaData().getConnection());
        }
        return con;
      }


==============================

Oracle中table变量在JDBC中的运用

1.先定义一个返回数组类型的方法

create or replace type my_table_type is table of varchar2(20);

create or replace function func
return my_table_type
is
i my_table_type:=my_table_type();
begin
    select name bulk collect into i from emps;
    return i;
end;


2.在JDBC中调用,如果返回的是table变量

public void SelectAgus(String sql)
    {
         OracleCallableStatement call = null;
         try
         { 
             call = (OracleCallableStatement) con.prepareCall(sql);
             //如果返回的是table则用ARRAY类型,如果返回的是OBJECT的就用STRUCT
             //第三个参数是定义table的类型名
             call.registerOutParameter(1, OracleTypes.ARRAY,"MY_TABLE_TYPE");
             call.execute();
             //获取第一个参数(这里即返回值)
             ARRAY array = call.getARRAY(1);
             //获取表中的元素
             Datum[] dat = array.getOracleArray();
             //遍历依次打印
             for(Datum d : dat)
             {
                 System.out.println(new String(d.getBytes()));
             }
         }catch(Exception e)
         {
             e.printStackTrace();
         }
    }

2.如果定义的是嵌套表结构,

   如下定义:
   create or replace type all_table is object(id number,name varchar2(20));
   create or replace type emps_table_type is table of all_table;

--创建一个函数,返回类型为emps_table_type;

create or replace function funcc
return emps_table_type
is
i emps_table_type;
begin
   --把emps中的ID,NAME属性值全部读取到i中
   select all_table(id,name) bulk collect into i from emps;
   return i;--返回table
end;


   

public void SelectAgus(String sql)
    {
        OracleCallableStatement call = null;
        try
        {
            call = (OracleCallableStatement) con.prepareCall(sql);
            call.registerOutParameter(1, OracleTypes.ARRAY,"EMPS_TABLE_TYPE");
            call.execute();
            ARRAY array = call.getARRAY(1);
            Datum[] dat = array.getOracleArray();
            for(Datum d : dat)
            {   //获取了行后,要获取一行中的元素
                STRUCT struct = (STRUCT)d;
                //这里有可能会出现乱码,所以我分别用了两种方式获取元素
                Datum[] d1 = struct.getOracleAttributes();
                Object[] d2 = struct.getAttributes();
                System.out.println("ID="+d2[0]+"  "+"NAME="+
                        new String(d1[1].getBytes()));
            }
           
        }catch(Exception e){
            e.printStackTrace();
        }
    }




oracle 在一个存储过程中调用另一个返回游标的存储过程
实际项目当中经常需要在一个存储过程中调用另一个存储过程返回的游标,本文列举了两种情况讲述具体的操作方法。
第一种情况是返回的游标是某个具体的表或视图的数据,如:

CREATE OR REPLACE PROCEDURE P_TESTA (
PRESULT OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN PRESULT FOR SELECT * FROM USERS;
END P_TESTA;

其中USERS就是数据库中一个表。在调用的时候只要声明一个该表的ROWTYPE类型就可以了:

CREATE OR REPLACE PROCEDURE P_TESTB
AS
    VARCURSOR SYS_REFCURSOR;
    R USERS%ROWTYPE;
BEGIN
    P_TESTA(VARCURSOR);
LOOP
    FETCH VARCURSOR INTO R;
    EXIT WHEN VARCURSOR%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(R.NAME);
END LOOP;
END P_TESTB;

第二种情况,我们返回的不是表的所有的列,或许只是其中一列或两列,如:

CREATE OR REPLACE PROCEDURE P_TESTA (
PRESULT OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN PRESULT FOR SELECT ID,NAME FROM USERS;
END P_TESTA;

这里我们只返回了USERS表的ID,NAME这两个列,那么调用的时候也必须做相应的修改:

CREATE OR REPLACE PROCEDURE P_TESTB
AS
VARCURSOR SYS_REFCURSOR;
CURSOR TMPCURSOR IS SELECT ID,NAME FROM USERS WHERE ROWNUM=1;
R TMPCURSOR%ROWTYPE;
BEGIN
P_TESTA(VARCURSOR);
LOOP
FETCH VARCURSOR INTO R;
EXIT WHEN VARCURSOR%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(R.ID);
END LOOP;
END P_TESTB;

与之前不同的是我们声明了一个游标类型的变量TMPCURSOR ,注意TMPCURSOR 的结构必须与存储过程P_TESTA 返回的游标结构一致,否则就会出现错误。同理只要保持两个游标类型结构一致,就可以实现自由调用。



create table test(id int,name varchar(10))
insert into test select 1,'AAAA'
insert into test select 2,'BBBB'
go

create procedure sp_test1(@count int output)
as
    select @count=count(*) from test
go

create procedure sp_test2
as
begin
    declare @count int
    exec sp_test1 @count output
    select @count
end
go

exec sp_test2
go

--输出结果
/*
2
*/

drop procedure sp_test2,sp_test1
drop table test
go


oracle procedure 和function 的区别有哪些?

procedure 可多个返回参数,也就是out类型
function就一个
就这点区别
我觉得看使用的地方,如果只要执行一段sql的语句段,两个都行,如过想有返回值,一个的话用function,多个的话procedure。

procedure是存储过程 相当于程序语言里面一个处理业务的方法 也可以返回值
function是方法 相当于程序语言里面返回一个值的方法 一般较简单 可以在dml语句中用这个方法加参数增删改查
package相当于程序里面一个接口 里面可以定义常量数组bean 多个procedure和多个function的空实现
package body相当于程序里面一个类 是对应实现接口package的


循环:
1、..
is
     i int;
begin
     i :=1;
     loop
         ..
         exit when i =10;
         i :=i+1;
     end loop;
2、
     i :=1;
     while i<=5 loop
     ..   
         i :=i+1;
     end loop;
3、
     --如果指定了reverse选项,则循环控制变量会自动减1,否则自动加1
     for j in reverse  1..10 loop
    ..
     end loop;



1.基本结构

  CREATE OR REPLACE PROCEDURE 存储过程名字
  (
  参数1 IN NUMBER,
  参数2 IN NUMBER
  ) IS
  变量1 INTEGER :=0;
  变量2 DATE;
  BEGIN
  END 存储过程名字

  2.SELECT INTO STATEMENT

  将select查询的结果存入到变量中,可以同时将多个列存储多个变量中,必须有一条
  记录,否则抛出异常(如果没有记录抛出NO_DATA_FOUND)
  例子:
  BEGIN
  SELECT col1,col2 into 变量1,变量2 FROM typestruct where xxx;
  EXCEPTION
  WHEN NO_DATA_FOUND THEN
  xxxx;
  END;
  ...

  3.IF 判断

  IF V_TEST=1 THEN
  BEGIN
  do something
  END;
  END IF;

  4.while 循环

  WHILE V_TEST=1 LOOP
  BEGIN
  XXXX
  END;
  END LOOP;

  5.变量赋值

  V_TEST := 123;

  6.用for in 使用cursor
  ...
  IS
  CURSOR cur IS SELECT * FROM xxx;
  BEGIN
  FOR cur_result in cur LOOP
  BEGIN
  V_SUM :=cur_result.列名1 cur_result.列名2
  END;
  END LOOP;
  END;

  7.带参数的cursor

  CURSOR C_USER(C_ID NUMBER) IS SELECT NAME FROM USER WHERE TYPEID=C_ID;
  OPEN C_USER(变量值);
  LOOP
  FETCH C_USER INTO V_NAME;
  EXIT FETCH C_USER%NOTFOUND;
  do something
  END LOOP;
  CLOSE C_USER;

  8.用pl/sql developer debug

  连接数据库后建立一个Test WINDOW
  在窗口输入调用SP的代码,F9开始debug,CTRL N单步调试












猜你喜欢

转载自zhyp29.iteye.com/blog/2302047