114

Click here to load reader

PL/SQL 基础培训

Embed Size (px)

DESCRIPTION

PL/SQL 基础培训. 北京神州数码思特奇信息技术股份有限公司 研究院. Oracle 数据库开发 ——PL/SQL. PL/SQL 基础 PL/SQL 语言的简介 数据类型 程序结构 流程控制 异常处理 过程与函数 游标 触发器 SQL 优化初步. PL/SQL 基础. SQL语句、PL/SQL块和SQL*Plus命令之间的区别: - PowerPoint PPT Presentation

Citation preview

Page 1: PL/SQL 基础培训

www.si-tech.com.cn

PL/SQL基础培训

北京神州数码思特奇信息技术股份有限公司研究院

Page 2: PL/SQL 基础培训

Oracle 数据库开发—— PL/SQL

PL/SQL基础– PL/SQL语言的简介– 数据类型– 程序结构– 流程控制– 异常处理– 过程与函数– 游标– 触发器– SQL优化初步

Page 3: PL/SQL 基础培训

PL/SQL 基础 SQL语句、PL/SQL块和SQL*Plus命令之间的区别:

1)SQL语句是以数据库为操作对象的语言,主要包括数据定义语言DDL、数据操纵语言DML和数据控制语言DCL以及数据存储语言DSL。当输入SQL语句后,SQL*Plus将其保存在内部缓冲区中。

2)PL/SQL块同样是以数据库中的数据为操作对象。但由于SQL不具备过程控制功能,所以,为了能够与其他语言一样具备面向过程的处理功能,在SQL中加入了诸如循环、选择等面向过程的处理功能,由此形成了PL/SQL。所有PL/SQL语句的解释均由PL/SQL引擎来完成。使用PL/SQL块可编写过程、触发器和包等数据库永久对象。

3)SQL*Plus命令主要用来格式化查询结果、设置选择、编辑及存储 SQL命令、以设置查询结果的显示格式,并且可以设置环境选项。

Page 4: PL/SQL 基础培训

PL/SQL 基础1、PL/SQL:PL/SQL 是一种过程化编程语言,运行于服务器端的编程语言, PL/SQL和SQL 作无缝连接,是和 C、 Java 一样关注于实现的细节,因此可以实现复杂的业务逻辑处理。 PL/SQL 是对 SQL 的一种扩展,把 SQL 语言的灵活性和过程化结构融合在一起。

2、PL/SQL 改善性能:• PL/SQL 是以整个语句块的形式发送给服务器的,降低了网络负载,从而提高性能。因为SQL 语句是以语句为单位进行发送的,在网络环境下会占用大量的服务器时间,同时导致网络拥挤。

如图:

Oracle数据库服务器

SQL SQL SQL

客户端应用程序

使用 SQL

Oracle数据库服务器

SQL……SQL

客户端应用程序

Page 5: PL/SQL 基础培训

PL/SQL 结构PL/SQL 的基本结构是块,所有的 PL/SQL 程序都是由块组成的,

PL/SQL 的块结构如下:

declare

/* 声明部分,包括变量、常量、游标、用户定义的异常的定义等 */

begin

/* 可执行的部分,包括 SQL 语句和过程化语句,是程序的主体 */

exception

/* 异常处理部分,包括异常处理语句 */

end

/* 块结束语句 */

PL/SQL 基础

Page 6: PL/SQL 基础培训

PL/SQL数据类型

数字型用来存储整数或实数。 NUMBER、 BINARY_INTEGER、 PLS_INTEGER

NUMBER----- 存储整数和浮点数BINARY_INTEGER----- 存储带符号的整数值,溢出时不发生错误PLS_INTEGER----- 存储带符号的整数值,溢出时发生错误

例如: V_NUM NUMBER( 5 ); v_binarynum binary_integer;

PL/SQL 基础

Page 7: PL/SQL 基础培训

PL/SQL 基础

• 字 符 型 : 用 类 存 储 字 符 串 或 字 符 数 据 。 包 括 VARCHAR2、 CHAR、 LONG、 NCHAR、 NVARCHAR2 。

VARCHAR2----- 存储可变长度的字符串 CHAR----- 存储固定长度的字符串 LONG----- 存储可变长度的字符串,其最大长度是 32760 字节。 NCHAR 和 NVARCHAR2-----NLS 字符类型用于存储来自不同于

PL/SQL 语言的字符集中的字符集。

例如: V_CHAR VARCHAR2( 20 );

Page 8: PL/SQL 基础培训

PL/SQL 基础

• 日期型:用来存储日期和时间信息,包括世纪、年、月、天、小时、分钟和秒。唯一的类型为 DATE 。

例如: V_DATE DATE ;

• 布尔型:布尔型的类型为 BOOLEAN 。布尔变量在PL/SQL 控制结构中使用, BOOLEAN 变量只能存储TRUE、 FALSE、 NULL 值。

例如: V_BOOLEAN BOOLEAN;

Page 9: PL/SQL 基础培训

PL/SQL 基础

• date、 timestamp 类型

• 示例演示介绍

ʾÀý

Page 10: PL/SQL 基础培训

PL/SQL 基础

• 原始型:用来存储二进制数据。包括 RAW 、 LONG RAW

RAW----- 存储定长的二进制数据。类似于 CHAR 类型,但不在字符集之间进行转换。

LONG RAW-----与 LONG 类似,最大长度为 32760 字节,但不在字符集之间进行转换。

例如: V_LONG LONG;

Page 11: PL/SQL 基础培训

PL/SQL 基础

空值处理 NULL+〈数字〉 =NULL (空值加数字仍是空值 )

NULL> 〈数字〉 =NULL (空值与数字比较,结果仍是空值) NULL||‘ 字符串’ = ‘ 字符串’ (空值与字符串进行连接运算,

结果为原字符串)

判断一个变量的值是否为 NULL 的正确写法为: if my_var is null then

....

end if;

错误写法为: if my_var = null then

....

end if;

Page 12: PL/SQL 基础培训

使用 NULL 值进行比较时,注意:

例: a:=5;

b:=null;

if a<>b then

....

end if;

例: x:=null;

y:=null;

if x=y then

....

end if;

PL/SQL 基础

Page 13: PL/SQL 基础培训

PL/SQL 基础变量的声明:除了满足 SQL 基本命名规则,变量还要以 v_ 开头,常量以c_ 开头,声明变量或常量的语法如下:

标识符 [constant] datatype [not null][:=|default expr];

例如:declare v_ch char(20); c_ch constant char(10) not null := 'World!';begin

v_ch := 'Hello'; dbms_output.put_line(v_ch); dbms_output.put_line(c_ch);exception when others then null;end;

注意:变量名和常量名不能与 Oracle 数据库中表名或字段名相同,变量如果没有初始值,默认是 null 。

Page 14: PL/SQL 基础培训

PL/SQL 基础

例如:declare

c_pi constant number(3,2) default 3.14;

v_area number(8,2);

v_r number(2);

begin

v_r:=2;

v_area:=c_pi*v_r*v_r;

dbms_output.put_line(' 圆的面积是: '||v_area);

end;

Page 15: PL/SQL 基础培训

PL/SQL 基础

例如:declare

v_i number := 5;

v_ch char(20) := 'How are you';

v_today char(15) := to_char(sysdate,'yyyy/mm/dd');

v_flag boolean:=true;

begin

dbms_output.put_line(v_i||' '||v_ch||' '||v_today);

end;

Page 16: PL/SQL 基础培训

PL/SQL 基础

例如:declare v_avgprice titles.price%type; v_date char(20);begin v_date:=to_char(sysdate,'yyyy/mm/dd'); select avg(price) into v_avgprice from titles; dbms_output.put_line(v_avgprice); dbms_output.put_line(v_date);end;

Page 17: PL/SQL 基础培训

PL/SQL 基础set serveroutput on--set serveroutput offdeclarev_empno emp.empno%Type; /* 声明变量 v_empno, %type: 使该变量的类型与 emp 表中的 empno 类型相同 */v_emprecord emp%Rowtype;/* 声明变量 v_emprecord, %rowtype: 使该变量的类型与 emp 表中的整行相同 */

beginSelect * Into v_emprecord From emp Where empno=&v_empno;dbms_output.put_line(' 雇员编号 ‘ || v_emprecord.empno);dbms_output.put_line(' 雇员姓名 ‘ || v_emprecord.ename);dbms_output.put_line(' 入职日期 ‘ || v_emprecord.hiredate);dbms_output.put_line(' 职位 ‘ || v_emprecord.job);dbms_output.put_line(' 管理员编号 ‘ || v_emprecord.mgr);dbms_output.put_line(' 工资 ‘ || v_emprecord.sal);dbms_output.put_line(' 奖金 ‘ || v_emprecord.comm);dbms_output.put_line(' 部门编号 ‘ || v_emprecord.deptno);end;/

