(7)Oracle——————游标的使用,异常错误处理,存储过程和触发器

1,游标的使用

2,异常错误处理

3,存储函数和过程

4,触发器

游标的使用

  • 在 PL/SQL 程序中,对于 处理多行记录的事务经常使用游标来实现。
1,游标的概念
  • 为了处理 SQL 语句,ORACLE 必须分配一片叫上下文( context area )的区域来处理所必需的信息,其中包括要处理的行的数目,一个指向语句被分析以后的表示形式的指针以及查询的活动集(active set)。游标是一个 指向上下文的句柄( handle) 或指针。通过游标,PL/SQL 可以控制上下文区和处理语句时上下文区会发生些什么事情。
  • 对于不同的 SQL 语句,游标的使用情况不同:
    在这里插入图片描述
2,处理显式游标

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

  • 定义游标 :就是定义一个游标名,以及与其相对应的 SELECT 语句。格式:
CURSOR cursor_name[(parameter[, parameter])] IS select_statement;

游标参数只能为输入参数,其格式为:

parameter_name [IN] datatype [{:= | DEFAULT} expression]

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

  • 打开游标 :就是执行游标所对应的 SELECT 语句,将其查询结果放入工作区,并且指针指向工作区的首
    部,标识游标结果集合。如果游标查询语句中带有 FOR UPDATE 选项,OPEN 语句还将锁定数据库表中
    游标结果集合对应的数据行。格式:
OPEN cursor_name[([parameter =>] value[, [parameter =>] value])];

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

  • 提取游标数据 :就是检索结果集合中的数据行,放入指定的输出变量中。格式:
FETCH cursor_name INTO {variable_list | record_variable };

对该记录进行处理;继续处理,直到活动集合中没有记录;

  • 关闭游标 :当提取和处理完游标结果集合数据后,应及时关闭游标,以释放该游标所占用的系统资源,
    并使该游标的工作区变成无效,不能再使用 FETCH 语句取其中数据。关闭后的游标可以使用 OPEN 语
    句重新打开。格式:
CLOSE cursor_name;
  • 例:查询前 5 名员工的信息,按工资从低往高排
declare
  ---定义游标
  cursor emp_cursor(emp_no number default 6) is
    select ename, sal from emp where rownum < emp_no order by sal;
  v_name emp.ename%type;
  v_sal  emp.sal%type;

begin
  --打开游标
  open emp_cursor(emp_no => 6);
  --提取游标数据
  fetch emp_cursor
    into v_name, v_sal;

  while emp_cursor%found loop
    dbms_output.put_line(v_name || '---' || v_sal);
    fetch emp_cursor
      into v_name, v_sal;
  end loop;
  --关闭游标
  close emp_cursor;
end;
3,游标属性
  • %FOUND 布尔型属性,当最近一次读记录时成功返回,则值为 TRUE;

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

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

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

  • 例:给工资低于 3000 的员工工资加30:

declare
  v_empno emp.empno%type;
  v_sal   emp.sal%type;
  cursor emp_cursor is
    select empno, sal from emp;

begin
  open emp_cursor;
  loop
    fetch emp_cursor
      into v_empno, v_sal;
    exit when emp_cursor %notfound;
    if v_sal <= 3000 then
      update emp set sal = sal + 30
      where empno = v_empno;
      dbms_output.put_line('员工号' || v_empno || '工资已更新');
    end if;
  end loop;
  dbms_output.put_line('记录数为' || emp_cursor %rowcount);
  close emp_cursor;
end;
4,游标的 FOR循环
  • PL/SQL 语言提供了 游标 FOR 循环语句,自动执行游标的 OPEN 、FETCH 、CLOSE 语句和 循环 语句的功能;当进入循环时,游标 FOR 循环语句 自动 打开游标,并提取第一行游标数据,当程序处理完 当前所提取的数据而进入下一次循环时,游标 FOR 循环语句自动提取下一行数据供程序处理,当提取完结果集合中的所有数据行后结束循环,并自动关闭游标,格式:
