PL/SQL
易忘记的:set serveroutput on;
1. 什么是pl/sql : Procedure Language & Structured Query Language 的缩写, PL/SQL是对SQL语言存储过程语言的扩展
2. pl/sql有什么优点:
有利于客户/服务器环境应用的运行
PL/SQL是对SQL语言存储过程语言的扩展
PL/SQL是ORACLE系统的核心语言,现在ORACLE的许多部件都是由PL/SQL写成
3. PL/SQL块的结构如下:
DECLARE
/* 声明部分: 在此声明PL/SQL用到的变量,类型及游标,以及局部的存储过程和函数 */
BEGIN
/* 执行部分: 过程及SQL 语句, 即程序的主要部分 */
EXCEPTION
/* 执行异常部分: 错误处理 */
END;
其中,执行部分 即 begin 和 end 块是必须的。
4. 变量定义:
a) 定义变量:
i. v_name varchar2(15) :='zhangsan';
ii. v_ename emp.ename%type;
--v_ename的数据类型与emp表的ename列相同
b) 定义变量一般应该以v_开头,变量名不能和数据库或表的表示符相同;
c) 标示符可以使用的符号:a-z,A-Z,0-9,$,_,#
d) 变量的赋值: :=
5. 定义常量:c_shcoolName constant varchar2(20) not null :='无锡niit';--定义常量时,要使用constant关键字,常量名一般以c_开头。如果使用了not null,则必须要赋初始值。常量的声明和赋值语句必须是在同一行
6. 使用变量:
a) select ename into v_ename from emp
where empno = &no;
--将查询出来的值赋值给变量v_ename
b) declare
v_deptno dept.deptno%type;
begin
select deptno into v_deptno from dept where dname = 'SALES';
dbms_output.put_line('变量值为:'||v_ deptno);
delete from emp1 where deptno = v_deptno;
--使用变量
end;
7. 条件结构
a) 简单if结构
if v_deptno = 30 then –指定if条件
update emp set sal = sal*1.2 where deptno = v_deptno;
end if; --if语句块结束
b) 双重if结构
i. if v_deptno = 30 then
update emp set sal = sal *1.1 where deptno = v_deptno;
else
dbms_output.put_line('哈哈,你有点不太幸运,加工资没有你的份');
end if;
c) 多重if结构
i. if v_deptno = 10 then
update emp set sal = sal * 1.1;
elsif v_deptno = 20 then –注意elsif的写法
update emp set sal = sal *1.2;
elsif v_deptno = 30 then
update emp set sal = sal*1.3;
else
dbms_output.put_line('不是一个幸运儿');
end if;
ii. declare
v_empno emp.empno%type;
v_sal emp.sal%type;
v_deptno emp.deptno%type;
begin
v_empno := &no;
select sal ,deptno into v_sal, v_deptno from emp where empno = v_empno;
if v_sal<2000 and v_deptno = 10 then
update emp set sal = sal*1.1 where empno = v_empno;
dbms_output.put_line('你的工资提升了10%');
elsif v_sal<2000 and v_deptno = 20 then
update emp set sal = sal *1.2 where empno = v_empno;
dbms_output.put_line('你的工资提升了20%');
else
dbms_output.put_line('谢谢参与');
end if;
end;
d) 嵌套if结构
declare
v_empno emp.empno%type;
v_sal emp.sal%type;
v_deptno emp.deptno%type;
begin
v_empno := &no;
select sal,deptno into v_sal,v_deptno from emp where empno = v_empno;
if v_sal < 2000 then
if v_deptno = 10 then
update emp set sal = sal * 1.1 where empno = v_empno;
dbms_output.put_line('本次加薪10%');
elsif v_deptno = 20 then
update emp set sal = sal *1.2 where empno = v_empno;
dbms_output.put_line('本次加薪20%');
else
dbms_output.put_line('你所在的部门不参与本次加薪');
end if;
else
dbms_output.put_line('你的工资已经够高了,不需要加薪');
end if;
end;
8. 循环结构
a) loop –循环开始,loop类似于do… while循环,至少要执行一次
declare
v int :=1;
begin
loop
if v>10
then exit;--循环退出
else
dbms_output.put_line(v);
v := v+1;
end if;
end loop;--循环结束
end;
练习:建一个表,有一个id列,number类型,使用循环插入20行数据
b) while循环:可以一次,也不执行
示例:
declare
i int := 1;
begin
while i<21 loop
insert into tempt values (i);
i := i+1;
end loop;
end;
c) for循环:for i in [reverse] 1..20 loop end loop;--循环变量不需要手动声明,由编译器自动创建
示例:
begin
for i in [reverse] 1..20 loop –如果使用reverse,则变量递减,否则是递加
insert into tempt values(i);
end loop;
end;
d) 嵌套循环:
示例
begin
for i in 6..14 loop
for j in 5..20 loop
dbms_output.put_line(i*j);
end loop;
end loop;
end;
e) 使用goto 和 标签
9. 复合数据类型: 自定义数据类型
a) 记录类型 record : 自定义记录类型命名规范:name_record_type
i. 示例1:
declare
type stu_record_type is record(
id number(4),
name varchar2(20),
birth date); --自定义记录类型 stu_record_type
stu1 stu_record_type; --声明记录类型的变量
begin
stu1.id := 1001;
stu1.name := 'lulu';
stu1.birth := to_date('1986-6-25','yyyy-mm-dd');
dbms_output.put_line(stu1.name||'''s birth is '||stu1.birth);
end;
ii. 示例2:
declare
type emp_record_type is record(
empno emp.empno%type,
ename emp.ename%type,
sal emp.sal%type);
emp1 emp_record_type;
begin
emp1.empno := 1001;
emp1.ename := 'guofucheng';
emp1.sal := 5000;
insert into emp (empno,ename,sal)
values (emp1.empno,emp1.ename,emp1.sal);
end;
iii. 示例3:
declare
emp1 emp%rowtype;
begin
emp1.empno := 1003;
insert into emp (empno) values (emp1.empno);
end;
b) 索引表类型:与高级语言的数组相似,有索引,区别是索引可以为负数,并且长度没有限制。索引表只能有一列,这一列的数据类型由of 子句决定,索引的数据类型可以为binary_integer,nls_intege,varchar2
示例1:
declare
type ename_table_type is table of varchar2(18)
index by binary_integer; --定义一个索引表,索引类型为binary_integer
ename_table ename_table_type; --定义一个索引表类型变量
begin
select ename into ename_table(-18) from emp where empno = 7788;
dbms_output.put_line(ename_table(-18));
end;
c) 嵌套表:与索引表的相同点时都是一列,但嵌套表的索引是从1,嵌套表有构造方法,嵌套表的变量在使用前必须要使用构造方法初始化,嵌套表的索引是不需要定义的
示例1:
declare
type ename_table_type is table of varchar2(20); --定义嵌套表类型
ename_table ename_table_type; -- 声明一个嵌套表类型的变量
begin
ename_table := ename_table_type('a','a','a'); --给这个变量初始化,这是必须的
select ename into ename_table(2) from emp where empno = 7788; --使用变量
dbms_output.put_line(ename_table(2));
end;
d) 记录表:和索引表、嵌套表只能存贮一列多行不同的是,它能够存储多行多列,和索引表相同的是它也有索引,不需要初始化
示例
declare
type emp_table_type is table of emp%rowtype
index by binary_integer; --定义记录表,注意有索引,数据类型是表的一行
emp_table emp_table_type; --声明一个变量
begin
select * into emp_table(1) from emp where empno = 7788; --给变量的一个元素赋值,要指定元素的索引
dbms_output.put_line(emp_table(1).ename||'''s salary is '||emp_table(1).sal);
end; --输出变量元素的值
e) 变长数组:与嵌套表相似,只能存贮单列,不用定义索引,都要使用构造方法进行初始化,在使用前都要用构造方法初始化,差别是有长度限制
示例1:
declare
type ename_varray_type is varray(3) of emp.ename%type; --定义变长数组类型,要注意数组长度、数组数据类型
ename_varray ename_varray_type; --定义变量
begin
ename_varray := ename_varray_type('a','a','a');--使用构造方法初始化
select ename into ename_varray(1) from emp where empno = 7788;--给数组变量元素赋值
dbms_output.put_line(ename_varray(1));--输出数组变量元素的值
end;
f) 集合的常用属性和方法
i. exists:判断某一个元素是否存在
1. 示例:
declare
type enames_table_type is table of emp.ename%type;
enames_table enames_table_type ;
begin
if enames_table.exists(1) then –判断元素1是否存在
null; --null是一个站位,表示这里什么都不用做,主要是方便阅读
else
dbms_output.put_line('table has not been initialized');
end if;
end;
ii. count:返回集合中元素的个数
iii. first : 返回集合第一个元素的索引
iv. last :返回集合最后一个元素的索引
v. 示例:
declare
type sal_table_type is table of emp.sal%type
index by emp.ename%type;
sal_table sal_table_type;
v_sal emp.sal%type;
v_ename emp.ename%type;
cursor emp_cursor
is
select sal,ename from emp;
begin
open emp_cursor;
loop
fetch emp_cursor into v_sal,v_ename;
exit when emp_cursor%notfound;
sal_table(v_ename) := v_sal;
end loop;
close emp_cursor;
dbms_output.put_line(sal_table.count); --显示集合的元素数量
if sal_table.exists('FORD') then --判断元素FORD是否存在
dbms_output.put_line(sal_table('FORD'));
end if;
dbms_output.put_line(sal_table.first); --显示第一个元素的索引
dbms_output.put_line(sal_table.last); --显示最后一个元素的索引
end;
10. 游标 CURSOR:
a) 什么是游标:oracle在执行insert、delete、update、select时会分配一个上下文区,使用游标指向这个上下文区。游标可以分为显式游标和隐式游标,用户只需要处理显式游标。显式游标的使用分为四个步骤:定义、打开、使用、关闭
b) 示例:
declare
cursor emp_cursor
is
select ename from emp; --定义游标
v_ename emp.ename%type;
begin
open emp_cursor; --打开游标
fetch emp_cursor into v_ename; --提取游标的值
dbms_output.put_line(v_ename);
close emp_cursor; --关闭游标
end;
c) 游标的属性:
i. %isopen
declare
cursor emp_cursor
is
select ename from emp;
v_ename emp.ename%type;
begin
if emp_cursor%isopen then
fetch emp_cursor into v_ename;
dbms_output.put_line(v_ename);
close emp_cursor;
else
dbms_output.put_line('please open the cursor');
end if;
end;
ii. %found : 如果从游标中提取数据后,找到数据了,返回true
iii. %notfound : 从游标中提取数据后,没找到数据,返回true
declare
cursor emp_cursor
is
select ename from emp;
v_ename emp.ename%type;
begin
open emp_cursor;
if emp_cursor%isopen then
loop
fetch emp_cursor into v_ename;
exit when emp_cursor%notfound;
dbms_output.put_line(v_ename);
end loop;
else
dbms_output.put_line('please open the cursor');
end if;
close emp_cursor;
end;
iv. %rowcount : 表示当前游标已经提取了多少行数据,与实际查询的结果行数无关
d) for游标:专门适用于需要进行循环处理的游标,不需要定义接受变量,不需要显式打开、提取、关闭游标
i. 示例:
declare
cursor emp_cursor
is
select ename,sal from emp;
begin
for e in emp_cursor loop
dbms_output.put_line(e.ename||'''s salary is '||e.sal);
end loop;
end;
e) 带参游标:当游标的select语句需要参数时,应该定义带参游标
i. 示例:
declare
cursor emp_cursor(no number) –在定义时定义参数
is
select sal from emp where empno = no;
v_sal emp.sal%type;
begin
open emp_cursor(7788); --在打开时传递参数值
fetch emp_cursor into v_sal;
dbms_output.put_line(v_sal);
close emp_cursor;
end;
ii. 练习:编写带参的for游标,根据指定的部门编号,查询这个部门的薪水,将游标中的值打印出来
declare
cursor emp_cursor(no number)
is
select sal from emp where deptno= no;
begin
for e in emp_cursor(10) loop
dbms_output.put_line(e.sal);
end loop;
end;
f) 隐式游标
i. 示例
begin
for e in (select sal from emp where deptno= &no) loop –这个游标没有显式定义
dbms_output.put_line(e.sal);
end loop;
end;
g) 使用游标删除记录
i. 示例:
declare
cursor emp1_cursor
is
select hiredate from emp1 for update nowait; --for update 在游标上加更新锁
v_hiredate date; --nowait 如果资源被占用,不等待,但会抛出异常
begin
open emp1_cursor;
loop
fetch emp1_cursor into v_hiredate;
exit when emp1_cursor%notfound;
if v_hiredate > to_date('1987-01-01','yyyy-mm-dd') then
delete from emp1 where current of emp1_cursor;
end if;
end loop;
close emp1_cursor;
end;
ii. 练习:
h) 使用游标更新记录
i. 示例:
declare
cursor emp1_cursor
is
select sal from emp1 for update nowait;
v_sal emp1.sal%type;
begin
open emp1_cursor;
loop
fetch emp1_cursor into v_sal;
exit when emp1_cursor%notfound;
if v_sal<1500 then
update emp1 set sal = sal + 200 where current of emp1_cursor;
elsif v_sal < 2000 then
update emp1 set sal = sal + 100 where current of emp1_cursor;
else
update emp1 set sal = sal + 50 where current of emp1_cursor;
end if;
end loop;
end;
ii. 练习:查询emp1表的薪水,如果薪水低于1500块加薪200,低于2000加薪100,其他加薪50
i) 游标变量:同一个游标变量可以为多个查询使用
i. 示例:
declare
type emp_cursor is ref cursor; --定义一个游标类型
e_c emp_cursor; --定义一个emp_cursor类型的游标变量
v_ename emp.ename%type;
begin
open e_c for select ename from emp; --使用这个变量打开特定查询
loop
fetch e_c into v_ename;
exit when e_c%notfound;
dbms_output.put(v_ename||' ');
end loop;
close e_c;
end;
ii. 练习:定一个游标变量,在同一个pl/sql块中使用两次
j) 游标与集合 :fetch … bulk collect into
i. 示例
declare
cursor emp_cursor
is
select ename from emp;
type emp_table_type is table of emp.ename%type
index by binary_integer;
emp_table emp_table_type;
begin
open emp_cursor;
fetch emp_cursor bulk collect into emp_table;
for i in 1 .. emp_table.count loop
dbms_output.put_line(emp_table(i));
end loop;
close emp_cursor;
end;
ii. 练习:定义一个游标,查询emp表中所有的数据,存贮到一个记录表中,将这个记录表中的每个人的姓名、薪水打印出来
一、 异常处理
a) 分类:
i. 预定义异常:系统视为错误,有异常码和异常名称,只需要捕获异常
1. no_data_found --没有直到数据
2. too_many_rows --返回多行
3. invalid_cursor--无效游标,可能是游标没有打开
4. cursor_already_open--游标已经打开
5. dup_val_on_index –重复索引或主键
6. collection_is_null –集合为空
7. access_into_null –对象为空
8. case_not_found –没有对应的case
9. 使用步骤:
a) 捕获异常
ii. 非预定义异常:系统视为错误,有异常码,但没有异常名称,
1. 使用步骤:
a) 定义异常变量,并将异常变量与异常码关联
b) 并捕获异常
iii. 自定义异常:系统并不视为错误,但如果希望给用户提供更详细的提示时,可以进行自定义异常,
1. 使用步骤:
a) 定义异常变量,不需要将变量与异常码关联,因为本来就没有异常码
b) 在需要触发的执行码位置触发异常,if sql%notfound then raise e_error_empno
c) 捕获异常
b) 预定义异常
i. 示例:
declare
v_ename emp.ename%type;
begin
select ename into v_ename from emp where empno = &noo;
dbms_output.put_line(v_ename);
exception
when no_data_found then –处理预定义异常
dbms_output.put_line('please input correct empno');
end;
ii. 练习:使用too_many_rows 预定义异常编码
c) 非预定义异常:
i. 示例
declare
e_integrity exception; --定义异常变量
pragma exception_init(e_integrity,-02291); --将异常变量与特定的异常码关联
begin
update emp set deptno= &dno where empno = &eno;
exception
when e_integrity then --处理非预定义异常
dbms_output.put_line('parent key not found');
end;
ii. 练习:使用非预定义异常的方式对too_many_rows类型的异常进行处理
d) 自定义异常:
示例:
declare
e_error_empno exception; --声明一个异常变量,与预定义异常不同的是,不需要与相应的错误码关联
begin
update emp set deptno = & dno where empno = &no;
if sql%notfound then
raise e_error_empno; --在你想要触发异常的地方抛出异常
end if;
exception
when e_error_empno then
dbms_output.put_line('wrong empno');
end;练习:使用自定义异常处理 编码为-06530类型的异常
e) 异常函数:
i. sqlcode
ii. sqlerrm
iii. raise_application_error
二、 函数:与存储过程不同的是,函数有返回语句
a) 示例
create or replace function fun_count
(dno in number)
return number ----一定要有return 语句
is
v_count number;
begin
select count(*) into v_count from emp where deptno= dno;
return v_count; --在执行部分至少要有一个返回语句
exception
when others then
dbms_output.put_line(sqlerrm);
end;
b) 调用
c) 练习:编写函数,统计emp表的全部员工平均工资。查看文本。删除函数
三、 存储过程:如果有一段pl/sol在程序中可能需要多次执行,可以定义成存储过程
a) 示例:
create or replace procedure pro_out_time –声明部分
is
begin
dbms_output.put_line(systimestamp);
end;
调用:exec pro_out_time;
调用:call pro_out_time();--注意使用call时要小括号
b) 练习:定义一个存储过程,在这个过程中,将emp表中的所有薪水的和打印出来,并调用
c) 如何显示编译错误:show errors
d) 带参存储过程:
i. 示例:
create or replace procedure pro_add_dept
(dno in dept.deptno%type,name in dept.dname%type,location dept.loc%type default 'wuxi') –参数可以有两种模式:输入( in )和输出( out )及输入输入(in out),默认为in, default 提供默认值,当没有为这个参数提供值时,自动使用默认值。所有的输入参数,如果没有提供默认值,则在调用过程时,必须要提供值
is
begin
insert into dept values(dno,name,location);
exception
when dup_val_on_index then
dbms_output.put_line('dump primary key value');
when others then
dbms_output.put_line('input correct parameters');
end;
ii. 调用测试:
1. exec pro_add_dept(60,'instude','suzhou'); --按位置提供正确的输入参数
2. exec pro_add_dept(60,'instude','suzhou'); --提供重复的主键值
3. exec pro_add_dept(61,'instude') –使用默认值
4. exec pro_add_dept(dno=>70,name=>'study') –使用名字提供参数
iii. 使用out
1. 示例:
create or repalce procedure pro_compute
(n1 in number,n2 in number,n3 out number)
is
begin
n3 := n1+n2;
end;
2. 调用:
var n3 number;
exec pro_compute(1,2,:n3);
iv. 使用in out参数
1. 示例
create or replace procedure pro_compute
(n1 in number,n2 in out number)
is
begin
n2 := n1*n2;
end;
2. 调用:
var n1 number;
var n2 number;
exec :n1 := 3;
exec :n2 := 4;
exec pro_compute(:n1,:n2);
v. 查看创建的存储过程的文本:
select text from user_source where name =upper('pro_compute')
vi. 删除存储过程;
drop procedure pro_compute
e) 练习:编译一个存储过程,根据部门编号,返回部门人数。查看存储过程的文本。删除存储过程。
四、 包
a) 为什么要使用包:
i. 可以将相关的数据库对象:过程、函数、索引表、嵌套表、记录表、变长数组等放在一个逻辑相关的包里
ii. 可以通过包对对象可见性进行控制
b) 如何使用包:
i. 声明包规范:包规范类型于java接口,可以在规范中定义共有的组件:变量、常量、函数、过程、游标
create or replace package pk_emp
is
g_deptno emp.deptno%type;
procedure add_emp(p_empno emp.empno%type,p_ename emp.ename%type,p_deptno emp.deptno%type);
function getSal(f_empno emp.empno%type) return emp.sal%type;
end pk_emp;
ii. 定义包体:在包规范中没有声明过,只在包体中定义的元素都是私有的,只能在包内可见
create or replace package body pk_emp
is
v_p_sal emp.sal%type; --定义一个私有的变量
function getAvg(dno emp.deptno%type) –定义一个私有的函数
return emp.sal%type
is
v_avg number(6,2);
begin
select avg(sal) into v_avg from emp where deptno=dno;
return v_avg;
exception
when others then
dbms_output.put_line(sqlerrm);
end getAvg;
function existsDeptno(f_deptno emp.deptno%type) –定义一个私有的函数
return boolean
is
v_count int;
begin
select count(*) into v_count from dept where deptno=f_deptno;
if v_count=1 then
return true;
else return false;
end if;
end existsDeptno;
procedure add_emp(p_empno emp.empno%type,p_ename emp.ename%type,p_deptno emp.deptno%type) –实现包规范中定义的过程
is
begin
if existsDeptno(p_deptno) then –调用包内的私有函数
insert into emp(empno,ename,deptno)
values(p_empno,p_ename,p_deptno);
else
raise_application_error(-20001,'部门编号不存在');
end if;
exception
when dup_val_on_index then
raise_application_error(-20002,'员工编号已存在');
when others then
dbms_output.put_line(sqlerrm);
end add_emp;
function getSal (f_empno emp.empno%type) –实现包规范中定义的函数
return emp.sal%type
is
v_sal emp.sal%type;
begin
select sal into v_sal from emp where empno=f_empno;
return v_sal;
exception
when no_data_found then
dbms_output.put_line('员工编号不正确');
when others then
dbms_output.put_line(sqlerrm);
end getSal;
end pk_emp;
iii. 练习:创建一个包emp_package,含有的公有变量员工编号、公有函数getMinSal、公有存储过程根据员工信息(empno,sal)修改emp表,定一个私有的函数,根据empno判断员工是否存在。调用公有函数、存储过程、私有函数,查询包定义文本,删除包
iv. 访问其他用户的包的函数:
conn sysdba/tiger as sysdba;
var min number;
exec :min :=scott.emp_package.getMinSal(10);
print min;
c) 包重载:在同一个包在中,函数和存储过程有相同的名称,但参数列表不同
i. 示例:
create or replace package emp_package
is
function getSal(v_empno emp.empno%type) return emp.sal%type; --根据员工编号获取员工薪水
function getSal(v_ename emp.ename%type) return emp.sal%type; --根据员工姓名获取员工薪水
end emp_package;
create or replace package body emp_package
is
function getSal(v_empno emp.empno%type)
return emp.sal%type --根据员工编号获取员工薪水
is
v_sal emp.sal%type;
begin
select sal into v_sal from emp where empno = v_empno;
return v_sal;
exception
when no_data_found then
dbms_output.put_line('错误的员工编号');
end getSal;
function getSal(v_ename emp.ename%type)
return emp.sal%type --根据员工姓名获取员工薪水
is
v_sal emp.sal%type;
begin
select sal into v_sal from emp where lower(ename) = lower(v_ename);
return v_sal;
exception
when no_data_found then
dbms_output.put_line('错误的员工编号');
end getSal;
end emp_package;
ii. 练习:使用重载创建两个存储过程,一个是根据员工编号更新薪水,另一个是根据员工姓名更新薪水
d) 包构造过程:作用是对包的变量进行初始化,在同一个会话中,构造过程在第一次调用函数或过程时被调用,并且在同一个会话中只被调用一次;
i. 如何定义构造过程:在包的结尾,以begin … end 语句块形式定义
create or replace package emp_p
is
--定义两个全局变量,新加入的员工工资应该处于这两个变量之间
g_minSal emp.sal%type;
g_maxSal emp.sal%type;
procedure addEmp(v_empno emp.empno%type,v_ename emp.ename%type,v_sal emp.sal%type);
end emp_p;
create or replace package body emp_p
is
procedure addEmp(v_empno emp.empno%type,v_ename emp.ename%type,v_sal emp.sal%type)
is
begin
if v_sal>= g_minSal and v_sal <= g_maxSal then
insert into emp (empno,ename,sal) values (v_empno,v_ename,v_sal);
else
raise_application_error(-20000,'薪水不在合理的范围内');
end if;
exception
when others then
dbms_output.put_line(sqlerrm);
end addEmp;
--构造过程,对g_maxSal、g_minSal进行初始化
begin
dbms_output.put_line('构造过程被调用');
select min(sal) into g_minSal from emp;
select max(sal) into g_maxSal from emp;
end; --此后不能再有代码
ii. 练习:编写一个存储过程,根据员工的编号和薪水对emp表进行更新,更新的薪水不得低于平均工资,不得高于最高工资的120%
e) 包纯度级别:可以对包的函数或过程禁止读、写数据库的数据或包的变量
i. 示例:pragma restrict_references(updateEmp, wnds) –禁止updateEmp写数据库
ii. 示例:pragma restrict_references(updateEmp, rnds) –禁止updateEmp读数据库
iii. 示例:pragma restrict_references(updateEmp, rnps) –禁止updateEmp读包的变量
iv. 示例:pragma restrict_references(updateEmp, wnps) –禁止updateEmp写包的变量
五、 触发器
a) 什么是触发器:就是一个由事件触发而自动调用的存储过程
b) 触发器分类:DML触发器,DDL触发器,系统触发器
c) DML触发器:
i. 示例:
create or replace trigger tri_forbidOperateOnWeekend –创建一个触发器
before insert or update or delete on emp --定义事件、触发时机
begin
if to_char(sysdate,'day') in ('星期六','星期日') then
raise_application_error(-20000,'不能在休息时间对emp进行操作');
end if;
end forbidOperateOnWeekend;
ii. 练习:模仿上例,禁止在休息时间对emp表进行操作,要求代码纯手写
iii. 查看触发器的编译错误:show errors trigger tri_forbidOperateOnWeekend
iv. 使用谓词:inserting、updating、deleting
1. 示例:
create or replace trigger tri_forbidOperateOnWeekend
before insert or update or delete on emp
begin
if to_char(sysdate,'dy') in ('星期六','星期日') then
case
when inserting then
raise_application_error(-20000,'不能在周末新增雇员');
when updating then
raise_application_error(-20000,'不能在周末修改雇员信息');
when deleting then
raise_application_error(-20000,'不能在周末解雇雇员');
end case;
end if;
end forbidOperateOnWeekend;
v. 使用of子句:在更新时可以更精确的控制触发
1. 示例:
create or replace trigger tri_updateSal
before update of sal on emp --of 子句对列进行精确控制
for each row –表示这个触发器是一个行级触发器
begin –业务逻辑是如果更新的值比以前的值要高20%,则禁止更新
if :new.sal <
ld.sal*0.7 then -- :new.sal表示更新的值
ld.sal表示之前的值
raise_application_error(-20000,'一次性提高工资不得超出原工资的20 %');
end if;
end;
2. 练习:创建一个行级触发器,要求对sal列进行更新时,工资不得低于原工资的70%
vi. 使用after
1. 示例
create or replace trigger tri_sal_update_record
after update of sal on emp –after 表示在事件发生后启动触发器
for each row
begin
insert into salUpdateRecord
values (:old.ename,:old.sal,:new.sal,sysdate);
end tri_sal_update_record;
2. 练习:创建一个触发器,要求对工资提升20%的记录到salUpdateRecord
vii. 触发器条件:
1. 示例:
create or replace trigger tri_sal_update_record
after update of sal on emp –after 表示在事件发生后启动触发器
for each row
when (new.sal >= old.sal*1.2) –触发器启动条件,如果条件返回为true,则执行触发器,否则不执行
begin
insert into salUpdateRecord
values (:old.ename,:old.sal,:new.sal,sysdate);
end tri_sal_update_record;
viii. 定义变量:
1. 示例:
create or replace trigger tri_sal_update_record
after update of sal on emp
for each row
when ( new.sal >= old.sal*1.2)
declare
v_sal number :=2500 ; --定义变量
begin
if :new.sal > v_sal then
insert into salUpdateRecord
values (:old.ename,:old.sal,:new.sal,sysdate);
end if;
end tri_sal_update_record;
ix. 使用dml触发器不能从基表在中查询数据:
create or replace trigger tri_sal_update_record
after update of sal on emp
for each row
when ( new.sal >= old.sal*1.2)
declare
v_sal number :=2500 ;
begin
select sal into v_sal from emp where empno = 7788;
if :new.sal > v_sal then
insert into salUpdateRecord
values (:old.ename,:old.sal,:new.sal,sysdate);
end if;
end tri_sal_update_record;
这段代码在编译时能通过,但在执行时会报错,原因是在触发器中对基表进行了查询
d) 如何查询触发器:select trigger_name from user_triggers;
e) instead of触发器:如果对复杂视图进行DML操作,需要创建instead of
i. 示例:
create or replace trigger tri_insert_v_dept_emp
instead of insert on v_dept_emp --instead of
for each row
begin
--假定用户插入的部门编号和员工编号在数据库中不存在
insert into dept(deptno,dname) values(:new.deptno,:new.dname);
insert into emp(empno,ename) values(:new.empno,:new.ename);
end tri_insert_v_dept_emp;
ii. 练习:创建一个触发器,在对v_dept_emp插入数据,判断所插入的主键是否存在,如果存在,则执行更新,否则,执行插入
f) 系统触发器:在特定的系统事件(例程启动、关闭、用户登陆、登出)发生时触发,系统触发器只能由特定的用户创建(sys/tiger as sysdba)
i. 示例:
conn sys/tiger as sysdba;
create table sysevent(
event varchar2(30),
eventtime timestamp);
create or replace trigger tri_sysevent_startup
after startup on database
begin
insert into sysevent values(ora_sysevent,sysdate);
end;
create or replace trigger tri_sysevent_shutdown
after shutdown on database
begin
insert into sysevent values(ora_sysevent,sysdate);
end;
--创建用户日志表,记载用户登陆 或登出
create table user_log(
username varchar2(30),
event varchar2(30),
eventtime timestamp);
--创建用户登入触发器
create or replace trigger tri_user_logon
after logon on database
begin
insert into user_log values(ora_login_user,ora_sysevent,sysdate);
end;
--创建用户等处触发器
create or replace trigger tri_user_logoff
before logoff on database
begin
insert into user_log values(ora_login_user,ora_sysevent,sysdate);
end;
g) DDL触发器:由DDL语句触发
i. 示例:
conn sys/tiger as sysdba;
--创建数据表
create table ddl_log(
username varchar2(30),
time timestamp,
objectname varchar2(30),
objecttype varchar2(30)
);
--创建触发器
create or replace trigger tri_ddl_log
after ddl on scott.schema
begin
insert into ddl_log values(ora_login_user,sysdate,ora_dict_obj_name,ora_dict_obj_type);
end;
--创建存储过程,以便验证
create or replace procedure pro_getSal
(eno in emp.empno%type,v_sal out emp.sal%type)
is
begin
select sal into v_sal from emp where empno = eno;
end;
--查看记录表,存储过程的定义已经记录到表中,说明触发器创建成功
select * from ddl_log;
六、 用户管理
a) conn sys/tiger as sysdba;
b) create user smith
identified by "tiger"; --创建用户,
c) grant connect to s; --授予用户创建会话的权限
d) grant select, insert ,update,delete
on scott.emp to smith; --授予smith对scott的emp表的select、insert、update、delete的权限
e) revoke select,insert,update,delete
on scott.emp from smith; --收权
七、 文件导入导出
八、 触发器常见应用
a) oralce使用序列和触发器为表创建自动增长的主键值
--建表
create table book(
bid int primary key,
name varchar2(30),
price number(6,1));
--创建序列
create sequence sq_book;
--创建触发器
create or replace trigger tri_book
before insert on book
for each row
begin
select sq_book.nextval into :new.bid from dual;
end;
--插入数据
insert into book (name,price) values ('oracle expert',99);