Page 18: PL/SQL 基础培训

PL/SQL 的记录类型 

把逻辑相关的数据作为一个单元存储起来 , 在 declare 段中定义record 类型数据,使某一变量使用该 record 型数据.

定义方法: TYPE r_record is RECORD (

v_name emp.ename%TYPE, v_job emp.job%TYPE, v_sal emp.sal%TYPE

);

变量定义 r_emp r_record;变量使用    SELECT ename,job,sal INTO r_emp FROM emp WHERE empno=7934; 则, r_emp.v_ename,r_emp.v_job,r_emp.v_sal 已有值;

给变量赋值: r_employee r_record; r_employee.v_ename :=‘JACK’; r_employee.v_job :=‘CLERK’;

r_employee.v_sal := 890.98;

PL/SQL 基础

Page 19: PL/SQL 基础培训

PL/SQL 的记录类型 PL/SQL 的复合类型主要包括 record 记录类型。 record 复合数据类型在

使用前必须定义,类似 C 语言的结构体类型。也可以使用 rowtype 来定义。

例 1 :declare type recTypeStudent is record(

sname varchar2(10), age number(2) );v_recStu recTypeStudent;

beginv_recStu.sname := 'zhang';v_recStu.age := 20;dbms_output.put_line ( v_recStu.sname || ' ' || v_recStu.age );end;

例 2 :定义一个记录类型 , 接收 7369 号员工的信息并打印。(独立实现)

declare type rec_emp is record (

v_empno emp.empno%type,v_ename emp.ename%type,v_sal emp.sal%type

); v_empinfo rec_emp; begin select empno,ename,sal into v_empinfo from emp where empno=7369; dbms_output.put_line(v_empinfo.v_empno||' '||v_empinfo.v_ename);end;

declare

v_emp emp%rowtype;

begin

select * into v_emp

from emp

where empno=7369;

dbms_output.put_line(v_emp.empno);

end;

PL/SQL 基础

Page 20: PL/SQL 基础培训

TABLE 类型数据 

PL/SQL 中的表 (table) 类型类似于C语言中的结构类型数组.

定义方法: TYPE table_emp IS TABLE OF emp . ename %TYPE INDEX BY BINARY_INTEGER;

一个 PL/SQL表有两个列, (key, value)1. key 列类型即是 BINARY_INTEGER

2. value 类型则是所定义的数据类型.

table 类型使用:  定义变量 my_name 为 table_emp 类型 ,则可以使用变量

my_name ,也可以在 SQL 语句中使用 table 类型变量.      my_name table_emp; my_name(0) :=‘SCOTT’; my_name(1) :=‘SMITH’; my_name(2) :=‘SUSAN’;

SELECT ename INTO my_name(10) FROM emp WHERE empno = 7934;

PL/SQL 基础

Page 21: PL/SQL 基础培训

用法演示

PL/SQL 基础

Page 22: PL/SQL 基础培训

PL/SQL 中数据类型之间的转换:使用 to_char、 to_date、 to_number 来进行显性的数据类型转换。例如:declare v_i number; v_j number;begin v_i:=1; v_j:=2; dbms_output.put_line(concat('i+j=',to_char(v_i+v_j)));end;Oracle 中的表达式和运算符:( 1)Oracle 的过程语句中使用的函数:

1 )单行数值函数: mod、 round、 trunc、 ceil、 floor 。2 )单行字符函数:chr、 concat、 initcap、 length、 lower、 lpad、 rpad、 ltrim 、

replace、 rtrim、 substr、 trim、 upper3 )日期函数: add_months、 last_day、months_between、 next_day、 round、

sysdate、 trunc4 )转换函数: to_char、 to_number、 to_date

( 2 )在 oracle 中不能直接使用的函数: decode 、组函数

PL/SQL 基础

Page 23: PL/SQL 基础培训

函数用法:1.instr 函数• 格式为

INSTR(源字符串 , 要查找的字符串 , 从第几个字符开始 , 要找到第几个匹配的序号 )

返回找到的位置,如果找不到则返回 0.

• 例如: INSTR('CORPORATE FLOOR','OR', 3, 2) 中,源字符串为 'CORPORATE FLOOR', 在字符串中查找 'OR' ,从第三个字符位置开始查找 "OR" ,取第三个字后第 2 个匹配项的位置。

• 默认查找顺序为从左到右。当起始位置为负数的时候,从右边开始查找。

• 所以 SELECT INSTR('CORPORATE FLOOR', 'OR', -1, 1) "aaa" FROM DUAL 的显示结果是– Instring

——————14

PL/SQL 基础

Page 24: PL/SQL 基础培训

函数用法:2.substr 函数

• 取得字符串中指定起始位置和长度的字符串 – substr( string, start_position, [ length ] )

如 :     substr(‘This is a test’, 6, 2)     would return ‘is’     substr(‘This is a test’, 6)     would return ‘is a test’     substr(‘TechOnTheNet’, -3, 3)     would return ‘Net’     substr(‘TechOnTheNet’, -6, 3)     would return ‘The’ 

select substr('Thisisatest', -4, 2) value from dual

PL/SQL 基础

Page 25: PL/SQL 基础培训

综合应用:1.

• SELECT INSTR('CORPORATE FLOOR', 'OR', -1, 1) "Instring"

FROM DUAL--INSTR( 源字符串 , 目标字符串 , 起始位置 , 匹配序号 )

• SELECT INSTR('CORPORATE FLOOR','OR', 3, 2) "Instring"

FROM DUAL• SELECT INSTR('32.8,63.5',',', 1, 1) "Instring" FROM DUAL

• SELECT SUBSTR('32.8,63.5',INSTR('32.8,63.5',',', 1, 1)+1) "INSTRING" FROM DUAL

• SELECT SUBSTR('32.8,63.5',1,INSTR('32.8,63.5',',', 1, 1)-1) "INSTRING" FROM DUAL

PL/SQL 基础

Page 26: PL/SQL 基础培训

综合应用:2. DECLARE

  -- LOCAL VARIABLES HERE  T   VARCHAR2(2000);  S   VARCHAR2(2000);  NUM INTEGER;  I   INTEGER;  POS INTEGER;BEGIN  -- TEST STATEMENTS HERE  T := '12.3,23.0;45.6,54.2;32.8,63.5;';  SELECT LENGTH(T) - LENGTH(REPLACE(T, ';', '')) INTO NUM FROM DUAL;  DBMS_OUTPUT.PUT_LINE('NUM:' || NUM);  POS := 0;  FOR I IN 1 .. NUM LOOP    DBMS_OUTPUT.PUT_LINE('I:' || I);    DBMS_OUTPUT.PUT_LINE('POS:' || POS);    DBMS_OUTPUT.PUT_LINE('==:' || INSTR(T, ';', 1, I));    DBMS_OUTPUT.PUT_LINE('INSTR:' || SUBSTR(T, POS + 1, INSTR(T, ';', 1, I) - 1));    POS := INSTR(T, ';', 1, I);  END LOOP;END;

PL/SQL 基础

Page 27: PL/SQL 基础培训

函数用法:3.decode 函数• 格式

– decode( 条件 ,值 1, 翻译值 1,值 2, 翻译值 2,...值 n, 翻译值 n, 缺省值 )

Eg1:

假设我们想给职员加工资,其标准是:工资在 8000元以下的将加20%;工资在 8000元以上的加 15% select decode(sign(salary - 8000),1,salary*1.15,-1,salary*1.2)

as salary

from employee

PL/SQL 基础

Page 28: PL/SQL 基础培训

函数用法:4. substr与 substrb 函数

substr 是按照字来算的,而 substrb() 是按照字节来计算。– SQL> select substr('今天是个好日子 ',3,5) from dual;

----------是个好日子

– SQL> select substrb(‘今天是个好日子’ ,3,5) from dual;-----天是

length与 lengthb 长度计算函数 • select length(' 你好 ') from dual         ----output:2• select lengthb(' 你好 ') from dual       ----output :4

Instr与 Instrb 字符串查找函数 instr(原字符串 , 查的字符串 , 起始位置 ,

第几个匹配 ) 返回字符串位置 ,找不到返回 0 .• select instr(' 日日花前长病酒 ',' 花前 ',1,1) from dual     ----output:3• select instrb(' 日日花前长病酒 ',' 花前 ',1,1) from dual     ----output:5

PL/SQL 基础

Page 29: PL/SQL 基础培训

Oracle 中可以使用的操作符:1 )算术操作符:+、-、 *、 / 、 ** (乘方)2 )关系运算符: <、 <=、 >、 >=、 =、 <>、 !=

3 )其他的比较运算符: is null、 like、 between…and、 in

4 )逻辑运算符: and、 or、 not

5 )其他操作符 ||、 :=

PL/SQL 中的控制结构:( 1 )条件语句:

if 条件 1 then