FOR index_variable IN cursor_name[value[, value]] LOOP
-- 游标数据处理代码
END LOOP;

其中:
index_variable 为游标 FOR 循环语句隐含声明的索引变量,该变量为 记录变量,其 结构与游标查询语句返回的结构集合的结构相同。在程序中可以通过引用该索引记录变量元素来读取所提取的游标数据,index_variable 中各元素的名称与游标查询语句选择列表中所制定的列名相同。 如果在游标查询语句的选择列表中存在计算列,则必须为这些计算列指定别名后才能通过游标 FOR 循环语句中的索引变量来访问这些列数据

declare
  cursor c_emp(dep_id number default 20) is
    select empno, sal from emp where deptno = dep_id;

begin
  for v_emp in c_emp(30) loop
    dbms_output.put_line(v_emp.empno || ',' || v_emp.sal);
  end loop;
end;
5,处理隐式游标
  • 由系统隐含创建的游标称为隐式游标, 隐式游标的名字为 SQL,对于隐式游标的操作,如定义、打开、取值及关闭操作,都由 ORACLE 系统自动地完成,无需用户进行处理。 用户只能通过隐式游标的相关属性,来完成相应的操作。

  • 隐式游标属性
    SQL%FOUND 布尔型属性,当最近一次读记录时成功返回,则值为 TRUE;
    SQL%NOTFOUND 布尔型属性,与%FOUND 相反;
    SQL %ROWCOUNT 数字型属性, 返回已从游标中读取得记录数;
    SQL %ISOPEN 布尔型属性, 取值总是 FALSE。SQL 命令执行完毕立即关闭隐式游标。

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

declare
  v_name emp.ename%type;
  v_id   emp.empno%type := &v_id;
begin
  update emp set ename = v_name where empno = v_id;
  if SQL%NOTFOUND then
    dbms_output.put_line('查无此人');
  end if;
end;
6,关于 NO_DATA_FOUND 和 %NOTFOUND 的区别
  • SELECT … INTO 语句触发 NO_DATA_FOUND;
  • 当一个显式游标的 WHERE 子句未找到时触发%NOTFOUND;
  • 当 UPDATE 或 DELETE 语句的 WHERE 子句未找到时触发 SQL%NOTFOUND;在提取循环中要用 %NOTFOUND
    或%FOUND 来确定循环的退出条件,不要用 NO_DATA_FOUND.
7,游标修改和删除操作
  • 游标修改和删除操作是指在游标定位下,修改或删除表中指定的数据行。这时, 要求游标 查询语句中必须使用 FOR UPDATE 选项,以便在打开游标时锁定游标结果集合在表中对应数据行的所有列和部分列。
  • 为了对正在处理( 查询) 的行不被另外的用户改动,ORACLE 提供一个 FOR UPDATE 子句来对所选择的行进行锁住。该需求迫使 ORACLE 锁定游标结果集合的行,可以防止其他事务处理更新或删除相同的行,直到您的事务处理提交或回退为止。
  • 语法:
SELECT . . . FROMFOR UPDATE [OF column[, column]] [NOWAIT]
  • 如果使用 FOR UPDATE 在 声明游标,则可在 DELETE 和 和 UPDATE 用 语句中使用 WHERE CURRENT OF
    cursor_name 子句,修改或删除游标结果

  • 例 :从 EMPLOYEES 表中查询某部门的员工情况,将其工资最低定为 1000:

declare
  v_dep_id emp.deptno%type := &v_dep_id;
  cursor emp_cursor is
    select ename, sal from emp where deptno = v_dep_id for update nowait;
begin
  for emp_rec in emp_cursor loop
    if emp_rec.sal < 1000 then
      update emp set sal = 1000 where current of emp_cursor;
    end if;
  end loop;
end;

