一、游标

  在 PL/SQL 程序中,对于处理多行记录的事务经常使用游标来实现。游标是一个指向上下文的句柄( handle)或指针。通过游标,PL/SQL 可以控制上下文区和处理语句时上 下文区会发生些什么事情。

1.显式游标:

显式游标主要是用于对查询语句的处理,尤其是在查询结果为多条记录的情况下。

显式游标处理需四个 PL/SQL 步骤

(1)定义游标:就是定义一个游标名,以及与其相对应的 SELECT 语句。

格式: CURSOR cursor_name[(parameter[, parameter]…)] IS select_statement;

游标参数只能为输入参数,其格式为: parameter_name [IN] datatype [{:= | DEFAULT} expression]

在指定数据类型时,不能使用长度约束。如 NUMBER(4)、CHAR(10) 等都是错误的。

(2)打开游标:就是执行游标所对应的 SELECT 语句,将其查询结果放入工作区,并且指针指向工作区的首部,标识游标结果集合。如果游标查询语句中带有 FOR UPDATE 选

项,OPEN 语句还将锁定数据库表中 游标结果集合对应的数据行。

格式: OPEN cursor_name[([parameter =>] value[, [parameter =>] value]…)];

在向游标传递参数时,可以使用与函数参数相同的传值方法,即位置表示法和名称表示 法。PL/SQL 程序不能用 OPEN 语句重复打开一个游标

(3)提取游标数据:就是检索结果集合中的数据行,放入指定的输出变量中。

格式: FETCH cursor_name INTO {variable_list | record_variable };

(4) 对该记录进行处理;

(5)继续处理,直到活动集合中没有记录;

(6)关闭游标:当提取和处理完游标结果集合数据后,应及时关闭游标,以释放该游标所占用的系统资源, 并使该游标的工作区变成无效,不能再使用 FETCH 语句取其中数据。

关闭后的游标可以使用 OPEN 语 句重新打开。

格式:CLOSE cursor_name;

注:定义的游标不能有 INTO 子句

实例:

declare
    --定义游标
    cursor c_cursor(emp_no number default 11) is select last_name,salary
        from employees
        where rownum < emp_no
        order by salary;
    v_name employees.last_name%type;
    v_sal employees.salary%type;
begin
    --打开游标
    open c_cursor(emp_no => 20);
    --提取游标数据
    fetch c_cursor into v_name,v_sal;
    
    while c_cursor %found loop
        dbms_output.put_line(v_name || ':' || v_sal);
        fetch c_cursor into v_name,v_sal;
    end loop;
    --关闭游标
    close c_cursor;
end;

游标属性:

%FOUND 布尔型属性,当最近一次读记录时成功返回,则值为 TRUE;

%NOTFOUND 布尔型属性,与%FOUND 相反;

%ISOPEN 布尔型属性,当游标已打开时返回 TRUE;

%ROWCOUNT 数字型属性,返回已从游标中读取的记录数。

游标的 FOR 循环:

  PL/SQL 语言提供了游标 FOR 循环语句自动执行游标的 OPEN、FETCH、CLOSE 语句和循环语句的功能; 当进入循环时,游标 FOR 循环语句自动打开游标,并提取第一行游标数据,当程序处理完当前所提取的数 据而进入下一次循环时,游标 FOR 循环语句自动提取下一行数据供程序处理,当提取完结果集合中的所有 数据行后结束循环,并自动关闭游标。

格式:

FOR index_variable IN cursor_name[value[, value]…] LOOP

-- 游标数据处理代码

END LOOP;

注意:

index_variable 为游标 FOR 循环语句隐含声明的索引变量,该变量为记录变量,其结构与游标查询语句返回的结构集合的结构相同。在程序中可以通过引用该索引记录变量元素来读取所提取的游标数据。

如果在游标查询语句的选择列表中存在计算列,则必须为这些计算列指定别名后才能通过游标 FOR 循环语句中的索引变量来访问这列数据。

不要在程序中对游标进行人工操作;不要在程序中定义用于控制 FOR 循环的记录。因为FOR语句会自动完成以上操作

实例:

declare
    cursor c_emp(dep_id number default 50) is 
        select last_name,salary sal
        from employees
        where department_id = dep_id;
begin
    for v_emp in c_emp(80) loop
        dbms_output.put_line(v_emp.last_name||', '||v_emp.sal);
    end loop;