语句体;elsif 条件 2 then

语句体; ……….

else

语句体;endif;

例:用上面的结构,写一个分时问候的程序

set serveroutput on

declare

v_time number(2);

begin

v_time := to_char(sysdate,'hh24');

if v_time >= 6 and v_time <= 8 then

dbms_output.put_line(' 起床。 ');

elsif v_time > 8 and v_time < 17 then

dbms_output.put_line(' 工作 ');

elsif v_time >= 18 and v_time <= 22 then

dbms_output.put_line(' 下班 ');

else

dbms_output.put_line(' 睡觉 ');

end if;

end;

PL/SQL 基础

Page 30: PL/SQL 基础培训

( 2 )条件语句的嵌套使用:在 if或 if…else 中可以嵌套 if或 if…else

例 1 :取出雇员 ID为 7369的薪水,如果 <1200,则输出‘ low’,如果<2000,

则输出 'middle',否则输出 'high'例 2 :判断当前年分是否是闰年。

declare

v_year number;

begin

v_year : =to_char(sysdate,'yyyy');

if mod(v_year,4)=0 and mod(v_year,100)!=0 or mod(v_year,400)=0 then

dbms_output.put_line('The year is '||v_year);

dbms_output.put_line('The year is leap year');

else

dbms_output.put_line('The year is not leap year');

end if;

end;

例 2 :打印今年当前月份的天数。(独立实现)

PL/SQL 基础

Page 31: PL/SQL 基础培训

declare

v_year number;

v_days number;

v_month number;

v_leapyear boolean;

begin

v_year:=to_char(sysdate,'yyyy');

v_month:=to_char(sysdate,'mm');

v_leapyear:=( mod(v_year,4)=0 and mod(v_year,100)!=0 or mod(v_year,400)=0);

……..

dbms_output.put_line(v_days);

end;

if v_leapyear then if (v_month=1 or v_month=3 or v_month=5 or v_month=7 or v_month=8 or v_month=10 or v_month=12) then v_days:=31; elsif (v_month=4 or v_month=6 or v_month=8 or v_month=10) then v_days:=30; else v_days:=29; end if; else if (v_month=1 or v_month=3 or v_month=5 or v_month=7 or v_month=8 or v_month=10 or v_month=12) then v_days:=31; elsif (v_month=4 or v_month=6 or v_month=8 or v_month=10 )then v_days:=30; else v_days:=28; end if; end if;

Page 32: PL/SQL 基础培训

(3) PL/SQL :CASE 结构CASE

WHEN 条件表达式 1 THEN

语句段 1

WHEN 条件表达式 2 THEN

语句段 2

……

ELSE

语句段 N

END CASE;

PL/SQL 基础

Page 33: PL/SQL 基础培训

示例: CASE 结构

PL/SQL 基础

declare v_grade char :='A';begin

case v_grade when 'A' then DBMS_OUTPUT.put_line('Excellent');

when 'B' then DBMS_OUTPUT.put_line('Very good');

when 'C' then DBMS_OUTPUT.put_line('Good');

else DBMS_OUTPUT.put_line('No such grade'); end case;

end;

Page 34: PL/SQL 基础培训

循环结构:1 )简单循环:循环体至少循环一次, loop 语法如下:

loop

语句体; [exit;]

end loop;

例 1 :打印 1, 2, 3…20;

例 2 :求 1+ 3+ 5+… 25;

注意:也可以使用 exit when 来结束循环。例 3 :将上面的两个例子用 exit when 改写。

declare

v_x number;

begin

v_x:=1;

loop

dbms_output.put_line(v_x);

v_x:=v_x+1;

if v_x>20 then

exit;

end if;

end loop;

end;

declare

v_x number;

v_sum number;

begin

v_sum:=0;

v_x:=1;

loop

v_sum:=v_sum+v_x;

v_x:=v_x+1;

if v_x>=25 then

exit;

end if;

end loop;

dbms_output.put_line(v_sum);

end;

PL/SQL 基础

Page 35: PL/SQL 基础培训

2) while 循环语法: while 条件 loop

语句体; end loop;

declare

v_x number;

v_i number;

begin

v_x:=1;

v_i:=0;

while v_x<=100 loop

dbms_output.put(v_x||' ');

v_x:=v_x+1;

v_i:=v_i+1;

if mod(v_i,10)=0 then

dbms_output.put_line('');

end if;

end loop;

end;

PL/SQL 基础

Page 36: PL/SQL 基础培训

2) for 循环语法:

例 1 :

declare

v_i number:=1;

begin

for counter in 1..10 loop

dbms_output.put_line(v_i);

v_i:=v_i+1;

end loop;

end;

PL/SQL 基础

Page 37: PL/SQL 基础培训

2) for 循环语法:

例 2 : set serveroutput on

declare v_x number;

begin v_x:=0; for counter in 1..10 loop dbms_output.put_line(v_x); v_x:=v_x+1; end loop; dbms_output.put_line(v_x); end;

PL/SQL 基础

Page 38: PL/SQL 基础培训

练习:例 1 :如想获得 7369号员工的信息,并打印出来。例 2 :打印 bu1032图书的基本信息和销售信息,包括书号、书名、类型、价格

、销售日期和销售量、及出版社名称和作者信息。(独立实现)。

declare

v_empno number(4);

v_ename varchar2(10);

v_job varchar2(9);

v_mgr number(4);

v_hiredate date;

v_sal number(7,2);

v_comm number(7,2);

v_deptno number(2);

begin

select *

into v_empno,v_ename,v_job,v_mgr,v_hiredate,v_sal,v_comm,v_deptno

from emp where empno=7369;

dbms_output.put_line(v_empno||' '||v_ename||' '||v_job||' '||v_hiredate||' '||v_sal||' '||v_deptno);

exception

when others then

null;

end;

PL/SQL 基础

Page 39: PL/SQL 基础培训

INSERT语句的使用  Declare v_empno EMP . empno%TYPE :=1234; v_ename EMP . ename%TYPE :=‘SCOTT’; v_job VARCHAR2(15) :=‘MANAGER’; v_deptno EMP . deptno%TYPE :=20; v_sal NUMBER(7,2) :=890.50;

Begin INSERT INTO emp(empno, ename, job, hiredate, sal, deptno) VALUES(v_empno, v_ename, v_job, SYSDATE, v_sal, v_deptno);

dbms_output.put_line(sql%rowcount || " 条记录被影响! "); END;

/

注意 :非空 (NOT NULL) 必须有值

PL/SQL 基础

Page 40: PL/SQL 基础培训

DELETE 语句的使用 Declare v_empno EMP.empno%TYPE :=1234; Begin DELETE FROM emp WHERE empno=v_empno; End;

事务处理语句的使用在 PL/SQL 中可以使用 SQL 的 COMMIT,ROLLBACK 及 SAVEPOINT 语句.Declare v_empno EMP.empno%TYPE := 1234;Begin DELETE FROM emp WHERE empno = v_empno; COMMIT; End; /

PL/SQL 基础

Page 41: PL/SQL 基础培训

在 PL/SQL 中执行 DDL 语句

begin

execute immediate

'create table t ( num varchar2(20) default ''hello'')';

end

PL/SQL 基础

Page 42: PL/SQL 基础培训

例外处理 (EXCEPTION)

许多编写得很好的程序都必须能够正确处理各种出错情况,并且尽可能地从错误中进行恢复。异常处理方法是程序对运行时刻错误作出反应并进行处理的方法。当引发一个异常情况时,控制便会转给块的异常处理部分。

异常处理部分的语法如下:EXCEPTION

WHEN 异常情况 1 THEN

语句序列 1 ; WHEN 异常情况 2 THEN

语句序列 2 ; ...

WHEN OTHERS THEN

语句序列 3;

END;

OTHERS 异常处理将对所有引发的异常情况执行其代码。它应该是块中最后一个处理语句,确保所有的错误都被检测到。但 OTHERS 只是简单地记录发生了错误,而没有记录发生的是哪一个错误。我们可以在 OTHERS 中用预定义函数

SQLCODE

和 SQLERRM 来决定引发异常处理的是哪个错误。

异常情况包括系统预定义的异常情况、用户自定义的异常情况。

Page 43: PL/SQL 基础培训

例外处理 (EXCEPTION)

⑴ 系统预定义的异常情况

ORACLE 有一些预定义的异常情况和大多数通常的 ORACLE 错误是对

应的。这些异常情况所使用的标识符在包 STANDARD90 中进行定义。在程序的异常处理部分直接对它们进行处理。

Page 44: PL/SQL 基础培训

预定义的ORACLE异常情况

ORACLE错误 对应的异常情况 说明

ORA-0001 DUP_VAL_ON_I NDEX 唯一值约束被破坏

ORA-0051 TI MEOUT_ON_RESOURCE 在等待资源时发生超时现象

