数据库中的函数-包-变量-触发器

版权声明:作者: 子非鱼Leo 关于作者:专注大数据,java学习! 本文为博主原创文章,欢迎转载,但未经博主同意必须保留此段声明,且在文章页面明显位置给出原文链接 如有问题, 欢迎共同进步!邮箱[email protected] 原文链接 https://blog.csdn.net/Leo1120178518/article/details/86147688

在数据库的操作中,在编写sql语句的时候,我们可能会用到函数以及触发器等的操作来简化我们的工作。
在这片文章中,也和我以前的博文一样,不是纯粹的理论讲解,而是采用的是案例的方式进行笔记的记录说明。还是用到的以前博文中的员工表emp

1.认识plsql的函数

问题1 输入雇员姓名返回年薪

 create or replace function sp_func(spName varchar2) return number is
  yearSal number(7,2);
  begin
    select sal*12+nvl(comm,0)*12 into yearSal from emp where ename=spName;
    return yearSal;
  end;

总结:
语法:

 create 【or replace】 function 函数名(参数1 类型1,参数2 类型2) return 返回值类型
                   is 变量、常量和游标的定义;
                  begin
                        执行部分
                     exception
                        异常处理部分(可选)
                   end;

调用函数的第一种方式

           var  income number;                                          --定义全局变量var 变量名  变量类型
           call sp_func('SCOTT') into:income;   --全局变量赋值:call 函数into:全部变量名
           print income;                        --打印全局变量 print 全局变量名

调用函数的第二种方式

select sp_func('SCOTT') from dual;

2,包

包:
主要是一种用来组织和管理过程和函数的一种机制,主要由两部分组成,包规范和包体
包里面主要是声明一下过程和函数,过程和函数的实现放在包体里面

–创建包:包里面只是声明过程和函数

问题:创建一个包,包含一个过程,包含一个函数,
过程:输入员工姓名,新的工资,更新员工工资。
函数:输入员工的姓名,计算该员工的收入

          createor replace package sp_package is
            procedure update_sal(name emp.ename%type,newSal emp.sal%type);
            function annual_income(name emp.ename%type) return number;
           end;

总结
语法:创建包

       create 【or replace】 package 包名  is
              过程1声明
              过程2声明
              .......
              函数1声明
              函数2声明
              ....
         end;

过程的实现放在包体里面,包里面只是声明过程和函数
–创建包体:

  create or replace package body sp_package is
                     procedure update_sal(name emp.ename%type,newSal emp.sal%type) is
                       begin
                         update emp set sal=newSal where ename=name;
                       end;
                      
                     function annual_income(name emp.ename%type) return number is
                       yearSal number(7,2);
                       begin
                         select sal*12+nvl(comm,0)*12 into yearSal from emp where ename=name;
                         return yearSal;
                       end;
              end;

创建包体的语法

  create 【or replace】 package body 包名 is
         过程1实现体
         过程2实现体
         ....
         函数1实现体
         函数2实现体
  end;

包中的过程和函数的调用 通过 包名.过程名或者 包名.函数名 来调用的

exec sp_package.update_sal('SCOTT',4500);
 
var income number;
call sp_package.annual_income('SCOTT') into:income;
print income;
select sp_package.annual_income('SCOTT') from dual;

PL/SQL变量:标量类型,复合类型,参照类型lob(large object)类型

–标量类型: 变量名【constan】 数据类型【not null】【:=值】|【default 值】;

案例:
定义一个变长的字符串:v_ename varchar2(20);
定义一个小数:v_vearsal number(7,2);
定义一个小数,给初始值为3.14:v_sal number(3,2):=3.14;
定义一个常量PI给默认值为3.14:PI constant number(3,2):=3.14;
定义一个日期类型;v_hiredate date
定义一个布尔类型 v_invid boolean not null default false;

编写一个PL/sql块,输入员工编号,显示雇员的姓名和工资,以及个人所得税(税率为0.05)

 declare
         v_tax_rate constant number(3,2):=0.05;
         v_ename emp.ename%type;
         v_sal emp.sal%type;
         v_personal_tax number(7,2);
      begin
         select ename,sal into v_ename,v_sal from emp where empno=&no;
v_personal_tax=v_sal*v_tax_rate;
         dbms_output.put_line('姓名:'||v_ename||'工资是'||v_sal||'个人所得税'||v_personal_tax);
      end;
  %type :输入标量类型一种,比较灵活  v_ename emp.ename%type;表示v_ename 和emp表中的ename类型一样

复合类型:
(1)PL/SQL的记录类型:
(2)pl/sql的表类型
(3)嵌套表类型
(4)varray类型

