调用存储过程
创建的存储过程一般包含三个模块,即声明部分、执行部分、异常处理部分,语法如下
-- OR REPLACE 表示,如果已经存在改存储过程,那么创建的存储过程hi直接替换原有的
CREATE OR REPLACE procedure 存储过程名(参数1,参数2,...参数n) is/as -- is as可以互换
BEGIN
SQL语句;
EXCEPTION
SQL语句;
end 存储过程名;
举例:
CREATE OR REPLACE procedure insertDept is
BEGIN
insert into dept values(66,'Test66','BeiJing'); -- 插入数据
commit; -- 提交
dbms_output.put_line('插入成功') ; -- 提示插入成功
end insertDept;
但是数据还未插入数据库,需要我们调用存储过程执行编译
call insertDept();
此时可以看到数据库里已经有我们插入的对应的信息了
select * from dept;
如果在创建存储过程中出现错误,在SQLPLUS里可以使用show error
来查看错误信息
-是一种输入类型的参数,参数值由调用方法传入,并且只能被存储过程读取,关键字IN位于参数名之后
举例:创建一个存储过程并定义三个IN模式变量,然后将变量值参数表中
create or replace procedure insertDept_IN(
num_deptno in number, -- 定义IN模式变量存储部门编号
var_ename in varchar2, -- 定义IN模式变量存储部门名称
var_loc in varchar2) is -- 定义IN模式变量存储地址
begin
insert into dept
values(num_deptno,var_ename,var_loc); -- 插入记录
commit; -- 提交数据库
end insertDept_IN;
存储过程创建成功,需要注意的是IN模式参数类型不能指定长度。
在调用IN模式存储过程时,用户需要向存储过程中传递若干参数值,以保证执行部分(即BEGIN部分)有具体的数值参与数据操作。
向存储过程传入参数有如下三种方式,我们以上面创建的存储过程insertDept_IN
为例讲解一下“”
// =>是赋值号
参数名称=>参数值
举例
call insertdept_in(var_ename=>'Test3',var_loc => '汕尾',num_deptno => '12');
可以看到,使用指定名称传递参数,我们调用存储过程的参数顺序可以和我们定义存储过程的参数顺序不一样,即使用“指定名称”的方式传递参数值与参数的定义顺序无关,但与参数个数有关。
call insertdept_in(11,'Tes4t','成都');
call insertdept_in(18,var_loc => '广州',var_ename=>'Test5');
需要注意的是,使用混合方式传值时,在某个位置已经使用按指定名称传入参数值后,后面的参数值也要使用指定参数名称传递,因为指定参数名称传递有可能已经破坏了参数原始的定义顺序。
举例
-- 创建一个存储过程,定义两个out参数
-- 将dept表中检索到的一行部门信息存储到这两个参数中
create or replace procedure select_dept(
num_deptno in number, -- 定义in模式变量,要求输入模式编号
var_dname out dept.dname%type, -- 定义out模式变量,存储部门名称并输出
var_loc out dept.loc%type) is
begin
select dname,loc into var_dname,var_loc from dept
where deptno = num_deptno;
exception
when no_data_found then -- 定义异常
dbms_output.put_line('部门编号不存在'); -- 输出信息
end select_dept;
由于存储过程通过out返回参数值,调用或执行这个存储过程时,我们需要定义变量来保存两个参数,调用过程如下
set serverout on -- 注意,此句第一句只在sqlplus环境中有效,用于输出信息
declare
var_dname dept.dname%type; -- 声明变量
var_loc dept.loc%type; -- 声明变量
begin
scott.select_dept(10,var_dname,var_loc); -- 传入部门编号
dbms_output.put_line(var_dname||'位于:'||var_loc); -- 输出部门信息
end;
顾名思义,兼顾两种模式参数的优点
举例:新建一个存储过程计算平方或者平方根
create or replace procedure square_inout(
num in out number, -- 定义一个in out 参数
flag in boolean) is -- 计算平方或平方根的标识,一个In参数
i int :=2; -- 表示计算平方,这是一个内部变量
begin
if flag then -- 若为true
num :=power(num,i); -- 计算平方
else
num :=sqrt(num); -- 计算平方根
end if;
end;
调用
set serverout on -- 注意,此句第一句只在sqlplus环境中有效,用于输出信息
declare
var_number number; -- 存储要进行运算的值和愶的后果
var_temp number; -- 存储要进行运算的值
boo_flag boolean; -- 平方或平方根的逻辑标记
begin
var_temp := 3; -- 变量赋值
var_number := var_temp;
boo_flag := false; -- true => 计算平方 false => 计算平方根
square_inout(var_number,boo_flag); -- 调用存储过程
if boo_flag then
dbms_output.put_line(var_temp || '的平方是' || var_number); -- 输出计算结果
else
dbms_output.put_line(var_temp || '的平方跟是' || var_number);
end if;
end;
-- drop procedure 存储过程名;
drop procedure square_inout;
当一个存储过程已经过时想重新定义时,不必先删除再创建,在create
关键字后加or replace
即可。
create or replace function 函数名称(参数1 参数1类型,参数2 参数2类型) return 函数的返回值类型 is
函数的内部变量(可选项);
begin
sql语句;
exception
异常处理代码;
end;
举例
-- 定义一个函数,用于计算emp表中的平均工资
create or replace function get_avg_pay(num_deptno number) return number is
num_avg_pay number; -- 保存平均工资
begin
select avg(sal) into num_avg_pay from emp where deptno=num_deptno; -- 返回平均工资
return (round(num_avg_pay,2));
exception
when no_data_found then
dbms_output.put_line('该部门编号不存在');
return (0);
end;
set serveroutput on -- 注意,此句第一句只在sqlplus环境中有效,用于输出信息
SQL> declare
2 avg_pay number;
3 begin
4 avg_pay:=get_avg_pay(10); -- 调用函数,获取返回值
5 dbms_output.put_line('平均工资是:'||avg_pay);
6 end;
7 /
drop function 函数名
举例
drop function get_avg_pay;
create or replace trigger tri_name
before/after/instead of tri_event
on table_name/view_name/user_name/db_name
for each row when tri_condition
begin
sql_sentences;
end tri_name;
下面一个一个解释一下
顾名思义是针对一条DML语句引起的触发器执行。不使用for each row子句,无论操作数据影响多少行,触发器都只会执行一次。
下面通过例子来认识。
创建一个日志表dept_log
create table dept_log
(
operate_tag varchar2(20), -- 定义字段,存储操作种类信息
operate_time date -- 定义字段,存储操作日期
);
创建一个关于dept表的语句级触发器
create or replace trigger tri_dept
before insert or update or delete on dept --创建触发器
declare
var_tag VARCHAR2 (10); -- 存储操作类型
begin
if inserting then -- 当触发事件为Insert时
var_tag :='插入'; -- 标识插入操作
elsif updating then -- 但出发事件为Update时
var_tag :='修改'; -- 标识修改操作
elsif deleting then -- 但出发事件为delete时
var_tag := '删除'; -- 标识删除操作
end if;
insert into dept_log
values (var_tag,sysdate ) ;-- 向日志表中插入对dept表的操作信息
end tri_dept;
在上述代码中,使用before关键字指定触发器的“触发时机”,指定当前触发器在DML语句执行前触发,这使得它非常适合于强化安全性、启用业务逻辑和进行日志信息记录。
另外,为了具体判断对dept表进行了何种操作,代码中还是用了条件谓词,由关键字if或elsif
和谓词inserting 、updating、deleting
组成。如果条件谓词值为true,那么就执行相应的触发器语句。
另外,用户可以通过条件谓词判断特定列是否被更新。
在这里插入代码片
举例:判断用户是否对dept表中的dname进行修改
if updating(dname) then -- 若dname被修改
do something about update dname
end if;
创建完触发器我们需要执行,触发器的执行不像存储过程由用户或者程序调用,而是通过一定的触发事件来诱发执行。我们在对dept表进行一定的操作时,就会引发触发器,然后触发器执行往dept_log表插入数据。
insert into dept values(87,'业务部','北京');
update dept set loc='广州' where deptno=101;
delete from dept where deptno=87;
执行完操作之后查询一下日志表
select * from dept_log;
for each row
举例:创建一个商品种类表,包括商品序列号和商品名称列
create table goods(
id int primary key,
good_name varchar2(50)
);
为了让id生成不重复的有序值,这里需要创建一个序列
create sequence seq_id;
创建一个行级触发器,为id赋值
create or replace trigger tri_insert_good
before insert on goods --在goods表里,insert数据前引起本触发器运行
for each row --设置行级出发
begin
select seq_id.nextval
into :new.id --:new.id 列标识符,用来指向新行的id列
from dual; --从序列中生成一个新值,赋值个当前插入行的id列
end;
我们可以通过上面的列标识符来访问当前正在受到影响(添加、修改、删除等操作)的数据行,列标识符可以分为“原值标识符”和“新值标识符”,写法是old.列名
和new.列名
。
new.id
通常在insert
和update
中使用,因为delete
无法产生新值。
向表中插入两条数据,一条指定id,一条不指定,并查询
insert into goods(good_name) values('苹果');
insert into goods(id,good_name) values(9,'西瓜');
select * from goods;
查询结果
可以发现,无论指定与否,数据均插入成功,但是西瓜的序号并不是我们插入的9,而是2。
这是因为在触发器中将序列seq_id
的nextval
属性值赋给了:new.id
,其属性值是不间断的,这个咧标识符的值就是当前插入行的id列的值。
举例:创建视图,保罗dept表与emp表
首先需要进入system模式给scott分配创建视图权限
grant create view to scott;
创建视图
create or replace view v_emp_dept
as select empno,ename,dept.deptno,dept.dname,job,hiredate
from dept,emp
where emp.deptno=dept.deptno;
在没创建触发器之前,尝试向视图中插入数据就会报错。
创建一个关于v_emp_dept视图的替换触发器,在该触发器下插入两行相互关联的数据
create or replace trigger tri_insert_view
instead of insert --创建触发器
on v_emp_dept --行级视图
for each row
declare
row_dept dept%rowtype;
begin
select * into row_dept from dept where deptno = :new.deptno; --检索部门指定编号记录行
if sql%notfound then
insert into dept(deptno , dname) values ( :new.deptno,:new.dname) ; --向dept表中插入数据
end if;
insert into emp(empno ,ename ,deptno ,job ,hiredate)
values(:new.empno, :new.ename , :new.deptno , :new.job, :new.hiredate) ; --向emp表中插入数据
end tri_insert_view ;
向视图中插入一条记录,然后检索插入的记录行
insert into v_emp_dept(empno,ename,deptno,dname,job,hiredate)
values(8888,'东方',10,'test','test',sysdate);
select * from v_emp_dept where empno=8888;
CREATE、ALTER、DROP、ANALYZE、COMMENT、GRANT、REVOKE
等举例:创建一个日志信息表
create table ddl_oper_log
(
db_obj_name varchar2(20), --数据对象名称
db_obj_type varchar2(20), --数据对象类型
oper_action varchar2(20), --具体DDL行为
oper_user varchar2(20), --操作用户
oper_data date --操作日期
);
创建一个关于scott用户的DDL操作的触发器,然后将DDL相关操作信心插入到日志表中
create or replace trigger tri_ddl_oper
before create or alter or drop
on scott.schema --scott模式下,在创建、修改、删除数据对象之前将引发该触发器运行
begin
insert into ddl_oper_log values(
ora_dict_obj_name, --操作的数据对象名称
ora_dict_obj_type, --对象类型
ora_sysevent, --系统事件名称
ora_login_user, --登录用户
sysdate);
end;
在scott模式下进行如下操作,引发触发器运行,然后并查询日志表
create table tb_test(id number);
create view v_test as select empno,ename from emp;
alter table tb_test add(name varchar2(10));
drop view v_test;
select * from ddl_oper_log;
指Oracle数据库系统的事件中进行出发的触发器,如Oralce实例的启动与关闭
程序包规范规定了程序包中可以使用哪些变量、类型、游标和子程序
create or replace package 程序包名 is
规范内声明的变量;
规范内声明的类型;
规范内定义的游标;
规范内声明的函数;
规范内声明的存过过程;
end 程序包名;
举例:创建一个程序包规范,首先在该程序包中声明一个可以获取指定部门的平均工资的函数,然后声明一个可以实现按照指定比例上调指定职务工资的存储过程
create or replace package pack_emp is
function fun_avg_sal(num_deptno number) return number; --获取指定部门的平均工资
procedure pro_regulate_sal(var_job varchar2,num_proportion number); --按照指定比例上调指定职务的工资
end pack_emp;
从代码中可以看到,在规范声明的函数和存储过程只有头部的声明,而没有函数体和存储过程主体,符合规范特点。
create package body
语句create or replace package body 包名 is
程序包主体的内部变量
游标主体
从规范中引入的头部声明
{begin
SQL语句,函数功能主要实现部分;
exception
异常处理语句
end 函数名称}
从规范中引入的存储过程声明
{begin
SQL语句,存储过程功能主要实现部分;
exception
异常处理语句
end 存储过程名称}
...
end 包名
举例:创建程序包pack_emp,并在该主体中实现规范中声明的函数与存储过程对应
create or replace package body pack_emp is
function fun_avg_sal(num_deptno number)return number is --引入规范中的函数
num_avg_sal numberm --定义内部变量
begin
select avg(sal) into nu_avg_sal from emp
where deptno=num_deptno; --计算某个部门的平均工资
return(num_avg_sal); --返回平均工资
exception
when no_data_found then --若未发现记录
dbms_output.put_line('该部门编号不存在员工记录');
return 0; --返回0
end fun_avg_sal;
procedure pro_regulate_sal(var_job varchar2,
num_proportion number) is --引入规范中的存储过程
begin
update emp set sal=sal*(1+num_proportion)
where job=var_job;
end pro_regulate_sal;
end pack_emp;
删除程序包
drop package 包名