ORA-0061 TRANSACTI ON_BACKED_OUT 由于发生死锁事物处理被撤消了

ORA-1001 I NVALI D_CURSOR 非法的游标操作

ORA-1012 NOT_LOGGED_ON 没有连接到 ORACLE

ORA-1017 LOGI N_DENI ED 无效的用户名/口令

ORA-1403 NO_DATA_FOUND 没有找到数据

ORA-1422 TOO_MANY_ROWS SELECT. . . I NTO语句匹配多个行

ORA-1476 ZERO_DI VI DE 被零除

ORA-1722 I NVALI D_NUMBER 转换为一个数字失败。

Page 45: PL/SQL 基础培训

ORA-6500 STORAGE_ERROR 如果 PL/ SQL 运行时内存不够就引发内部的 PL/SQL 错误

ORA-6501 PROGRAM_ERROR 内部 PL/ SQL错误

ORA-6502 VALUE_ERROR 截尾、算术或转换错误

ORA-6504 ROWTYPE_MI SMATCH 宿主游标变量和 PL/SQL 游标变量有不兼容的行类型

ORA-6511 CURSOR_ALREADY_OPEN 试图打开已存在的游标

ORA-6530 ACCESS_I NTO_NULL 试图为 NULL 对象的属性赋值

ORA-6531 COLLECTI ON_I S_NULL 试图将 EXI STS 以外的集合方法应用于一个NULL PL/ SQL

表或 VARRAY上

ORA-6532 SUBSCRI PT_OUTSI DE_LI MI T 对嵌套表或 VARRAY 索引的引用超出说明范围以外

ORA-6533 SUBSCRI PT_BEYOND_COUNT 对嵌套表或 VARRAY 索引的引用大于集合中元素的个数

Page 46: PL/SQL 基础培训

⑵ 用户自定义的异常情况

用户自定义的异常情况是程序定义的一个错误。程序所定义的这个错误并不一定非是一个 ORACLE 错误,它可能是与数据相关的一个错误。

用户定义的异常情况的处理分三步:

Page 47: PL/SQL 基础培训

· 定义异常情况 用户定义的异常情况是在 PL/SQL 块的说明部分进行定义的,和变量相类似,异常情况有一个类型和作用域。例如: my_exception exception;

· 触发异常情况 当一个异常情况相关的错误出现时,就会引发该异常情况。用户定义的异常情况是通过显式使用 RAISE 语句来引发的,而预定义的异常情况是当相关的 ORACLE 错误发生时被隐式触发的。例如: RAISE MY_EXCEPTION ;· 在程序的异常处理部分对定义的异常情况进行处理。

例如: WHEN MY_EXCEPTION THEN

...

⑵ 用户自定义的异常情况

Page 48: PL/SQL 基础培训

例 1 :DECLARE

tin_rec tin % rowtype ;

v_passwd user.passwd % type ;

err_ps EXCEPTION ;

BEGIN

select * into tin_rec from tin ;

select passwd into v_passwd from user

where userid = tin_rec.uid ;

if tin_rec.ps = v_passwd then

insert into tout values(‘ login ok’ );

else

raise err_ps ;

end if ;

⑵ 用户自定义的异常情况

Page 49: PL/SQL 基础培训

exception

when err_ps then