(1)PL/SQL记录类型:%rowtype

案例:输入一个员工编号,显示该员工的所有信息
      declare
         v_emp emp%rowtype;
       begin
         select *into v_emp from emp where empno=&no;
         dbms_output.put_line('姓名:'||v_emp.ename||'工作:'||v_emp.sal||'奖金:'||v_emp.comm)
       end;

记录类型(自定义的)

 declare
      type emp_record_type is record(name emp.ename%type,salary emp.sal%type,dept emp.deptno%type);
      sp_record emp_record_type;
      begin
        select ename,sal,deptno into sp_record from emp where empno=&no;
        dbms_output.put_line('姓名:'||sp_record.name||'工资'||sp_record.salary||'部门'||sp_record.dept);
      end;

记录类型的语法:
type 类型名 is record(变量1 数据类型1 ,变量2 数据类型2,…)
变量名 类型名;

PL/SQL的表类型

  --表类型(类似于高级语言中的数组)
  --定义表类型时,不定义表的大小
  --定义表类型时,需要指定表中的每个数据的类型
  --表类型的下标可以为正,可以为负。
  declare
        type emp_table_type is table of emp.ename%type index by binary_integer;
        sp_table emp_table_type;
        begin
          select ename into sp_table(-1)from emp where empno=&no;

参照类型:游标

案例:输入一个部门名称,显示该部门所有员工的姓名和工资

declare
type sp_emp_cursor is ref cursor;
test_cursor sp_emp_cursor;
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
  open test_cursor for select ename,sal from emp where deptno=&no;
  loop
    fetch test_cursor into v_ename,v_sal;
    exit when test_cursor%notfound;
    dbms_output.put_line('姓名:'||v_ename||';工资:'||v_sal);
  end loop;
  close test_cursor;
end;
  --总结:
      --(1)定义:type 类型名 is ref cursor: 表明sp_cursor  是一个游标类型
      --(2)test_cursor sp_emp_cursor;定义一个游标变量test_cursor,是sp_emp_cursor类型
      --(3)open test_cursor  for  查询语句;
      --(4)fetch 游标变量名into 变量1,变量2;
      --(5)exit when  test_cursor%notfound;   定义循环退出的条件
      --(6)close test_cursor :游标使用完要关闭游标,关闭游标

写法2(建议使用这一种写法)

declare
cursor v_cursor is select ename,sal from emp where deptno=&no;
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
  open v_cursor;
  loop
    fetch v_cursor into v_ename,v_sal;
    exit when v_cursor%notfound;
    dbms_output.put_line('姓名:'||v_ename||';工资:'||v_sal);
  end loop;
  close v_cursor;
end;

/*
cursor 游标变量名is select 语句
open 游标变量
fetch 游标变量into 变量1,变量2,…
close 游标变量

*/

–写法3:用for循环使用游标

declare
cursor v_cursor is select ename,sal from emp where deptno=&no;
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
  for v_cursors in v_cursor loop
    v_ename:=v_cursors.ename;
    v_sal:=v_cursors.sal;
    dbms_output.put_line('姓名:'||v_ename||';工资:'||v_sal);
  end loop;
end;

–for循环会自动打开游标和关闭游标

触发器:

当用户登录、退出或者是对对象进行操作的时候,或者是进行ddl操作的时候,引起某个存储过程的执行,我们把这样一个隐含调用的存储过程称之为触发器
1)我们在开发中,可能会提出这样的问题:
–当用户登录的时候,我们希望能够自动记录用户的名字和登录的时间
–当用户在周四或非工作日对表进行修改的时候,不允许这样做。
–当用户去删除一条记录的时候,希望能够将删除记录自动的备份到另外一张表中

解决方法:触发器
触发器分类:
dml触发器(insert,update,delete),
ddl触发器,系统事件触发器(登录、退出等操作)

下面我们通过例子,来具体的讲解
案例1:创建一张my_emp表,给该表添加记录,提示“添加一条数据”

--语句级的触发器:针对update,insert,select只触发一次操作
create table my_emp(id number(3),name varchar2(10));
 
create or replace trigger tri1
after insert on scott.my_emp
begin
  dbms_output.put_line('添加一条数据');
end;

–执行3次

insert into my_emp values(101,'ellen')

–案例2:在my_emp表中,修改id为101的员工信息的时候,修改一次提示一次
–for each row表明这是一个行级触发器:修改一行,触发一次输出操作

create or replace trigger tri2
after update on scott.my_emp
for each row
begin
  dbms_output.put_line('修改一条记录');
end;