end;

 2.隐式游标:

对于非查询语句, 如修改、删除操作,由 ORACLE 系统自动地为这些操作设置游标并创建其工作区,这些由系统隐含创建 的游标称为隐式游标隐式游标的名字为 SQL,这是由 ORACLE 系统定义的。对于隐式游标的操作,如定义、 打开、取值及关闭操作,都由 ORACLE 系统自动地完成,无需用户进行处理。用户只能通过隐式游标的相关属性,来完成相应的操作。在隐式游标的工作区中,所存放的数据是与用户自定义的显示游标无关的、

隐式游标属性:

SQL%FOUND 布尔型属性,当最近一次读记录时成功返回,则值为 TRUE;

SQL%NOTFOUND 布尔型属性,与%FOUND 相反;

SQL %ROWCOUNT 数字型属性, 返回已从游标中读取得记录数;

SQL %ISOPEN 布尔型属性, 取值总是 FALSE。SQL 命令执行完毕立即关闭隐式游标。

实例:

更新指定员工信息,如果该员工没有找到,则打印”查无此人”信息。

declare
    v_name employees.last_name%type;
    v_id employees.employee_id%type := &v_id;
begin
    update employees
    set last_name = 'xx'
    where employee_id = v_id;
    
    if SQL%NOTFOUND then 
        dbms_output.put_line('查无此人');
    end if;
end;

3.游标修改和删除操作:

  游标修改和删除操作是指在游标定位下,修改或删除表中指定的数据行。这时,要求游标查询语句中 必须使用 FOR UPDATE 选项,以便在打开游标时锁定游标结果集合在表中对应数据行的所有列和部分列。 为了对正在处理(查询)的行不被另外的用户改动,ORACLE 提供一个 FOR UPDATE 子句来对所选择的行 进行锁住。该需求迫使 ORACLE 锁定游标结果集合的行,可以防止其他事务处理更新或删除相同的行,直到 您的事务处理提交或回退为止。

格式:

SELECT . . . FROM … FOR UPDATE [OF column[, column]…] [NOWAIT]

注意:

如果使用 FOR UPDATE 声明游标,则可在 DELETE 和 UPDATE 语句中使用 WHERE CURRENT OF cursor_name 子句,修改或删除游标结果集合当前行对应的数据库表中的数据行。

实例:

从 EMPLOYEES 表中查询某部门的员工情况,将其工资最低定为 3000;

declare
    v_dep_id employees.department_id%type := &v_dep_id;
    cursor emp_cursor is 
        select last_name,salary
        from employees
        where department_id = v_dep_id
        for update nowait;
begin
    for emp_rec in emp_cursor loop
        if emp_rec.salary < 3000 then
            update employees set salary = 3000
            where current of emp_cursor;
        end if;
    end loop;
end;

二、异常错误处理

概念:

异常情况处理(EXCEPTION)是用来处理正常执行过程中未预料的事件,程序块的异常处理预定义的错误和自定义错误,由于 PL/SQL 程序块一旦产生异常而没有指出如何处理时,程序就会自动终止整个程序运行

有三种类型的异常错误:

1. 预定义 ( Predefined )错误 ORACLE 预定义的异常情况大约有 24 个。对这种异常情况的处理,无需在程序中定义,由 ORACLE 自动 将其引发

2. 非预定义 ( Predefined )错误 即其他标准的 ORACLE 错误。对这种异常情况的处理,需要用户在程序中定义,然后由 ORACLE 自动将 其引发。

3. 用户定义(User_define) 错误 程序执行过程中,出现编程人员认为的非正常情况。对这种异常情况的处理,需要用户在程序中定义, 然后显式地在程序中将其引发

语法:

EXCEPTION

WHEN first_exception THEN <code to handle first exception>

WHEN second_exception THEN <code to handle second exception>

WHEN OTHERS THEN <code to handle others exception>

 

END;

注:异常处理可以按任意次序排列,但 OTHERS 必须放在最后.

1.处理预定义的异常

declare
    --声明变量的代码
begin
    --执行的代码
exception
    when NO_DATA_FOUND then
        dbms_output.put_line('数据库中没有编码为'|| v_empno ||'的员工');
    when TOO_MANY_ROWS then
        dbms_output.put_line('程序运行错误,请使用游标');
    when others then
        dbms_output.put_line('其他错误');
end;