insert into tout values(‘ password error’ 〕; when no_data_found then

insert into tout values(‘ userid error’ 〕;end;

⑵ 用户自定义的异常情况

Page 50: PL/SQL 基础培训

例 2 :declare e_toomanystudent exception; v_currentstudent number(3); v_maxstudent number(3); v_errorcode number; v_errortext varchar2(200);begin select current_student,max_students into v_currentstudent,v_maxstudent from classes where department=’HIS’ and course=101; if v_currentstudent > v_maxstudent then raise e_toomanyexception; end if;

⑵ 用户自定义的异常情况

Page 51: PL/SQL 基础培训

exception

when no_data_found or too_many_rows then

dbms_output.put_line(‘ 发生系统预定义错误‘ ) ;when e_toomanyexception then

insert into log_table(info)

values(‘history 101 has ‘||v_currentstudent);

when others then

v_errorcoed:=sqlcode;

v_errortext:=substr(sqlerrm,1,200);

insert into log_table(code,message,info)

values(v_errorcode,v_errortext,’oracle error occured’);

end;

/

⑵ 用户自定义的异常情况

Page 52: PL/SQL 基础培训

例外处理 (EXCEPTION)

在 PL/SQL 中,警告信息、出错信息、或返回信息统称为例外 (Exception)

有两中类型的例外。 Oracle预定义的例外 : 是由 PL/SQL 运行过程中,系统自动产生的信息。 用户定义例外 : 是用户根据需要,自己定义使用的例外,执行时

由用户自己引起。

预定义的例外CURSOR_ALREADY_OPEN VALUE_ERROR(算术、转换、截断或大小约束错误 )

NO_DATA_FOUND INVALID_NUMBER( 字符串转换为数字时失败 )TOO_MANY_ROWS ZERO_DIVIDEINVALID_CURSOR DUP_VAL_ON_INDEX( 给唯一约束列插入重复的值 )

用户自定义例外 用户定义的例外必须在 DECLARE段中说明,在Begin段中用 RAISE 引起,在 EXCEPTION段中使用。

Page 53: PL/SQL 基础培训

例外处理 (EXCEPTION)

declarev_temp number(4);

beginselect empno into v_temp from emp where deptno = 10;

exceptionwhen too_many_rows then

dbms_output.put_line(' 太多记录 ');when others then

dbms_output.put_line('error');end;

declarev_temp number(4);

beginselect empno into v_temp from emp where deptno = 1111;

exceptionwhen no_data_found then

dbms_output.put_line(' 没有数据 ');when others then

dbms_output.put_line('error');end;

Page 54: PL/SQL 基础培训

例外处理 (EXCEPTION)

declare v_empinfo emp%rowtype;begin

select * into v_empinfo from emp where empno=7369; insert into emp(empno,ename) values(v_empinfo.empno,v_empinfo.ename);

exception when dup_val_on_index then dbms_output.put_line(' 插入重复值 '); when others then null;

end;

Page 55: PL/SQL 基础培训

例外处理 (EXCEPTION)

--将错误信息保存到表中create table errorlog (

id number primary key,errorcode number,errormsg varchar(1024),errordate date

);-- 为了让 id 字段自动递增则:create sequence seq_errorlog_id start with 1 increment by 1;

Page 56: PL/SQL 基础培训

例外处理 (EXCEPTION)

示例:declare

v_deptnodept.deptno%type := 10;v_errcode number;v_errmsgvarchar2(1024);

begindelete from dept where deptno = v_deptno;commit;

exceptionwhen others then

rollback;v_errcode := SQLCODE;v_errmsg := SQLERRM;

insert into errorlog values(seq_errorlog_id.nextval,v_errcode,v_errmsg,sysdate);commit;

end;

select to_char(errordate,'yyyy-mm-dd HH24:MI:SS') from errorlog;

Page 57: PL/SQL 基础培训

游标 (CURSOR) 设计

游标的类型:一是隐性游标 二是显性游标

使用 select命令查询获得一行记录就是使用了隐性游标,它的特点是不用使用游标的声明和只能操作一条记录;而显性的游标必须经过严格的声明、打开和提取操作。

语法: CURSOR 光标名 (参数 ) IS SELECT 字句;

打开游标 获取活动集中的行 FETCH 语句检索活动集中的行,每次一行,每执行一次 FECTCH ,光

标前进到活动集中的下一行。 游标下移 关闭游标

Page 58: PL/SQL 基础培训

游标属性

游标的属性

从游标工作区中逐一提取数据,可以在循环中完成。但循环的开始以及结束,必须以游标的属性为依据。

游标属性及其描述如下:

Page 59: PL/SQL 基础培训

游标属性 描 述

游标名%I SOPEN 值为布尔型,如果游标已打开,取值为 TRUE

游标名%NOTFOUND 值为布尔型,如果最近一次 FETCH操作没有返回结果,则取值为 TRUE

游标名%FOUND 值为布尔型,如果最近一次 FETCH操作没有返回结果,则取值为 FALSE。否则,为 TRUE

游标名%ROWCOUNT 值为数字型,值是到当前为止返回的记录数

游标属性

Page 60: PL/SQL 基础培训

• eg1:

declare

cursor c is

select * from emp;

v_emp c%rowtype;

begin

open c;

fetch c into v_emp;

dbms_output.put_line(v_emp.ename);

close c;

end;

演示:

Page 61: PL/SQL 基础培训

eg2:

declare

cursor c is

select * from emp;

v_emp c%rowtype;

begin

open c;

loop

fetch c into v_emp;

exit when(c%notfound);

dbms_output.put_line(v_emp.ename);

end loop;

close c;

end;

演示:

Page 62: PL/SQL 基础培训

eg3: while 循环

declare

cursor c is

select * from emp;

v_emp c%rowtype;

begin

open c;

fetch c into v_emp;

while (c%found) loop

fetch c into v_emp;

dbms_output.put_line(v_emp.ename);

end loop;

close c;

end;

演示:

Page 63: PL/SQL 基础培训

eg4: for 循环

declare

cursor c(v_deptno emp.deptno%type,v_job emp.job%type)

is

select ename,sal from emp where deptno = v_deptno and job = v_job;

begin

for v_temp in c(30,'CLERK') loop

dbms_output.put_line(v_temp.ename);

end loop;

end;

演示:

Page 64: PL/SQL 基础培训

例 1 :通过游标打印所有员工的信息:set serveroutput ondeclare cursor cur_emp is

select empno,ename,job,hiredate,sal from emp; v_empno emp.empno%type; v_ename char(10); v_job char(10); v_hiredate emp.hiredate%type; v_sal emp.sal%type;

begin open cur_emp; dbms_output.put_line(' 工号 姓名 工作 入职时间 工资 '); dbms_output.put_line(' -----------------------------------------------------------'); loop fetch cur_emp into v_empno,v_ename,v_job,v_hiredate,v_sal; dbms_output.put_line(' '||v_empno||' '||v_ename||' '||v_job||' '||v_hiredate||' '||v_sal); dbms_output.put_line(' ----------------------------------------------------------'); exit when cur_emp%notfound; end loop; close cur_emp;exception

when invalid_cursor then dbms_output.put_line(' 无效游标。 '); when cursor_already_open then dbms_output.put_line(' 游标已经打开。 '); when others then dbms_output.put_line(' 其他异常 ');

end;

Page 65: PL/SQL 基础培训

例 2 :打印所有部门信息,用 while循环实现提取数据的操作。declare cursor c_dept is

select dname,loc from dept; v_dname dept.dname%type; v_loc dept.loc%type;

begin open c_dept; fetch c_dept into v_dname,v_loc; while c_dept%found loop

dbms_output.put_line(c_dept%rowcount); dbms_output.put_line(v_dname||' '||v_loc); fetch c_dept into v_dname,v_loc;

end loop; close c_dept;exception

when invalid_cursor then dbms_output.put_line(' 无效游标。 '); when cursor_already_open then dbms_output.put_line(' 游标已经打开。 '); when others then dbms_output.put_line(' 其他异常 ');

end;

Page 66: PL/SQL 基础培训

例 3 :打印工资最高的前几行记录:create or replace procedure print_emp

(

p_top number

)

as

cursor c_emp is

select ename,job,sal

from emp

where sal in (select sal

from (select distinct sal

from emp

order by sal desc) tab1

where rownum <= p_top)

order by sal desc;

v_ename emp.ename%type;

v_job emp.job%type;

v_sal emp.sal%type;

begin

open c_emp;

loop fetch c_emp into v_ename,v_job,v_sal; exit when c_emp%notfound; dbms_output.put_line ( v_ename||'

'||v_job||' '||v_sal);

end loop; close c_emp;exception when others then null;end print_emp;----------------exec print_emp(4)

Page 67: PL/SQL 基础培训

游标的属性:( 1)%isopen 判断游标是否被打开,如果打开, %isopen为 true, 否则为 false 。( 2)%found,%notfound 判断游标是否指向有效记录,如果有效,则 %found为

true , 那么 %notfound为 false ,否则 %found为 false ,那么 %notfound为 true 。( 3)%rowcount 返回当前位置游标读取的记录数。例如:打印工资高于 3000 的最低工资和此人的姓名,工作。declare

v_ename emp.ename%type;

v_job emp.job%type; v_sal emp.sal%type;

cursor cur_empsal is

select ename,job,sal from emp where sal>3000 order by sal;

begin

if cur_empsal%isopen=false then

open cur_empsal;

end if;

fetch cur_empsal into v_ename,v_job,v_sal;

while cur_empsal%found loop

if cur_empsal%rowcount=2 then

exit;

end if;

fetch cur_empsal into v_ename,v_job,v_sal;

end loop; ………………………

dbms_output.put_line(v_ename||' '||v_job||' '||v_sal); exception when invalid_cursor then dbms_output.put_line(' 无效游标 '); when others then null;end;

Page 68: PL/SQL 基础培训

游标 for循环: 游标 for 循环是一种快捷的处理游标的方式,它使用 for 循环依次读取结

果集中的行数据 ,当 for 循环开始时,游标自动打开,每循环一次系统自动读取游标当前行的数据,当退出 for 循环时,游标被自动关闭。注意:当使用游标 for 循环时不能使用 open、 fetch、 close 语句,否则会发生错误。例如打印工资大于 2500 小于 4000 的员工的信息,包括姓名,工资,部门。declare v_ename emp.ename%type; v_sal emp.sal%type; v_deptno emp.deptno%type; cursor cur_salary is select ename,sal,deptno from emp where sal>=2500 and sal<=4000;begin for cur_empsalary in cur_salary loop v_ename:=cur_empsalary.ename; v_sal:=cur_empsalary.sal; v_deptno:=cur_empsalary.deptno; dbms_output.put_line(v_ename||' '||v_sal||' '||v_deptno); end loop;exception when invalid_cursor then null; when others then null;end;

Page 69: PL/SQL 基础培训

5 、带参数的游标:与存储过程相似,可以将参数传给游标并在查询中使用,对处理在某些条件下打开的游标的情况非常有用。

语法如下:cursor cursor_name(parameter datatype [default value][,…])

is select statement;

注意: 与存储过程不同,游标只能有 In参数,参数定义类型不能指定长度。参数

也可以有缺省值,游标定义的参数只能在游标内使用,不要在超出游标以外的程序中使用。给游标参数传递值在游标打开时进行。

语法如下: open cursor_name(value[,…]);

例 1 :查找所有部门的部门号,部门名称,部门的工资工资总额。

Page 70: PL/SQL 基础培训

declare cursor curdept is select * from dept order by deptno; cursor cur_e(p_deptno dept.deptno%type) is select nvl(sum(e.sal),0) from dept d , emp e where d.deptno=e.deptno and d.deptno=p_deptno;

r_dept dept%rowtype; v_deptno dept.deptno%type; v_dname dept.dname%type; v_sumsal number;begin open curdept; loop

fetch curdept into r_dept; exit when curdept%notfound; dbms_output.put_line(' 部门号 :'||r_dept.deptno||' '||r_dept.dname); v_sumsal := 0; open cur_e(r_dept.deptno); fetch cur_e into v_sumsal; dbms_output.put_line(' 总工资 :'||v_sumsal); close cur_e;

end loop; close curdept; end;

Page 71: PL/SQL 基础培训

游标中的更新和删除

• UPDATE或 DELETE 语句中的 WHERE CURRENT OF子串专门处理要执行 UPDATE或 DELETE 操作的表中取出的最近的数据。要使用这个方法,在声明游标时必须使用 FOR UPDATE子串,当对话使用 FOR UPDATE子串打开一个游标时,所有返回集中的数据行都将处于行级( ROW-LEVEL)独占式锁定,其他对象只能查询这些数据行,不能进行 UPDATE、 DELETE或 SELECT...FOR UPDATE 操作。

• 语法:

FOR UPDATE [OF [schema.]table.column[,[schema.]table.column]..

• 在多表查询中,使用 OF子句来锁定特定的表 , 如果忽略了 OF子句,那么所有表中选择的数据行都将被锁定。如果这些数据行已经被其他会话锁定,那么正常情况下 ORACLE 将等待,直到数据行解锁。

在 UPDATE和 DELETE 中使用 WHERE CURRENT OF子串的语法如下:

WHERE{CURRENT OF cursor_name|search_condition}

Page 72: PL/SQL 基础培训

游标中的更新和删除 • DELCARE

CURSOR c1 IS SELECT empno,salaryFROM empWHERE comm IS NULLFOR UPDATE OF comm;

v_comm NUMBER(10,2);

BEGIN

FOR r1 IN c1 LOOP

IF r1.salary<500 THENv_comm:=r1.salary*0.25;

ELSEIF r1.salary<1000 THENv_comm:=r1.salary*0.20;

ELSEIF r1.salary<3000 THENv_comm:=r1.salary*0.15;

ELSEv_comm:=r1.salary*0.12;

END IF;

UPDATE emp;SET comm=v_commWHERE CURRENT OF c1l;

END LOOP;END

Page 73: PL/SQL 基础培训

过 程

• 过程( PROCEDURE ):可以没有返回值。• 基本格式如下:

CREATE [ OR REPLACE ] PROCEDURE 过程名 (

参数名 1 IN/OUT 参数类型 ,

参数名 2 IN/OUT 参数类型 )

IS

{ 变量声明部分 }

BEGIN

{ 过程部分 }

EXCEPTION

{ 异常处理部分 }

END;

Page 74: PL/SQL 基础培训

用法演示:

--存储过程a.不带参数create or replace procedure pis

cursor c isselect * from emp for update;

beginfor v_temp in c loop

if(v_temp.deptno = 10) thenupdate emp set sal = sal + 10 where current of c;

elsif(v_temp.deptno = 20) thenupdate emp set sal = sal + 20 where current of c;

elseupdate emp set sal = sal + 50 where current of c;

end if;end loop;commit;

end;

执行存储过程 (2种方法 ) :1. exec p;2. begin

p; end;

Page 75: PL/SQL 基础培训

用法演示:

--存储过程b.带参数

create or replace procedure p(v_a in number,v_b number,v_ret out number,v_temp in out number)isbegin

if(v_a > v_b) thenv_ret := v_a;

elsev_ret := v_b;

end if;v_temp := v_temp + 1;

end;

调用:declare

v_a number := 3;v_b number := 4;v_ret number;v_temp number := 5;

beginp(v_a,v_b,v_ret,v_temp);dbms_output.put_line(v_ret);dbms_output.put_line(v_temp);

end;

--show error; 如果有错误,则显示错误信息--drop procedure p; 删除存储过程

存储过程的查看

select * from user_procedures where object_name = ‘ 过程名 ';

Page 76: PL/SQL 基础培训

用法演示:

--存储过程b.带参数• create or replace procedure query_emp

(v_no in emp.empno%type, v_name out emp.ename%type, v_sal out emp.sal%type)is e_sal_error exception;begin

select ename,sal into v_name,v_sal from emp where empno=v_no;if v_sal >=2500 then dbms_output.put_line(' 该雇员工资 :'||v_no); raise e_sal_error;end if;exception

when no_data_found then dbms_output.put_line(' 没有该雇员 :'||v_no); when e_sal_error then dbms_output.put_line(' 该雇员工资高于 2500了 ');end query_emp;

Page 77: PL/SQL 基础培训

用法演示:

--存储过程b.带参数

存储过程的调用

variable a1 varchar2(16);variable a2 number;execute

query_emp(7788,:a1,:a2);或

declare v_a1 emp.ename%type; v_a2 emp.sal%type;

begin query_emp(v_name => v_a1,v_sal => v_a2,v_no =>5678);

end;

Page 78: PL/SQL 基础培训

示例:

p_stat_ryt_bill_tmp_new.prc

p_stat_ryt_bill_tmp_old.prc

Page 79: PL/SQL 基础培训

函 数

• PL/SQL :• 函数

– 函数用于计算和返回特定的数据 . 可以将经常需要进行的计算写成函数 . 函数的调用是表达式的一部分 , 而过程的调用是一条 PL/SQL语句 .

– 语法:create [or replace] function fun_name

([para_name [in | out | in out) type [, ….]])

return type

is | as

声明部分begin

statement;

end fun_name;

Page 80: PL/SQL 基础培训

例如:利用函数实现一个查询获取某雇员的工资 CREATE OR REPLACE FUNCTION get_sal (v_emp_no IN emp.empno% TYPE)

RETURN NUMBER

IS v_emp_sal emp.sal% TYPE:= 0 ;

BEGIN

SELECT sal INTO v_emp_sal FROM emp WHERE empno=v_emp_no; RETURN(v_emp_sal) ;

END get_sal ;

函 数

Page 81: PL/SQL 基础培训

函 数

• 用法演示:--创建函数:

create or replace function sal_tax

( v_sal number )

return number

is

begin

if(v_sal < 2000) then

return 0.10;

elsif(v_sal < 2750) then

return 0.15;

else

return 0.20;

end if;

end;

--使用函数select sal_tax(sal) from emp;

Page 82: PL/SQL 基础培训

函 数 • 用法演示:

--创建函数:create or replace function get_salary_by_deptno(

v_dept_no in emp.deptno%type,v_emp_cnt out number

)return number

is v_sum number(10,2);begin

select sum(sal),count(*) into v_sum,v_emp_cnt from emp where deptno = v_dept_no;

return v_sum;end get_salary_by_deptno;

Page 83: PL/SQL 基础培训

函 数

• 用法演示:

调用函数 1

variable a1 number;variable a2 number;execute :a1 := get_salary_by_deptno (10,:a2);

print :a1 :a2

• PL/SQL :• 函数的查看

– select text from user_source where name = ‘ 函数名‘• 删除

– drop function 函数名 ;

Page 84: PL/SQL 基础培训

函 数

• 用法演示:

调用函数 2set serveroutput on;declare v_a1 emp.deptno%type; v_a2 number; v_sum number(10,2);begin v_sum := get_salary_by_deptno(v_emp_cnt => v_a1,v_dept_no => 10); if v_a1 = 0 then dbms_output.put_line(' 该部门无人 '); else dbms_output.put_line(' 该部门工资总和 :'||v_sum||' 人数 '||v_a2); end if;end;

Page 85: PL/SQL 基础培训

触发器• 触发器是一种特殊的存储过程,也就是说触发器具有存储过程的所有优势,是命名

程序的一种,也是存储并运行在服务器端的。说其具有特殊性,是因为触发器的调用执行和存储过程不一样,存储过程的程序调用执行必须由程序员事先设计并编写好调用程序代码及对应的参数值,即存储过程的调用执行由程序员决定,而触发器程序不能被应用程序调用,也没有参数,当触发器程序创建并保存在数据库中后只要对应触发器触发器事件的发生,该触发器就会被自动调用执行。

1 、触发器程序的分类,( 1 )按触发事件分为:

1) DML 触发器:由 insert 、 update 、 delete 等操作触发的触发器称为 DML触发器。 DML 触发器是传统的触发器,可以使用 DML 触发器实现复杂的数据完整性。

2) DDL 触发器:由 create、 alter、 drop 操作触发的触发器称为 DDL 触发器。使用 DDL 触发器实现跟踪用户对数据库的 DDL 操作。

3 )系统触发器:用户连接或断开数据库,由 logon 、 logoff 、 shutdown 、 startup 操作触发的触发器称为系统触发器,使用系统触发器实现对用户连接数据库信息和数据库启动或停止的信息。

( 2 )按照执行特点分为: 1 )替代触发器:创建于视图上,触发事件和 DML 触发器相同,实现通过结构复

