PL/SQL基础 - Seek The Sun Slowly

advertisement
PL/SQL
基础培训
北京神州数码思特奇信息技术股份有限公司
研究院
www.si-tech.com.cn
Oracle 数据库开发——PL/SQL

PL/SQL基础
–
–
–
–
–
–
–
–
–
PL/SQL语言的简介
数据类型
程序结构
流程控制
异常处理
过程与函数
游标
触发器
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命令、以设置查询结果的显示格式,并且可以设置环境选项。
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
客户端应用程序
PL/SQL基础
PL/SQL结构
PL/SQL的基本结构是块,所有的PL/SQL程序都是由块组成的,
PL/SQL的块结构如下:
declare
/*声明部分,包括变量、常量、游标、用户定义的异常的定义等*/
begin
/*可执行的部分,包括SQL语句和过程化语句,是程序的主体*/
exception
/*异常处理部分,包括异常处理语句*/
end
/*块结束语句*/
PL/SQL基础
PL/SQL数据类型
数字型
用来存储整数或实数。NUMBER、BINARY_INTEGER、PLS_INTEGER
NUMBER-----存储整数和浮点数
BINARY_INTEGER-----存储带符号的整数值,溢出时不发生错误
PLS_INTEGER-----存储带符号的整数值,溢出时发生错误
例如:
V_NUM NUMBER(5);
v_binarynum binary_integer;
PL/SQL基础
• 字符型:用类存储字符串或字符数据。包括 VARCHAR2
、CHAR、LONG、NCHAR、NVARCHAR2。
VARCHAR2-----存储可变长度的字符串
CHAR-----存储固定长度的字符串
LONG-----存储可变长度的字符串,其最大长度是32760字节。
NCHAR 和 NVARCHAR2-----NLS 字 符 类 型 用 于 存 储 来 自 不 同 于
PL/SQL语言的字符集中的字符集。
例如: V_CHAR VARCHAR2(20);
PL/SQL基础
• 日期型:用来存储日期和时间信息,包括世纪、年、
月、天、小时、分钟和秒。唯一的类型为DATE。
例如: V_DATE DATE;
• 布尔型:布尔型的类型为 BOOLEAN。布尔变量在
PL/SQL控制结构中使用,BOOLEAN变量只能存储
TRUE、FALSE、NULL值。
例如: V_BOOLEAN BOOLEAN;
PL/SQL基础
• date、timestamp类型
• 示例演示介绍
PL/SQL基础
• 原始型:用来存储二进制数据。包括RAW、LONG RAW
RAW-----存储定长的二进制数据。类似于 CHAR类型,
但不在字符集之间进行转换。
LONG RAW-----与LONG 类似,最大长度为32760字节
,但不在字符集之间进行转换。
例如: V_LONG LONG;
PL/SQL基础
空值处理
NULL+〈数字〉=NULL (空值加数字仍是空值)
NULL> 〈数字〉=NULL (空值与数字比较,结果仍是空值)
NULL||‘字符串’= ‘字符串’ (空值与字符串进行连接运算,结
果为原字符串)
判断一个变量的值是否为NULL的正确写法为:
if my_var is null then
....
end if;
错误写法为:
if my_var = null then
....
end if;
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基础
变量的声明:除了满足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。
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;
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;
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;
PL/SQL基础
set serveroutput on
--set serveroutput off
declare
v_empno
emp.empno%Type;
/*声明变量v_empno, %type: 使该变量的类型与emp表中的empno类型相同*/
v_emprecord
emp%Rowtype;
/*声明变量v_emprecord, %rowtype: 使该变量的类型与emp表中的整行相同*/
begin
Select * 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;
/
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基础
PL/SQL 的记录类型
declare
type rec_emp is record
PL/SQL的复合类型主要包括record记录类型。record复合数据类型在使
(
用前必须定义,类似C语言的结构体类型。也可以使用rowtype来定义。
v_empno emp.empno%type,
例1:
v_ename emp.ename%type,
v_sal emp.sal%type
);
v_empinfo
rec_emp;
declare type recTypeStudent
is record(
begin
sname varchar2(10), age declare
number(2)
select empno,ename,sal
);
v_emp
emp%rowtype;
into v_empinfo
from emp
v_recStu recTypeStudent;
begin
where empno=7369;
select * into v_emp
dbms_output.put_line(v_empinfo.v_empno||'
begin
from emp
'||v_empinfo.v_ename);
where empno=7369;
v_recStu.sname := 'zhang';end;
dbms_output.put_line(v_emp.empno);
v_recStu.age := 20;
end; || ' ' || v_recStu.age );
dbms_output.put_line ( v_recStu.sname
end;
例2:定义一个记录类型,接收7369号员工的信息并打印。(独立实现)
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基础
用法演示
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、upper
3)日期函数:add_months、last_day、months_between、next_day、round、
sysdate、trunc
4)转换函数:to_char、to_number、to_date
(2)在oracle中不能直接使用的函数:decode、组函数
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基础
函数用法:
2.substr函数
• 取得字符串中指定起始位置和长度的字符串
– substr( string, start_position, [ length ] )
如:
substr(‘This is a test’, 6, 2)
substr(‘This is a test’, 6)
substr(‘TechOnTheNet’, -3, 3)
substr(‘TechOnTheNet’, -6, 3)
would return ‘is’
would return ‘is a test’
would return ‘Net’
would return ‘The’
select substr('Thisisatest', -4, 2) value from dual
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基础
综合应用:
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基础
函数用法:
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基础
函数用法:
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基础
Oracle中可以使用的操作符:
set serveroutput on
1)算术操作符:+、-、*、/、**(乘方)
declare
2)关系运算符:<、<=、>、>=、=、<>、!=
v_time number(2);
3)其他的比较运算符:is null、like、between…and、in
begin
4)逻辑运算符:and、or、not
v_time := to_char(sysdate,'hh24');
5)其他操作符||、:=
if v_time >= 6 and v_time <= 8 then
PL/SQL中的控制结构:
(1)条件语句:
dbms_output.put_line('起床。');
elsif v_time > 8 and v_time < 17 then
if 条件1 then
语句体;
elsif 条件2 then
语句体;
……….
else
语句体;
endif;
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基础
(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:打印今年当前月份的天数。(独立实现)
declare
v_year number;
v_days number;
if v_leapyear then
v_month number;
if (v_month=1 or v_month=3 or v_month=5 or v_month=7 or
v_leapyear boolean; 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)
begin
then
v_year:=to_char(sysdate,'yyyy');
v_days:=30;
else
v_month:=to_char(sysdate,'mm');
v_days:=29;
end if; and mod(v_year,100)!=0 or
v_leapyear:=( mod(v_year,4)=0
mod(v_year,400)=0); 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;
dbms_output.put_line(v_days);
end if;
end if;
end;
PL/SQL基础
(3) PL/SQL:
CASE 结构
CASE
WHEN 条件表达式1 THEN
语句段1
WHEN 条件表达式2 THEN
语句段2
……
ELSE
语句段N
END CASE;
PL/SQL基础
示例:CASE 结构
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;
PL/SQL基础
declare
declare
v_x number;
v_x number;
begin
循环结构:
v_sum
number;
v_x:=1;
1)简单循环:循环体至少循环一次,loop语法如下:
loop
begin
dbms_output.put_line(v_x);
loop
v_sum:=0;
v_x:=v_x+1;
语句体;
v_x:=1;
if v_x>20 then
[exit;]
loop exit;
end loop;
end if;
v_sum:=v_sum+v_x;
end loop;
例1:打印1,2,3…20;
v_x:=v_x+1;
end;
例2:求1+3+5+…25;
if v_x>=25 then
注意:也可以使用exit when来结束循环。
例3:将上面的两个例子用exit when改写。
exit;
end if;
end loop;
dbms_output.put_line(v_sum);
end;
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基础
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基础
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基础
declare
练习:v_empno number(4);
例1:如想获得7369号员工的信息,并打印出来。
v_ename varchar2(10);
例2:打印bu1032图书的基本信息和销售信息,包括书号、书名、类型、价
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基础
 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基础
 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基础