异常错误处理

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

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

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

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

  • 异常处理部分一般放在 PL/SQL 程序体的后半部,结构为:
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;
2,预定义的异常处理
  • 对这种异常情况的处理,只需在 PL/SQL 块的异常处理部分,直接引用相应的异常情况名,并对其完成相应的异常错误处理即可。
  • 例:更新指定员工工资,如工资小于 1000,则加 30;对 NO_DATA_FOUND 异常, TOO_MANY_ROWS 进行处理:
declare
  v_empno emp.empno%type := &v_empno;
  v_sal   emp.sal%type;

begin
  select sal into v_sal from emp where empno = v_empno for update;

  if v_sal <= 1000 then
    update emp set sal = sal + 30 where empno = v_empno;
    dbms_output.put_line('编码为' || v_empno || '员工工资已更新');
  else
    dbms_output.put_line('编码为' || v_empno || '员工工资不需要更新');
  end if;

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;
3,非预定义的异常处理
  • 在 PL/SQL 块的定义部分定义异常情况:
<异常情况> EXCEPTION;
  • 将其定义好的异常情况,与标准的 ORACLE 错误联系起来,使用 PRAGMA EXCEPTION_INIT 语句:
PRAGMA EXCEPTION_INIT(< 异常情况>, < 错误代码>);
  • 在 PL/SQL 块的异常情况处理部分对异常情况做出相应的处理。
  • 例 :删除指定部门的记录信息,以确保该部门没有员工:
declare
  v_depno emp.deptno%type := &v_depno;
  deptno_remaining exception;
  -- -2292是违反一致性约束的错误代码
  pragma exception_init(deptno_remaining, -2292);
begin
  delete from emp where deptno = v_depno;
exception
  when deptno_remaining then
    dbms_output.put_line('违反数据完整性约束');
  when others then
    dbms_output.put_line(sqlcode || '--' || sqlerrm);
end;
4,用户自定义的异常处理
  • 当与一个异常错误相关的错误出现时,就会隐含触发该异常错误。 用户定义的异常错误是通过显式使用 RAISE 语句来触发。当引发一个异常错误时,控制就转向到 EXCEPTION 块异常错误部分,执行错误处理代码。
  • 对于这类异常情况的处理,步骤如下:

      ①, 在 PL/SQL 块的定义部分定义异常情况:

< 异常情况> EXCEPTION;

      ②, RAISE < 异常情况>;

      ③, 在 PL/SQL 块的异常情况处理部分对异常情况做出相应的处理。

  • 例:更新指定员工工资,增加 100;若该员工不存在则抛出用户自定义异常: no_result
declare
  v_empid emp.empno%type := &v_empid;
  no_result exception;

begin
  update emp set sal = sal + 100 where empno = v_empid;

  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;
5,在 PL/SQL 中使用 SQLCODE, SQLERRM
  • SQLCODE 返回错误代码数字

  • SQLERRM 返回错误信息

  • 如: SQLCODE=-100 ------》 SQLERRM=’no_data_found ‘
    SQLCODE=0 -----》 SQLERRM=’normal, successfual completion’

  • 例 . 查询 ORACLE 错误代码:

BEGIN
  INSERT INTO emp
    (empno, ename, hiredate, deptno)
  VALUES
    (2222, 'Jerry', SYSDATE, 20);
  DBMS_OUTPUT.PUT_LINE('插入数据记录成功!');
  INSERT INTO emp
    (empno, ename, hiredate, deptno)
  VALUES
    (2222, 'Jerry', SYSDATE, 20);
  DBMS_OUTPUT.PUT_LINE('插入数据记录成功!');
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLCODE || '---' || SQLERRM);
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;
  • 不带参数的函数
create or replace function test_fun return date is
  v_date date;
begin
  select sysdate into v_date from dual;
  dbms_output.put_line('我是函数哈^^');
  return v_date;
end;
--执行函数
declare v_date date;
begin
v_date := test_fun(); dbms_output.put_line(v_date);
end;
  • 获取某部门的工资总和:
create or replace function get_salary(dep_id    emp.deptno%type,
                                      emp_count out number) return number is
  v_sum number;
begin
  select sum(salary), count(1)
    into v_sum, emp_count
    from emp
   where deptno = 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;