杂的视图修改基表的数据,实现维护数据的完整性。 2 )一般触发器: DML、 DDL 、系统触发器又称为一般触发器。

Page 86: PL/SQL 基础培训

2 、创建 DML 触发器:( 1 )语法:

create or replace trigger trigger_name before|after DML_statement [of colum,…] on table_name|view [when(condition)] [for each row] [declare declarations] begin execute statement exception end trigger_name;

( 2 )语法说明:1) trigger :触发器的关键字。2) before|after :确定触发器程序的执行时机。3) DML_statement :触发事件如 update,delete,insert 。4) of column :基于列及的触发器。5) on table_name|view :确定触发器的载体。6) when(condition) :触发条件。7) for each row :基于行级的触发器。8) declare: 变量常量的声明位置, Oracle 触发器没有 is或 as 。触发器没有参数。

触发器

Page 87: PL/SQL 基础培训

set serveroutput on

insert into emp1(empno) values(3333);

触发器( 3 )实例:

1 )创建简单触发器,并测试。create or replace trigger tri_insertemp1

after insert on emp1

begin

dbms_output.put_line(' 触发器程序执行了 ');

end tri_insertemp1;

2 )创建相应 update 触发器。(独立实现)( 4 )触发器的级别: Oracle 触发器分为行级触发器和语句级触发器。行级触发器表示 DML 操作每影响一

行记录时触发器程序就被执行一次。而语句级触发器每执行一次 DML 语句就会调用执行触发器一次,与影响的行无关。使用 for each row 参数设置触发为行级触发器如果不指定都是语句级触发器。

1 )关于 :new和 :old 变量的解释: :new和 :old 是在触发器被触发时产生的两个特殊的变量,它们数据类型 triggering_table%rowtype 类型,该变量的值是 DML 触发器所影响的记录,在编写触发器时可以将它们当作 rowtype 类型来处理它们。 :new 变量中保存的是 insert或 update 时的新数据。 :old 变量中保存的是执行 delete 触发器将要被删除的数据和执行 update 触发器时要被更新的数据即表中的原数据。

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

特性 INSERT UPDATE DELETE

NEW 有效 有效 NULL

OLD NULL 有效 有效

-- 创建日志信息表

create table emp_his as select * from emp where empno=3000;

delete from emp_his;

-- 创建触发器

create or replace trigger del_emp

before delete on emp1

for each row

begin

-- 将修改前数据插入到日志记录表 emp_his,以供监督使用。

insert into emp_his(empno, ename , job ,mgr , hiredate, sal , comm, deptno)