在PL/SQL中执行DDL语句
begin
execute immediate
'create table t ( num varchar2(20) default ''hello'')';
end
例外处理 (EXCEPTION)
许多编写得很好的程序都必须能够正确处理各种出错情况,并且尽
可能地从错误中进行恢复。异常处理方法是程序对运行时刻错误作
出反应并进行处理的方法。当引发一个异常情况时,控制便会转给
块的异常处理部分。
异常处理部分的语法如下:
EXCEPTION
WHEN 异常情况1 THEN
语句序列1;
WHEN 异常情况2 THEN
语句序列2;
...
WHEN OTHERS THEN
语句序列3;
END;
OTHERS 异常处理将对所有引发的异常情况执行
其代码。它应该是块中最后一个处理语句,确保
所有的错误都被检测到。但OTHERS 只是简单地
记录发生了错误,而没有记录发生的是哪一个错
误。我们可以在OTHERS 中用预定义函数
SQLCODE
和SQLERRM来决定引发异常处理的是哪个错误。
异常情况包括系统预定义的异常情况、用户自定
义的异常情况。
例外处理 (EXCEPTION)
⑴ 系统预定义的异常情况
ORACLE有一些预定义的异常情况和大多数通常的ORACLE错误是对
应的。这些异常情况所使用的标识符在包STANDARD90中进行定义。
在程序的异常处理部分直接对它们进行处理。
预定义的 ORACLE 异常情况
ORACLE 错误
对应的异常情况
说明
ORA-0001
DUP_VAL_ON_INDEX
唯一值约束被破坏
ORA-0051
TIMEOUT_ON_RESOURCE
在等待资源时发生超时现象
ORA-0061
TRANSACTION_BACKED_OUT
由于发生死锁事物处理被撤
消了
ORA-1001
INVALID_CURSOR
非法的游标操作
ORA-1012
NOT_LOGGED_ON
没有连接到 ORACLE
ORA-1017
LOGIN_DENIED
无效的用户名/口令
ORA-1403
NO_DATA_FOUND
没有找到数据
ORA-1422
TOO_MANY_ROWS
SELECT...INTO 语句匹配多
个行
ORA-1476
ZERO_DIVIDE
被零除
ORA-1722
INVALID_NUMBER
转换为一个数字失败。
ORA-6500
STORAGE_ERROR
如果 PL/SQL 运行时内存不
够就引发内部的 PL/SQL 错
误
ORA-6501
PROGRAM_ERROR
内部 PL/SQL 错误
ORA-6502
VALUE_ERROR
截尾、算术或转换错误
ORA-6504
ROWTYPE_MISMATCH
宿主游标变量和 PL/SQL 游
标变量有不兼容的行类型
ORA-6511
CURSOR_ALREADY_OPEN
试图打开已存在的游标
ORA-6530
ACCESS_INTO_NULL
试图为 NULL 对象的属性赋
值
ORA-6531
COLLECTION_IS_NULL
试图将 EXISTS 以外的集合
方法应用于一个 NULL PL/SQL
表或 VARRAY 上
ORA-6532
SUBSCRIPT_OUTSIDE_LIMIT
对嵌套表或 VARRAY 索引的
引用超出说明范围以外
ORA-6533
SUBSCRIPT_BEYOND_COUNT
对嵌套表或 VARRAY 索引的
引用大于集合中元素的个数
⑵ 用户自定义的异常情况
用户自定义的异常情况是程序定义的一个错误。程序所定义的这个
错误并不一定非是一个ORACLE错误,它可能是与数据相关的一个错
误。
用户定义的异常情况的处理分三步:
⑵ 用户自定义的异常情况
· 定义异常情况
用户定义的异常情况是在PL/SQL块的说明部分进行定义的,和变量
相类似,异常情况有一个类型和作用域。
例如: my_exception exception;
· 触发异常情况
当一个异常情况相关的错误出现时,就会引发该异常情况。用户定义
的异常情况是通过显式使用RAISE 语句来引发的,而预定义的异常情况
是当相关的ORACLE错误发生时被隐式触发的。
例如: RAISE MY_EXCEPTION;
· 在程序的异常处理部分对定义的异常情况进行处理。
例如: WHEN MY_EXCEPTION THEN
...
⑵ 用户自定义的异常情况
例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 ;
⑵ 用户自定义的异常情况
exception
when err_ps then
insert into tout values(‘ password error’〕;
when no_data_found then
insert into tout values(‘ userid error’〕;
end;
⑵ 用户自定义的异常情况
例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;
⑵ 用户自定义的异常情况
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;
/
例外处理 (EXCEPTION)
在PL/SQL中,警告信息、出错信息、或返回信息统称为例外(Exception)
有两中类型的例外。
Oracle预定义的例外: 是由PL/SQL运行过程中,系统自动产生的信息。
用户定义例外: 是用户根据需要,自己定义使用的例外,执行时
由用户自己引起。
 预定义的例外
CURSOR_ALREADY_OPEN
NO_DATA_FOUND
TOO_MANY_ROWS
INVALID_CURSOR
VALUE_ERROR(算术、转换、截断或大小约束错误)
INVALID_NUMBER(字符串转换为数字时失败)
ZERO_DIVIDE
DUP_VAL_ON_INDEX(给唯一约束列插入重复的值)
 用户自定义例外 用户定义的例外必须在DECLARE段中说明,在
Begin段中用RAISE引起,在EXCEPTION段中使用。
例外处理 (EXCEPTION)
declare
v_temp
number(4);
begin
select empno into v_temp from emp where deptno = 10;
exception
when too_many_rows then
dbms_output.put_line('太多记录');
when others then
dbms_output.put_line('error');
end;
declare
v_temp
number(4);
begin
select empno into v_temp from emp where deptno = 1111;
exception
when no_data_found then
dbms_output.put_line('没有数据');
when others then
dbms_output.put_line('error');
end;
例外处理 (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;
例外处理 (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;
例外处理 (EXCEPTION)
示例:
declare
v_deptnodept.deptno%type := 10;
v_errcode
number;
v_errmsg
varchar2(1024);
begin
delete from dept where deptno = v_deptno;
commit;
exception
when 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;
游标 (CURSOR) 设计
 游标的类型:
一是隐性游标
二是显性游标
使用select命令查询获得一行记录就是使用了隐性游标,它的特点是不
用使用游标的声明和只能操作一条记录;而显性的游标必须经过严格的
声明、打开和提取操作。
语法:
CURSOR 光标名(参数) IS SELECT 字句;
 打开游标
 获取活动集中的行
FETCH语句检索活动集中的行,每次一行,每执行一次FECTCH,光
标前进到活动集中的下一行。
 游标下移
 关闭游标
游标属性
游标的属性
从游标工作区中逐一提取数据,可以在循环中完成。但循环的开
始以及结束,必须以游标的属性为依据。
游标属性及其描述如下:
游标属性
游标属性
描
述
游标名%ISOPEN
值为布尔型,如果游标已打开,取值为 TRUE
游标名%NOTFOUND
值为布尔型,如果最近一次 FETCH 操作没有返回结果,则取值
为 TRUE
游标名%FOUND
值为布尔型,如果最近一次 FETCH 操作没有返回结果,则取值
为 FALSE。否则,为 TRUE
游标名%ROWCOUNT
值为数字型,值是到当前为止返回的记录数
演示:
• 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;
演示:
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;
演示:
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;
演示:
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;
例1:通过游标打印所有员工的信息:
set serveroutput on
declare 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;
例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;
例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)
游标的属性:
(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;
dbms_output.put_line(v_ename||'
end if;
'||v_job||' '||v_sal);
fetch cur_empsal into v_ename,v_job,v_sal;
exception
while cur_empsal%found loop
when invalid_cursor then
if cur_empsal%rowcount=2 then
dbms_output.put_line('无效游标');
exit;
when others then
end if;
null;
fetch cur_empsal into v_ename,v_job,v_sal; end;
end loop; ………………………
游标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;
5、带参数的游标:与存储过程相似,可以将参数传给游标并在查询中使用
,对处理在某些条件下打开的游标的情况非常有用。
语法如下:
cursor cursor_name(parameter datatype [default value][,…])
is select statement;
注意:
与存储过程不同,游标只能有In参数,参数定义类型不能指定长度。参数
也可以有缺省值,游标定义的参数只能在游标内使用,不要在超出游标以外
的程序中使用。给游标参数传递值在游标打开时进行。
语法如下:open cursor_name(value[,…]);
例1:查找所有部门的部门号,部门名称,部门的工资工资总额。
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;
游标中的更新和删除
• 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}
游标中的更新和删除
•
DELCARE
CURSOR c1 IS SELECT empno,salary
FROM emp
WHERE comm IS NULL
FOR UPDATE OF comm;
v_comm NUMBER(10,2);
BEGIN
FOR r1 IN c1 LOOP
IF r1.salary<500 THEN
v_comm:=r1.salary*0.25;
ELSEIF r1.salary<1000 THEN
v_comm:=r1.salary*0.20;
ELSEIF r1.salary<3000 THEN
v_comm:=r1.salary*0.15;
ELSE
v_comm:=r1.salary*0.12;
END IF;
UPDATE emp;
SET comm=v_comm
WHERE CURRENT OF c1l;
END LOOP;
END
过程
• 过程(PROCEDURE):可以没有返回值。
• 基本格式如下:
CREATE [ OR REPLACE ] PROCEDURE 过程名
(
参数名1
IN/OUT 参数类型,
参数名2
IN/OUT 参数类型
)
IS
{变量声明部分}
BEGIN
{过程部分}
EXCEPTION
{异常处理部分}
END;
用法演示:
--存储过程
a.不带参数
执行存储过程(2种方法):
1. exec p;
2.
begin
p;
create or replace procedure p
end;
is
cursor c is
select * from emp for update;
begin
for v_temp in c loop
if(v_temp.deptno = 10) then
update emp set sal = sal + 10 where current of c;
elsif(v_temp.deptno = 20) then
update emp set sal = sal + 20 where current of c;
else
update emp set sal = sal + 50 where current of c;
end if;
end loop;
commit;
end;
用法演示:
--存储过程
b.带参数
create or replace procedure p
(v_a in number,v_b number,v_ret out number,v_temp in out number)
is
begin
if(v_a > v_b) then
v_ret := v_a;
else
v_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;
begin
p(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 = ‘过
程名';
如果有错误,则显示错误信息
删除存储过程
用法演示:
--存储过程
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;
用法演示:
--存储过程
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;
示例:
函数
• 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;
函数
例如:利用函数实现一个查询获取某雇员的工资
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;
函数
• 用法演示:
--创建函数:
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;
函数
• 用法演示:
--创建函数:
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;
函数
• 用法演示:
调用函数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 函数名;
函数
• 用法演示:
调用函数2
set 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;
触发器
•
触发器是一种特殊的存储过程,也就是说触发器具有存储过程的所有优势,是命
名程序的一种,也是存储并运行在服务器端的。说其具有特殊性,是因为触发器
的调用执行和存储过程不一样,存储过程的程序调用执行必须由程序员事先设计
并编写好调用程序代码及对应的参数值,即存储过程的调用执行由程序员决定,
而触发器程序不能被应用程序调用,也没有参数,当触发器程序创建并保存在数
据库中后只要对应触发器触发器事件的发生,该触发器就会被自动调用执行。
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、系统触发器又称为一般触发器。
触发器
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。触发器没有参数。
触发器
(3)实例:
1)创建简单触发器,并测试。
--创建日志信息表
create or replace trigger tri_insertemp1
set serveroutput on
create after
tableinsert
emp_his
as select * from emp where empno=3000;
on emp1
insert into emp1(empno)
delete begin
from emp_his;
values(3333);
dbms_output.put_line('触发器程序执行了');
--创建触发器
end tri_insertemp1;
create or replace trigger del_emp
2)创建相应update触发器。(独立实现)
before delete on emp1
(4)触发器的级别:Oracle触发器分为行级触发器和语句级触发器。行级触发器表示DML操作每影响一
行记录时触发器程序就被执行一次。而语句级触发器每执行一次DML语句就会调用执行触发器一次,
for each row
与影响的行无关。使用for each row参数设置触发为行级触发器如果不指定都是语句级触发器。
begin
1)关于:new和:old变量的解释:
:new和:old是在触发器被触发时产生的两个特殊的变量,它们数据类
型triggering_table%rowtype类型,该变量的值是DML触发器所影响的记录,在编写触发器时可以将
-- 将修改前数据插入到日志记录表 emp_his,以供监督使用。
它们当作rowtype类型来处理它们。:new变量中保存的是insert或update时的新数据。:old变量中保
insert into emp_his(empno, ename , job ,mgr , hiredate, sal , comm, deptno)
存的是执行delete触发器将要被删除的数据和执行update触发器时要被更新的数据即表中的原数据。
values(:old.empno, :old.ename , :old.job,:old.mgr, :old.hiredate, :old.sal, :old.comm,:old.deptno);
INSERT
UPDATE
DELETE
end; 特性
NEW
--测试触发器
有效
OLD where empno=3000;
NULL
delete emp
有效
NULL
有效
有效
2)实例1:
建立一个触发器, 当职工表 emp 表被删除一条记录时,把被删除记录写到职工表删除日志表中。
(独立实现)
触发器
3)实例1:实现emp1表的sal字段的default属性,默认值为1000。
create or replace trigger tri_default
before insert on emp1
for each row
begin
if :new.sal is null then
:new.sal:=1000;
end if;
exception
when dup_val_on_index then
null;
when others then
null;
end;
insert into emp1(ename)
values(‘aaa’);
实例2:实现某字段的自动编号功能。
create sequence sque;
create or replace trigger tri_autoempno
before insert on emp1
for each row
declare
v_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(‘bbb’);
触发器
(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;
insert into emp
:new.mgr:=null;
values(1111,'aaa','ccc',1111,sysdate,1500,0,10);
:new.hiredate:=null;
:new.sal:=null;
:new.comm:=null;
:new.deptno:=null;
end if;
exception
when others then
null;
end;
触发器
(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;
触发器
(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)
);
触发器
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;
触发器
(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:通过多基表视图修改表中数据:
触发器
A、创建多基表视图:
create or replace view titles_sales
as
select t.title_id,t.title,t.price,s.ord_date,s.qty
from titles t,sales s
where t.title_id=s.title_id;
B、通过视图修改qty数据:
update titles_sales
ERROR 位于第 1 行:
set price=price+1,qty=qty-1
ORA-01732: 此视图的数据操纵操作非法
where title_id='BU1111';
C、创建替代触发器:
create or replace trigger tri_t_s
instead of update on titles_sales
begin
D、通过instead of 触发器实现price、qty数据的修改:
update titles
update titles_sales
set price=price+1
set price=price+1,qty=qty-1
where title_id=:old.title_id;
where title_id='BU1111';
update sales
set qty=qty+1
where title_id=:old.title_id;
end;
触发器
实例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);
-- 不可能执行成功。
触发器
D、创建触发器:
create or replace trigger auto_add_emp
instead of insert on empinfo
declare
begin
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);
--执行成功。
注意:当删除表或者视图时,触发器也随之被删除。
触发器
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赋予此权限。
触发器
(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)
);
--测试
create table emp4
as select * from emp;
--查询:
select * from ddl_log;
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;
触发器
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 分开;
触发器
实例1:创建LOGON、STARTUP和SERVERERROR 事件触发器
create or replace trigger trig_after
after logon or startup or servererror on database
declare
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
);
触发器
实例2:创建LOGOFF和SHUTDOWN 事件触发器
create or replace trigger trig_before
before logoff or shutdown on database
declare
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;
SQL优化初步
• SQL变量或字段的类型匹配:
对于数值型变量或字段不需要加单引号,字符型需要加单
引号。
• 例:
SELECT * FROM tcm_user WHERE user_id =
‘35627836’;
SELECT * FROM tbm_newfun_reso WHERE
newfunc_id = 9016;
避免复杂的多表关联
Select …
From user_files uf, df_money_files dm, cw_charge_record cc
Where uf.user_no = dm.user_no
and dm.user_no = cc.user_no
and ……
and not exists(select …)
???
很难优化,随着数据量的增加性能的风险很大。
使用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_SAL
FROM EMP WHERE ENAME LIKE ‘SMITH%’;
减少对表的查询
• 在含有子查询的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)
用NOT EXISTS替代NOT IN
• 在子查询中,NOT IN子句将执行一个内部的排序和合并.
无论在哪种情况下,NOT IN都是最低效的 (因为它对子查
询中的表执行了一个全表遍历). 为了避免使用NOT IN ,我
们可以把它改写成外连接(Outer Joins)或NOT EXISTS.
• 例如:
低效
SELECT USER_NO FROM CW_ARREARAGE
WHERE USER_NO NOT IN (SELECT USER_NO FROM
USER_FILES);
• 高效
SELECT USER_NO FROM CW_ARREARAGE CA
WHERE NOT EXISTS (SELECT 1 FROM USER_FILES UF
WHERE UF.USER_NO = CA.USER_NO);
用>=替代>
• 如果DEPTNO上有一个索引
• 高效:
SELECT *
FROM EMP
WHERE DEPTNO >=4
•
低效:
SELECT *
FROM EMP
WHERE DEPTNO >3
SQL优化
• 索引的使用:
1、主键索引(非空且不存在重复值)的效率最高,对于主键条
件查询的语句不必加分区字段作为条件(如city_id);
2、对于数据量较小的表,索引不会有明显的作用;
3、索引只会加快查询速度,对于大批量的插入和更新操作
会降低效率;
4、对于组合索引只对第一个字段做查询时起作用;
5、对于值重复率太高的字段,索引不会发挥最高作用;
SQL优化
• 对于并列条件的解析顺序:从后向前。表关联语句放在最
前,将能过滤掉最大记录数的条件放在最后。
• 例:对于分区表的非主键查询,应将分区字段的查询条件
(如city_id)放在最后。
• 表连接时的扫描顺序:从后向前,将效率最高的表放在最
后。
SQL优化
• 过程中尽量不要用到*号,*号会首先查询数据字典翻译字
段名,应用字段名代替。
• 对于模糊查询LIKE,只有在用到字段的前几位字符的时
候才会用到该字段的索引,以通配符开始的模糊查询不会
用到索引。
• 例:假设tcm_user表的user_name建有索引,则
SELECT * FROM tcm_user WHERE user_name like ‘王%’;
(会用到索引)
SELECT * FROM tcm_user WHERE user_name like ‘%熹微%’;
(不会用到索引)
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优化
• 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’);
小结
• PL/SQL语言基础
• PL/SQL语言的简介
• 数据类型
• 程序结构
• 流程控制
• 异常处理
• 过程与函数
• 游标
• 触发器
• SQL优化初步
Download