2.处理非预定义的异常

  对于这类异常情况的处理,首先必须对非定义的 ORACLE 错误进行定义。步骤如下:

  1. 在 PL/SQL 块的定义部分定义异常情况: 

    <异常情况> EXCEPTION;

  2. 将其定义好的异常情况,与标准的 ORACLE 错误联系起来,使用 PRAGMA EXCEPTION_INIT 语句

    PRAGMA EXCEPTION_INIT(<异常情况>, <错误代码>);

  3. 在 PL/SQL 块的异常情况处理部分对异常情况做出相应的处理

实例:

declare
    --声明其他变量的代码
    deptno_remaining exception;
    
    -- -2292 是违反一致性约束的错误异常的编号
    pragma exception_init(deptno_remaining, -2292);
begin
    --执行的代码
exception
    when deptno_remaining then
        dbms_output.put_line('违反数据完整性约束');
    when others then
        dbms_output.put_line(sqlcode || '--' || sqlerrm);
end;

注:SQLCODE 返回错误代码数字 SQLERRM 返回错误信息.

3.处理用户自定义的异常

当与一个异常错误相关的错误出现时,就会隐含触发该异常错误。用户定义的异常错误是通过显式使 用 RAISE 语句来触发当引发一个异常错误时,控制就转向到 EXCEPTION 块异常错误部分,执行错误处理代码。

对于这类异常情况的处理,步骤如下:

1. 在 PL/SQL 块的定义部分定义异常情况:

   <异常情况> EXCEPTION;

2. RAISE <异常情况>;

3. 在 PL/SQL 块的异常情况处理部分对异常情况做出相应的处理

实例:

declare
    --声明其他变量的代码
    no_result exception;
begin
    --执行update代码
    if sql%notfound then 
        raise no_result;
    end if;
exception
    when no_result then
        dbms_output.put_line('数据更新失败');
    when others then
        dbms_output.put_line('出现其他异常');
end;

三、存储函数与过程

  ORACLE 提供可以把 PL/SQL 程序存储在数据库中,并可以在任何地方来运行它。这样就叫存储过 程或函数。过程和函数统称为 PL/SQL 子程序,他们是被命名的 PL/SQL 块,均存储在数据库中,并 通过输入、输出参数或输入/输出参数与其调用者交换信息。过程和函数的唯一区别是函数总向调 用者返回数据,而过程则不返回数据

1.创建函数

建立内嵌函数:

格式:

CREATE [OR REPLACE] FUNCTION function_name
[ (argment [ { IN | IN OUT }] Type,
argment [ { IN | OUT | IN OUT } ] Type ]
[ AUTHID DEFINER | CURRENT_USER ]
RETURN return_type
{ IS | AS }
<类型.变量的说明>
BEGIN
  FUNCTION_body
EXCEPTION
  其它语句
END;

说明:
1) OR REPLACE 为可选. 有了它, 可以或者创建一个新函数或者替换相同名字的函数, 而不会出现冲突

2) 函数名后面是一个可选的参数列表, 其中包含 IN,OUT 或 IN OUT 标记, 参数之间用逗号隔开。IN 参数标记表示传递给函数的值在该函数执行中不改变;OUT 标记表示一个值在函数中进行计算并通过该参数传递给调用语句; IN OUT 标记表示传递给函数的值可以变化并传递给调用语句。若省略标记, 则参数隐含为 IN。

3) 因为函数需要返回一个值, 所以 RETURN 包含返回结果的数据类型

实例:获取某部门的工资总和

create or replace function get_salary(
    dept_id employees.department_id%type,
    emp_count out number)
    return number
    is
    
    v_sum number;
begin
    select sum(salary),count(*) int v_sum, emp_count
    from employees
    where department_id = dep_id;
    
    return v_sum;
exception
    when no_data_found then 
        dbms_output.put_line('您需要的数据不存在');
    when others then 
        dbms_output.put_line(sqlcode||':'|| sqlerrm);
end;

调用函数:

declare
    v_num number;
    v_sum number;
begin
    v_sum := get_salary(80, emp_count => v_num);
    dbms_output.put_line('80号部门的工资总和:'||v_sum||',人数:'||v_num);
end;

2.存储过程

创建存储过程:

语法:

CREATE [OR REPLACE] PROCEDURE Procedure_name
[ (argment [ { IN | IN OUT }] Type,
argment [ { IN | OUT | IN OUT } ] Type ]
[ AUTHID DEFINER | CURRENT_USER ]
{ IS | AS }
<类型.变量的说明>
BEGIN
<执行部分>
EXCEPTION
<可选的异常错误处理程序>
END;

实例:删除指定员工记录

create or replace procedure del_emp(
    v_empid in employees.employee_id%type)
    is
    
    no_result exception;
begin
    delete from employees
    where employee_id = v_empid;
    
    if sql%notfound then 
        raise no_result;
    end if;
    
    dbms_output.put_line('编号为:'||v_empid||'的员工已被除名');
exception
    when no_result then 
        dbms_output.put_line('您要删除的数据不存在');
    when others then 
        dbms_output.put_line(sqlcode||'--'||sqlerrm);
end;

 

四、触发器

定义:  

触发器类似过程和函数,都有声明,执行和异常处理过程的 PL/SQL 块。触发器在数据库里以独立的对象存储,它与存储过程不同的是,存储过程通过其它程序来启动运行或 直接启动运行,而触发器是由一个事件来启动运行。即触发器是当某个事件发生时自动地隐式运行。并且, 触发器不能接收参数。所以运行触发器就叫触发或点火(firing)。ORACLE 事件指的是对数据库的表进行的 INSERT、UPDATE 及 DELETE 操作或对视图进行类似的操作

 

分类:

DML 触发器

ORACLE 可以在 DML 语句进行触发,可以在 DML 操作前或操作后进行触发,并且可以对每个行或语句操作上进行触发

替代触发器

由于在 ORACLE 里,不能直接对由两个以上的表建立的视图进行操作。所以给出了替代触发器。

系统触发器

它可以在 ORACLE 数据库系统的事件中进行触发,如 ORACLE 系统的启动与关闭等。

组成:

触发事件即在何种情况下触发 TRIGGER; 例如:INSERT, UPDATE, DELETE

触发时间:即该 TRIGGER 是在触发事件发生之前(BEFORE)还是之后(AFTER)触发,也就是触发事件和 该 TRIGGER 的操作顺序。

触发器本身:即该 TRIGGER 被触发之后的目的和意图,正是触发器本身要做的事情。 例如:PL/SQL 块。

触发频率:说明触发器内定义的动作被执行的次数。即语句级(STATEMENT)触发器和行级(ROW)触发器。 语句级(STATEMENT)触发器:是指当某触发事件发生时,该触发器只执行一次; 行级(ROW)触发器:是指当某触发事件发生时,对受到该操作影响的每一行数据,触发器都单独执行一 次。

语法:

 

CREATE [OR REPLACE] TRIGGER trigger_name
{BEFORE | AFTER }
{INSERT | DELETE | UPDATE [OF column [, column …]]}
ON [schema.] table_name
[FOR EACH ROW ]
[WHEN condition]
trigger_body;

注意: 

BEFORE 和 AFTER 指出触发器的触发时序分别为前触发和后触发方式,前触发是在执行触发事件之前触 发当前所创建的触发器,后触发是在执行触发事件之后触发当前所创建的

触发器。

FOR EACH ROW 选项说明触发器为行触发器。行触发器和语句触发器的区别表现在:行触发器要求当一 个 DML 语句操做影响数据库中的多行数据时,对于其中的每个数据行,只要它们符合触发约束条件,均激 活一次触发器;而语句触发器将整个语句操作作为触发事件,当它符合约束条件时,激活一次触发器。当省略 FOR EACH ROW 选项时BEFORE 和 AFTER 触发器为语句触发器,而 INSTEAD OF 触发器则为行触发器。

当触发器被触发时,要使用被插入、更新或删除的记录中的列值,有时要使用操作前或操作后的列的值。实现:

:NEW 修饰符访问操作完成后列的值

:OLD 修饰符访问操作完成前列的值

实例:建立一个触发器, 当职工表 emp 表被删除一条记录时,把被删除记录写到职工表删除日志表中去。

create table emp_his as 
select * from emp
where 1 = 2

create or replace trigger del_emp_trigger
    before delete on emp for each row
begin 
    insert into emp_his(deptno, empno, ename, job, mgr, sal,comm,hiredate)
    values(:old.deptno, :old.empno, :old.ename, :old.job, :old.mgr, :old.sal,:old.comm,:old.hiredate);
end;