values(:old.empno, :old.ename , :old.job,:old.mgr, :old.hiredate, :old.sal, :old.comm,:old.deptno);

end;

-- 测试触发器

delete emp where empno=3000;

Page 88: PL/SQL 基础培训

触发器3 )实例 1 :实现 emp1 表的 sal 字段的 default 属性,默认值为 1000 。create or replace trigger tri_defaultbefore insert on emp1for each rowbegin if :new.sal is null then :new.sal:=1000; end if;exception when dup_val_on_index then null; when others then null;end;

实例 2 :实现某字段的自动编号功能。create sequence sque;create or replace trigger tri_autoempnobefore insert on emp1for each rowdeclarev_empno emp1.empno%type;begin select sque.nextval into v_empno from dual; if :new.empno is null then :new.empno:=v_empno; end if;end;

insert into emp1(ename)

values(‘aaa’);

insert into emp1(ename)

values(‘bbb’);

Page 89: PL/SQL 基础培训

( 5 )字段级触发器: DML 触发器用于实现复杂的数据完整性,而且通常都是行级触发器,这时必须精确触发器程序触发的时机,否则触发器会被无意义的触发执行,这样势必会浪费服务器资源,字段级触发器能够精确触发器触发的时机为必须操作某个或某几个字段时才会执行触发器程序。

实例 1 :实现 check约束,约束员工的工资在 0~ 10000之间。set serveroutput on

create or replace trigger tri_checkemp1

before insert on emp

for each row

begin

if :new.sal>10000 or :new.sal<0 then

:new.empno:=null;

:new.ename:=null;

:new.job:=null;

:new.mgr:=null;

:new.hiredate:=null;

:new.sal:=null;

:new.comm:=null;

:new.deptno:=null;

end if;

exception

when others then

null;

end;

insert into emp

values(1111,'aaa','ccc',1111,sysdate,1500,0,10);

触发器

Page 90: PL/SQL 基础培训

( 6) when子句在触发器中的应用: when()子句在触发器中限制行级触发器被触发的条件,如果 when()子句的条件表达式返回 true ,则触发器的程序体被执行,否则不执行触发器程序体,当使用了 when子句,它包含的条件表达式会为行级触发器的每行数据进行一次判断, when子句更加精确的确定触发器触发的时机,在 when 中如要用到 :new和 :old 时,不带冒号。

例:当修改员工工资时,如果修改后的工资高于 5000 ,则打印原工资和工资差信息

create or replace trigger print_update_sal_info

after update of sal,comm on emp

for each row

when (new.sal>5000)

declare

v_diff number;

begin

v_diff := :new.sal - :old.sal;

dbms_output.put_line(' 原工资 --> 新工资:工资差 ');

dbms_output.put_line(:old.sal||'-->'||:new.sal||':'||v_diff);

exception

when others then

null;

end print_update_sal_info;

update emp

set sal= 6000

where empno = 1;

触发器

Page 91: PL/SQL 基础培训

( 7) DML 触发器的触发谓词:一个触发器可以定义为响应多个触发事件的触发器,如 before insert or update or delete on table ,当向表中添加数据、修改数据、删除数据时触发触发器。判定是由哪种触发事件触发的触发器,就要使用谓词。触发器的谓词包括:1) inserting :当执行 insert 操作时, inserting 返回 true 。2) updating :当执行 update 操作时, updating 返回 true 。3) deleting :当执行 delete 操作时, deleting 返回 true 。

例:实现跟踪 emp 表的 dml 操作日志信息,包括记录操作的用户名、操作时间、操作类型等信息。A 、创建日志信息表:

create table emp_dml_log( logno number(20),

operuser varchar2(30), opertime date, opertype varchar2(10)

);

触发器

Page 92: PL/SQL 基础培训

B 、创建触发器:create or replace trigger emp_dml_log

after insert or update or delete on emp

for each row

begin

if inserting then

insert into emp_dml_log

values(seq2.nextval,user,sysdate,'INSRET');

elsif updating then

insert into emp_dml_log

values(seq2.nextval,user,sysdate,'UPDATE');

elsif deleting then

insert into emp_dml_log

values(seq2.nextval,user,sysdate,'DELETE');

end if;

end emp_dml_log;

触发器

Page 93: PL/SQL 基础培训

( 8) instead of 触发器:我们已经学习了 before 触发器和 after 触发器, before 触发器称为前触发器,即触发器程序优先于触发事件执行, after 触发器是触发事件优先于触发器程序的执行而执行。 instead of 触发器是这样一种触发器:即触发事件不真正执行,只执行触发器程序。

通过视图修改多基表数据,没有修改成功,这是因为通过视图不允许同时修改多基表数据。但通过建立基于多基表的视图就可以完成这样的功能。

1 )创建 instead of 触发器语法:CREATE [OR REPLACE] TRIGGER trigger_name

INSTEAD OF

{INSERT | DELETE | UPDATE [OF column [, column …]]}

ON [schema.] view_name

[REFERENCING {OLD [AS] old | NEW [AS] new| PARENT as parent}]

[FOR EACH ROW ]

trigger_body;

2 )实例 1 :通过多基表视图修改表中数据:

触发器

Page 94: PL/SQL 基础培训

A 、创建多基表视图:create or replace view titles_salesasselect t.title_id,t.title,t.price,s.ord_date,s.qtyfrom titles t,sales swhere t.title_id=s.title_id;B 、通过视图修改 qty 数据:update titles_salesset price=price+1,qty=qty-1where title_id='BU1111';C 、创建替代触发器:create or replace trigger tri_t_sinstead of update on titles_salesbeginupdate titlesset price=price+1where title_id=:old.title_id;update salesset qty=qty+1where title_id=:old.title_id;end;

ERROR 位于第 1 行 :

ORA-01732: 此视图的数据操纵操作非法

D 、通过 instead of 触发器实现 price、 qty 数据的修改:update titles_salesset price=price+1,qty=qty-1where title_id='BU1111';

触发器

Page 95: PL/SQL 基础培训

实例 2 :通过 empinfo视图,智能向 emp10,emp20 表中添加 10 号和 20 号部门员工信息。A 、创建表:create table emp10

as

select * from emp where deptno = 10;

create table emp20

as

select * from emp where deptno = 20;

B 、创建多基表视图:create view empinfo

as

select * from emp10

union

select * from emp20;

C 、测试insert into empinfo(empno,ename,deptno) values(2,'aaa',10);

-- 不可能执行成功。

触发器

Page 96: PL/SQL 基础培训

D 、创建触发器:create or replace trigger auto_add_empinstead of insert on empinfodeclarebegin if :new.deptno = 10 then insert into emp10 values(:new.empno,:new.ename,:new.job,:new.mgr,:new.hiredate,:new.sal,:new.comm,:new.deptno); elsif :new.deptno = 20 then insert into emp20 values(:new.empno,:new.ename,:new.job,:new.mgr,:new.hiredate,:new.sal,:new.comm,:new.deptno); else dbms_output.put_line(' 只能添加 10, 20 号部门员工信息。 '); end if;end tri_name;

E 、重新测试:insert into empinfo(empno,ename,deptno)values(2,'aaa',10);-- 执行成功。

注意:当删除表或者视图时,触发器也随之被删除。

触发器

Page 97: PL/SQL 基础培训

3 、触发器的限制( 1) CREATE TRIGGER 语句文本的字符长度不能超过 32KB;( 2 )触发器体内的 SELECT 语句只能为 SELECT…INTO…结构,或为定义游标所使用的 SELECT 语句

。( 3 )触发器中不能使用数据库事务控制语句 COMMIT; ROLLBACK, SVAEPOINT 语句;( 4 )由触发器所调用的过程或函数也不能使用数据库事务控制语句;( 5 )触发器中不能使用 LONG, LONG RAW 类型;( 6 )触发器内可以参照 LOB 类型列的列值,但不能通过 :NEW 修改 LOB列中的数据;4 、创建 DDL 触发器:( 1 )语法: create or replace trigger trigger_name before|after create[ or alter or drop] on schema | database [ declare declarations ] begin excute statements; [ exception exception handles; ] end [tirgger_name]; 语法说明: on schema: 模式(用户)级触发器,只有触发器的创建者执行 ddl 操作才能触发。 on database: 数据库级触发器,数据库中任何用户执行 ddl 操作都触发。注意:用户必须具有 administer database trigger 的系统权限,才能创建数据库级触发器。只有管理员才能给其他用户如 scott赋予此权限。

触发器

Page 98: PL/SQL 基础培训

( 2 )实例:跟踪数据库用户的 DDL 操作日志,包括用户名、操作时间、数据库对象名、对象 所有者、对象类型、操作信息。1 )创建日志表:create table ddl_log (

logno number(20) primary key,

uname varchar2(30),

oper_time date,

obj_name varchar2(30),

obj_owner varchar2(30),

obj_type varchar2(20),

oper_type varchar2(20)

);