2,内嵌函数的调用
  • 第一种参数传递格式称为 位置表示法 ,格式为:
argument_value1[,argument_value2 …]
declare
  v_num number;
  v_sum number;
begin
  v_sum := get_sal(80, v_num);
  dbms_output.put_line('80号部门的工资总和:' || v_sum || ',人数' || v_num);
end;
  • 第二种参数传递格式称为 名称表示法 (必须与函数定义时所声明的形式参数名称相同),格式为:
argument => parameter [,]
declare
  v_num number;
  v_sum number;
begin
  v_sum := get_sal(emp_count => v_num,deptno => 80);
  dbms_output.put_line('80号部门的工资总和:' || v_sum || ',人数' || v_num);
end;
  • 第三种参数传递格式称为 混合表示法(使用位置表示法所传递的参数必须放在名称表示法所传递的参数前面):
declare
  v_num number;
  v_sum number;
begin
  v_sum := get_sal(80, emp_count => v_num);
  dbms_output.put_line('80号部门的工资总和:' || v_sum || ',人数' || v_num);
end;
  • 论采用哪一种参数传递方法,实际参数和形式参数之间的数据传递只有两种方法 :传址法和传值法。

  • 所谓传址法是指在调用函数时, 将实际参数的地址指针传递给形式参数, 使形式参数和实际参数指向内存中的同一区域,从而实现参数数据的传递。这种方法又称作参照法,即形式参数参照实际参数数据。 输入参数均采用传址法传递数据。

  • 传值法是指将实际参数的数据拷贝到形式参数,而不是传递实际参数的地址。默认时, 输出参数和输入/ 输出参数均采用传值法。

  • 删除指定员工记录:

create or replace procedure del_emp(v_empid in emp.empno%type) is
  no_result exception;
begin
  delete from emp where empno = 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;
3,调用存储过程
  • ORACLE 使用 EXECUTE 语句来实现对存储过程的调用:
EXEC[UTE] Procedure_name( parameter1, parameter2…);
  • 查询指定员工记录:
create or replace procedure query_map(v_empid emp.empno%type,
                                      v_name  out emp.ename%type,
                                      v_sal   out emp.sal%type) is
begin
  select ename, sal into v_name, v_sal from emp where empno = v_empid;
  dbms_output.put_line('员工号为:' || v_empid || '的员工已经找到');
exception
  when no_data_found then
    dbms_output.put_line('你要查询的数据不存在');
  when others then
    dbms_output.put_line(sqlcode || '--' || sqlerrm);
end;
  • 调用
declare
  v1 emp.ename%type;
  v2 emp.sal%type;
begin
  query_map(7499, v1, v2);
  dbms_output.put_line('姓名' || v1 || ',工资' || v2);
  query_map(201, v1, v2);
  dbms_output.put_line('姓名' || v1 || ',工资' || v2);
end;
  • 计算指定部门的工资总和,并统计其中的职工数量:
create or replace procedure proc_demo(v_depid    emp.deptno%type default 10,
                                      v_salsum   out emp.sal%type,
                                      v_empcount out number) is
begin
  select sum(sal), count(1)
    into v_salsum, v_empcount
    from emp
   where deptno = v_depid;
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
  proc_demo(v_salsum => v_sum, v_empcount => v_num);
  dbms_output.put_line('10号部门的工资总额为:' || v_sum || ',人数为:' || v_num);
end;
4,AUTHID
  • 在创建存储过程时, 可使用 AUTHID CURRENT_USER 或 AUTHID DEFINER 选项,以表明在执行该过程时Oracle 使用的权限
  • 如果使用 AUTHID CURRENT_USER 选项创建一个过程, 则 Oracle 用调用该 过程的用户权限执行该过程. 为了成功执行该过程, 调用者 必须具有访问该存储过程体中引用的所有数据库对象所必须的权限
  • 如果用默认的 AUTHID DEFINER 选项创建过程, 则 Oracle 使用过程所有者的特权执行该过程,为了成功执行该过程,过程的所有者 必须具有访问该存储过程体中引用的所有数据库对象所必须的 权限, 想要简化应用程序用户的特权管理, 在创建存储过程时, 一般选择 AUTHID DEFINER选项 –-- 这样就不必授权给需要调用的此过程的所有用户了.