–创建触发器的语法结构
/*
create [or replace] trigger trigger_name
{befor|after]} {insert|update|delete}
on table_name
[for each row]
[when condition]
begin
–触发的操作
end;
*/

–案例3:在删除my_emp表中的记录的时候,提示’删除一条记录’

create or replace trigger tri3
after delete on scott.my_emp
for each row
begin
  dbms_output.put_line('删除一条记录');
end;

–案例4:在删除my_emp表中的记录的时候,触发报错操作,不让删除

create or replace trigger tri4
before delete on scott.my_emp
begin
  raise_application_error(-20001,'不能删除');
end;

/*
raise_application_error是oracle给我们提供的存储过程,
可以传入两个参数,第一个参数是错误号,-20000–20999之间,
第二个参数是报错信息
*/

–案例5:为了禁止工作人员在周四删除my_emp表中的员工信息,创建一个触发器完成这个操作

create or replace trigger tri5
before delete on scott.my_emp
begin
  if to_char(sysdate,'day')='星期四' then
    raise_application_error(-20002,'对不起,周四不允许删除员工信息');
  end if;
end;

–案例6:禁止工作人员在周四修改员工信息(insert update delete),不同操作报错信息不同

create or replace trigger tri6
before insert or update or delete on scott.my_emp
begin
  case
    when inserting then
      dbms_output.put_line('请不要添加');
      raise_application_error(-20003,'请不要添加');
    when updating then
      raise_application_error(-20004,'请不要修改');
    when deleting then
      raise_application_error(-20005,'请不要删除');
  end case;
end;

–当触发器中同时包含多个触发事件时,为了区分触发事件,可以使用三个条件:updating,deleting,inserting

–案例7:修改员工工资的时候,要求新工资必须比原来工资高,创建一个触发器来解决这个问题,输出新工资和老工资

create or replace trigger tri7
before update on scott.emp
for each row
begin
  if :new.sal<:old.sal then
    raise_application_error(-20006,'新工资不能低于原来工资');
  else
    dbms_output.put_line('原来的工资是:'||:old.sal||'新的工资:'||:new.sal);
  end if;
end;

–:new:update操作完成之后的列的值
–:old:update操作完成前的列的值

–案例8:编写一个触发器,当用户删除my_emp的记录的时候,自动将删除的id和name备份到另外一张表中

create table my_emp_bak as select * from my_emp where 1=2;
 
create or replace trigger tir8
before delete on scott.my_emp
for each row
begin
  insert into my_emp_bak values(:old.id,:old.name);
end;

–禁用触发器

alter trigger trigger_name disable;

–启用触发器

alter trigger trigger_name enable;

–删除触发器

drop trigger trigger_name;

–编写一个触发器,控制员工的新工资不能低于原来的工资,但是也不能高于原来工资的20%

create or replace trigger tri9
before update on scott.emp
for each row
begin
  if :new.sal<:old.sal or :new.sal>:old.sal*1.2 then
    raise_application_error(-20007,'工资范围不对');
  end if;
end;

–作业:创建一张学生表stu(id,name,birthdate),编写一个触发器,要求是:如果学生年龄小于18岁,则不允许插入

–Oracle的系统触发器:主要是针对oracle的系统事件的一个触发器
–Oracle定义的常用的系统事件:
–ora_client_ip_address:返回客户端的ip
–ora_database_name:返回数据库名
–ora_login_user:返回登录用户名
–ora_sysevent:返回触发器的系统事件名
–ora_des_encrypted_password:返回加密后的密码

–案例:为了记录用户登录和退出的时间,可以建立登录和退出的触发器

create table log_table(username varchar2(10),logon_time date,logoff_time date,address varchar2(20));

–创建登录触发器​

create or replace trigger logon
after logon on database
begin
  insert into log_table(username,logon_time,address)values(ora_login_user,sysdate,ora_client_ip_address);
end;

–创建退出触发器

create or replace trigger logoff
before logoff on database
begin
  insert into log_table(username,logoff_time,address)values(ora_login_user,sysdate,ora_client_ip_address);
end;

–系统触发器需要时系统管理员身份才能创建

–ddl触发器:
–编写一个触发器,可以记录用户所进行的ddl操作

create table my_ddl_event(event varchar2(20),username varchar2(10),time date);
 
create or replace trigger ddl_tri
after ddl on scott.schema
begin
  insert into my_ddl_event values(ora_sysevent,ora_login_user,sysdate);
end;

–禁用一张表上所有的触发器:

alter table 表名 disable all triggers;

–激活一张表上所有的触发器:

alter table 表名 enable all triggers;

猜你喜欢

转载自blog.csdn.net/Leo1120178518/article/details/86147688
今日推荐