2 )创建序列,确定主键值。create sequence seq_ddl_log;

3 )在日志表上创建触发器:create or replace trigger ddl_log

after create or alter or drop on database

begin

insert into ddl_log values(seq_ddl_log.nextval,user,sysdate,sys.dictionary_obj_name,sys.dictionary_obj_owner,

sys.dictionary_obj_type,sys.sysevent);

end ddl_log;

-- 测试

create table emp4

as select * from emp;

--查询:

select * from ddl_log;

触发器

Page 99: PL/SQL 基础培训

5 、创建系统事件触发器 系统触发器就是相应数据库系统事件的触发器,包括数据库服务器的启动或关闭,用户的登

录与退出、数据库服务错误等。创建系统触发器的语法如下: CREATE OR REPLACE TRIGGER [sachema.] trigger_name

{BEFORE|AFTER}

{database_event_list}

ON { DATABASE | [schema.] SCHEMA }

[WHEN_clause]

trigger_body;

database_event_list :一个或多个数据库事件,事件间用 OR 分开;

触发器

Page 100: PL/SQL 基础培训

实例 1 :创建 LOGON、 STARTUP和 SERVERERROR 事件触发器 create or replace trigger trig_afterafter logon or startup or servererror on databasedeclare v_event varchar2(20); v_instance number; v_err_num number; v_dbname varchar2(50); v_user varchar2(30);begin v_event := sysevent; if v_event= 'logon' then v_user := login_user; insert into eventlog(eventname, username) values(v_event, v_user); elsif v_event = 'servererror' then v_err_num := server_error(1); Insert into eventlog(eventname, srv_error) values(v_event, v_err_num); else v_Instance := instance_num; v_dbname := database_name; Insert into eventlog(eventname, inst_num, db_name) values(v_event, v_instance, v_dbname); end if;end;

--系统操作的日志信息表

create table eventlog

(

eventname varchar2(20),

username varchar2(30),

db_name varchar2(50),

inst_num number,

srv_error number

);

触发器

Page 101: PL/SQL 基础培训

实例 2 :创建 LOGOFF和 SHUTDOWN 事件触发器create or replace trigger trig_beforebefore logoff or shutdown on databasedeclare v_event varchar2(20); v_instance number; v_dbname varchar2(50); v_user varchar2(30);begin v_event := sysevent; if v_event = 'logoff' then v_user := login_user; insert into eventlog(eventname, username) values(v_event, v_user); else v_instance := instance_num; v_dbname := database_name; insert into eventlog(eventname, inst_num, db_name) values(v_event, v_instance, v_dbname); end if;end;

6 、删除触发器: drop trigger trigger_name;

触发器

Page 102: PL/SQL 基础培训

SQL优化初步

• SQL 变量或字段的类型匹配: 对于数值型变量或字段不需要加单引号,字符型需要加

单引号。

• 例:SELECT * FROM tcm_user WHERE user_id = ‘35627836’;

SELECT * FROM tbm_newfun_reso WHERE newfunc_id = 9016;

Page 103: PL/SQL 基础培训

避免复杂的多表关联

Select …From user_files uf, df_money_files dm, cw_charge_record ccWhere uf.user_no = dm.user_noand dm.user_no = cc.user_noand ……and not exists(select …)

???

很难优化,随着数据量的增加性能的风险很大。

Page 104: PL/SQL 基础培训

使用 DECODE来减少处理时间• 例如 :

SELECT COUNT(*), SUM(SAL) FROM EMP WHERE DEPT_NO = 0020 AND ENAME LIKE ‘ SMITH%’;

SELECT COUNT(*), SUM(SAL) FROM EMP WHERE DEPT_NO = 0030 AND ENAME LIKE ‘ SMITH%’;

• 你可以用 DECODE 函数高效地得到相同结果

SELECT COUNT(DECODE(DEPT_NO,0020,’X’,NULL)) D0020_COUNT, COUNT(DECODE(DEPT_NO,0030,’X’,NULL)) D0030_COUNT, SUM(DECODE(DEPT_NO,0020,SAL,NULL)) D0020_SAL, SUM(DECODE(DEPT_NO,0030,SAL,NULL)) D0030_SALFROM EMP WHERE ENAME LIKE ‘SMITH%’;

Page 105: PL/SQL 基础培训

减少对表的查询

• 在含有子查询的 SQL 语句中 , 要特别注意减少对表的查询 .• 例如 :

低效 SELECT TAB_NAME FROM TABLES WHERE TAB_NAME = ( SELECT TAB_NAME FROM TAB_COLUMNS WHERE VERSION = 604) AND DB_VER= ( SELECT DB_VER FROM TAB_COLUMNS WHERE VERSION = 604)

高效 SELECT TAB_NAME FROM TABLES WHERE (TAB_NAME,DB_VER) = ( SELECT TAB_NAME,DB_VER) FROM TAB_COLUMNS WHERE VERSION = 604)

Page 106: PL/SQL 基础培训

用 NOT EXISTS替代 NOT IN

• 在子查询中 ,NOT IN子句将执行一个内部的排序和合并 . 无论在哪种情况下 ,NOT IN 都是最低效的 ( 因为它对子查询中的表执行了一个全表遍历 ). 为了避免使用 NOT IN ,我们可以把它改写成外连接 (Outer Joins)或 NOT EXISTS.

• 例如 :低效SELECT USER_NO FROM CW_ARREARAGEWHERE USER_NO NOT IN (SELECT USER_NO FROM

USER_FILES);

• 高效SELECT USER_NO FROM CW_ARREARAGE CAWHERE NOT EXISTS (SELECT 1 FROM USER_FILES UF

WHERE UF.USER_NO = CA.USER_NO);

Page 107: PL/SQL 基础培训

用 >=替代 >

• 如果 DEPTNO上有一个索引

• 高效 : SELECT * FROM EMP WHERE DEPTNO >=4

• 低效 : SELECT * FROM EMP WHERE DEPTNO >3

Page 108: PL/SQL 基础培训

• 索引的使用:1 、主键索引 (非空且不存在重复值 ) 的效率最高,对于主键条件查询的语句不必加分区字段作为条件 (如 city_id);

2 、对于数据量较小的表,索引不会有明显的作用;3 、索引只会加快查询速度,对于大批量的插入和更新操作

会降低效率;4 、对于组合索引只对第一个字段做查询时起作用;5 、对于值重复率太高的字段,索引不会发挥最高作用;

SQL优化

Page 109: PL/SQL 基础培训

• 对于并列条件的解析顺序:从后向前。表关联语句放在最前,将能过滤掉最大记录数的条件放在最后。

• 例:对于分区表的非主键查询,应将分区字段的查询条件(如 city_id)放在最后。

• 表连接时的扫描顺序:从后向前,将效率最高的表放在最后。

SQL优化

Page 110: PL/SQL 基础培训

• 过程中尽量不要用到 * 号, * 号会首先查询数据字典翻译字段名,应用字段名代替。

• 对于模糊查询 LIKE ,只有在用到字段的前几位字符的时候才会用到该字段的索引,以通配符开始的模糊查询不会用到索引。

• 例:假设 tcm_user表的 user_name建有索引,则SELECT * FROM tcm_user WHERE user_name like ‘王%’;

(会用到索引)

SELECT * FROM tcm_user WHERE user_name like ‘%熹微%’;

(不会用到索引)

SQL优化

Page 111: PL/SQL 基础培训

• 对于表关联操作时的非歧义字段,加上表名或表别名会省下检索数据字典的时间,提高效率。

• 例:效率高的写法: SELECT a.*, b.pay_type_name FROM tcm_user a, tvlbm_pay_type b WHERE a.pay_rule_id = b.pay_type_id;

效率低的写法: SELECT a.*, pay_type_name FROM tcm_user a, tvlbm_pay_type b WHERE pay_rule_id = pay_type_id;

SQL优化

Page 112: PL/SQL 基础培训

• EXISTS与 IN• 例:• 效率低的写法:

SELECT * FROM tpm_serv

WHERE serv_id IN (SELECT newfunc_id FROM tbm_newfun_reso

WHERE city_id = ’03’);

效率高的写法:SELECT * FROM tpm_serv a

WHERE EXISTS (SELECT * FROM tbm_newfun_reso

WHERE newfunc_id = a.serv_id AND city_id=‘03’);

效率最高的写法:SELECT * FROM tpm_serv a

WHERE EXISTS (SELECT 1 FROM tbm_newfun_reso

WHERE newfunc_id = a.serv_id AND city_id = ‘03’);

SQL优化

Page 113: PL/SQL 基础培训

小 结

• PL/SQL语言基础• PL/SQL语言的简介• 数据类型• 程序结构• 流程控制• 异常处理• 过程与函数• 游标• 触发器• SQL优化初步

Page 114: PL/SQL 基础培训