5,删除存储过程
DROP PROCEDURE [user.]Procudure_name;

触发器

  • 触发器是许多关系数据库系统都提供的一项技术。在 ORACLE 系统里, 触发器类似过程和函数,都有声明,执行和异常处理过程的 PL/SQL 块。
1,触发器类型
- 触发器在数据库里以独立的对象存储,它与存储过程不同的是,存储过程通过其它程序来启动运行或直接启动运行,而 触发器是由一个事件来启动运行。 即触发器是当某个事件发生时自动地隐式运行。并且,触发器不能接收参数。所以 运行触发器就叫触发或点火(firing) )。ORACLE 事件指的是对数据库的表进行的INSERT 、UPDATE 及 及 DELETE 操作或对视图进行类似的操作。ORACLE 将触发器的功能扩展到了触发 ORACLE,如数据库的启动与关闭等。
2,DML 触发器
  • ORACLE 可以在 DML 语句进行触发,可以在 DML 操作前或操作后进行触发,并且 可以对每个行或语句操作上进行触发。

触发器组成:

  • 触发事件:即在何种情况下触发 TRIGGER; 例如:INSERT, UPDATE, DELETE。
  • 触发时间:即该 TRIGGER 是在触发事件发生之前(BEFORE)还是之后(AFTER)触发,也就是触发事件和该 TRIGGER 的操作顺序。
  • 触发器本身:即该 TRIGGER 被触发之后的目的和意图,正是触发器本身要做的事情。 例如:PL/SQL 块。
  • 触发频率:说明触发器内定义的动作被执行的次数。即 语句级(STATEMENT) 触发器和行级(ROW) 触发器。
    语句级(STATEMENT)触发器:是指当某触发事件发生时,该触发器只执行一次;
    行级(ROW)触发器:是指当某触发事件发生时,对受到该操作影响的每一行数据,触发器都单独执行一次
3,创建触发器
  • 创建触发器的一般语法是:
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 触发器则为行触发器。
    WHEN 子句说明触发约束条件。Condition 为一个逻辑表达时,其中必须包含相关名称,而不能包含查
    询语句,也不能调用 PL/SQL 函数。WHEN 子句指定的触发约束条件只能用在 BEFORE 和 AFTER 行触发器
    中,不能用在 INSTEAD OF 行触发器和其它类型的触发器中。
4, 触发器触发次序
  • 执行 BEFORE 语句级触发器;
  • 对与受语句影响的每一行:
    执行 BEFORE 行级触发器
    执行 DML 语句
    执行 AFTER 行级触发器
  • 执行 AFTER 语句级触发器
5,创建 DML
  • 触发器名可以和表或过程有相同的名字,但在一个模式中触发器名不能相同。

  • 触发器的限制
    CREATE TRIGGER 语句文本的字符长度不能超过 32KB;
    触发器体内的 SELECT 语句只能为 SELECT … INTO … 结构,或者为定义游标所使用的 SELECT 语句。
    触发器中不能使用数据库事务控制语句 COMMIT; ROLLBACK, SVAEPOINT 语句;
    由触发器所调用的过程或函数也不能使用数据库事务控制语句;

  • 建立一个触发器,当职工表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;
6,删除触发器
  • 删除触发器 :
DROP TRIGGER trigger_name;

当删除其他用户模式中的触发器名称,需要具有 DROP ANY TRIGGER 系统权限,当删除建立在数据库上的触发器时,用户需要具有 ADMINISTER DATABASE TRIGGER 系统权限。此外,, 当删除表或视图时,建立在这些对象上的触发器也随之删除。

发布了74 篇原创文章 · 获赞 21 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41530004/article/details/105007333