Procedure Language

　　Procedure Language 实际上是Oracle对SQL语言的能力扩展,让SQL语言拥有了if条件判断,for循环等处理。



https://docs.oracle.com/cd/E16338_01/index.htm

https://www.uniqlo.com/jp/store/goods/414055-00#thumbnailSelect



https://oreno-it.info/archives/752

https://www.cnblogs.com/gdwkong/p/8509354.html

https://www.oracle.com/technetwork/jp/ondemand/database/db-basic/b-11-plsql-1448399-ja.pdf

http://otndnld.oracle.co.jp/document/products/oracle11g/111/doc_dvd/appdev.111/E05670-03/overview.htm#i8859
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/appdev.102/B19257-01/collections.html#1004
https://docs.oracle.com/cd/E82638_01/sqlrf/DROP-TYPE.html#GUID-2CBA2EFD-1B01-46A8-A4CD-B2975D3A1D67


http://otndnld.oracle.co.jp/document/products/oracle11g/111/doc_dvd/appdev.111/E05670-03/static.htm#BABJCAAG
一、PLSQL基本语法

复制代码
1 DECLARE 
2     -- 声明部分
3     变量名 变量类型 := 初始值 
4     变量名 emp.sal % TYPE  -- 引用类型的变量
5           emp % rowtype -- 记录型变量           
6 BEGIN
7     -- 业务逻辑
8 END ;
复制代码
1、变量的声明与使用

复制代码
 1 -- 已知数据类型的赋值声明 
 2 DECLARE 
 3     i NUMBER := 100 ;
 4 BEGIN
 5     -- 输出语句相当于 System.out.print();
 6     dbms_output.put_line('Hello World!' || i) ;
 7 END ;
 8 
 9 -- 未知数据类型的类型声明
10 -- 输出7369的工资
11 
12 DECLARE 
13     vsal emp.sal % TYPE ;
14 BEGIN
15     -- 给变量赋值
16     SELECT sal INTO vsal FROM emp WHERE empno = 7369 ; 
17     dbms_output.put_line(vsal) ;
18 END ;
19 
20 -- 记录型变量声明与赋值
21 -- 输出7369的所有信息
22 DECLARE 
23     vrow emp % rowtype ;
24 BEGIN    
25     SELECT * INTO vrow FROM emp WHERE empno = 7369 ;
26     dbms_output.put_line(vrow.empno||'   '|| vrow.ename);
27 END ;
复制代码
2、if条件判断语法与使用

复制代码
 1 -- 根据不同年龄输出信息
 2 DECLARE
 3     -- 由客户端输入     
 4     age number := &aaa;
 5 BEGIN
 6     IF age <= 18 THEN
 7         dbms_output.put_line('未成年人');
 8     ELSIF age > 18 AND age <= 24 THEN
 9         dbms_output.put_line('年轻人');
10     ELSIF age > 24 AND age < 48 THEN
11         dbms_output.put_line('中年人');
12     ELSE 
13         dbms_output.put_line('老年人');
14     END IF;
15 END;
复制代码
3、三种循环

复制代码
 1 /*
 2    三种循环
 3    for 变量名 in 起始值..结束值  loop
 4      
 5    end loop; 
 6   ----------------------------------
 7    while 条件 loop
 8      
 9    end loop;
10   -----------------------------------    
11    loop
12      exit when 退出的条件
13      循环体
14    end loop;
15 */
16 
17 -- for 循环
18 -- 输出1-10
19 DECLARE 
20 
21 BEGIN
22     FOR i IN 1..10 LOOP
23         dbms_output.put_line(i);
24     END LOOP;
25 END;
26 -- 输出10-1
27 DECLARE 
28 
29 BEGIN
30     FOR i IN REVERSE 1..10 LOOP
31         dbms_output.put_line(i);
32     END LOOP;
33 END;
34 
35 -- while 循环
36 DECLARE 
37     i NUMBER := 1;
38 BEGIN
39     WHILE i <= 10 loop
40         dbms_output.put_line(i);
41         i := i+1;
42     END LOOP;
43 END;
44 
45 -- 简单循环
46 DECLARE 
47     i NUMBER := 1;
48 BEGIN 
49     LOOP
50         EXIT WHEN i > 10;
51         dbms_output.put_line(i);
52         i := i+1;
53     END LOOP;
54 END;
复制代码
二、游标

1、游标概述　　

1.1 游标: (光标/指针) 是对查询结果集的封装, 相当于是jdbc中的ResultSet

1.2 语法:

1 -- 声明游标
2     CURSOR 游标名 IS 查询语句;
3     CURSOR 游标名(参数名 参数类型) IS 查询语句 WHERE 列名 = 参数名;
1.3 开发步骤:

1.打开游标 open 游标名

2.从游标中提取数据:

fetch 游标名 into 变量

　　   游标名%notfound 没有数据

　　　游标名%found 找到数据

3.关闭游标 close 游标名

2、使用示例：

复制代码
 1 -- 无参 
 2 -- 输出所有员工的信息
 3 DECLARE
 4    -- 声明游标
 5   CURSOR vemps IS SELECT * FROM emp;
 6    -- 声明变量
 7   vrow emp % rowtype;
 8 BEGIN
 9    --1. 打开游标
10    open vemps;
11    --2. 提取数据
12    LOOP
13        FETCH vemps INTO vrow;
14         -- 判断是否有数据
15        EXIT WHEN vemps % notfound;
16         -- 打印数据
17        dbms_output.put_line('姓名:'||vrow.ename||'  工资:'||vrow.sal);
18    END LOOP;
19    
20    -- 关闭游标
21    CLOSE vemps;
22 END;
23 ---------------------------------------------------------------- 
24 -- for 变量游标        
25 DECLARE
26    -- 声明游标
27    CURSOR vemps IS SELECT * FROM emp;
28    -- 声明记录型变量
29    vrow emp % rowtype;
30 BEGIN
31    -- 循环遍历游标
32    FOR vrow IN vemps
33    LOOP
34         dbms_output.put_line('姓名:'||vrow.ename||' 工资:'||vrow.sal);
35    END LOOP;
36 END;
37 
38 -- ===============================================================
39 -- 有参
40 -- 输出指定部门的员工信息
41 DECLARE
42    -- 声明游标
43    CURSOR vemps(vdeptno NUMBER) IS SELECT * FROM emp WHERE deptno = vdeptno;
44    -- 声明记录型变量
45    vrow emp % rowtype;
46 BEGIN
47    -- 1. 打开游标
48    OPEN vemps(20);
49    -- 2.循环遍历游标
50    LOOP
51     FETCH vemps into vrow;
52     EXIT when vemps % notfound;
53       -- 打印数据
54         dbms_output.put_line('姓名:'||vrow.ename||' 工资:'||vrow.sal);
55    END LOOP;
56    -- 3. 关闭游标
57    CLOSE vemps;
58 END;
复制代码
三、例外

1、例外概述

      例外 (意外): 相当于是java异常

　　语法: 

复制代码
 1 declare 
 2     声明部分 
 3 begin 
 4     业务逻辑
 5 exception 
 6     处理例外 
 7     when 例外1 then 
 8 
 9     when 例外2 then 
10 
11     when others then 
12 
13 end;
复制代码
　　常见的系统的例外:

zero_divide : 除零例外
value_error : 类型转换
no_data_found : 没有找到数据例外
too_many_rows : 查询出多行记录,但是赋值给了单行变量
2、例外使用示例

复制代码
 1 DECLARE
 2     i NUMBER;
 3     vrow emp % rowtype;
 4 BEGIN
 5 --     i := 5/0;
 6 --     i := 'aaa';
 7 --     select * into vrow from emp where empno = 1234566;
 8     select * into vrow from emp;
 9 EXCEPTION
10     WHEN too_many_rows THEN
11         dbms_output.put_line('查询出多行记录,但是赋值给了单行变量');
12     WHEN no_data_found THEN
13         dbms_output.put_line('发生了没有找到数据例外');
14     WHEN value_error THEN
15         dbms_output.put_line('发生类型转换的例外');
16     WHEN zero_divide THEN
17         dbms_output.put_line('发生除零的例外');
18     WHEN others THEN
19         dbms_output.put_line('发生未知的例外');
20 END;
复制代码
3、自定义例外

　　语法：

复制代码
 1 DECLARE 
 2     -- 声明例外
 3     例外名称 EXCEPTION ;
 4 BEGIN
 5     -- 抛出例外
 6     raise 例外名称 ; 
 7 EXCEPTION 
 8     -- 捕获例外
 9     WHEN 例外名称 THEN
10     ....
11 END ;
复制代码
　　使用示例：

复制代码
 1 -- 查询指定编号的员工,若没有找到,则抛出自定义例外
 2 DECLARE 
 3     -- 声明游标
 4     CURSOR vemps IS SELECT * FROM emp WHERE empno = 1234 ; 
 5     -- 记录型变量
 6     vrow vemps % rowtype ; 
 7     -- 定义例外
 8     no_emp_found EXCEPTION ; 
 9 BEGIN 
10     --1.打开游标
11     OPEN vemps ;
12     --2.提取记录 
13     FETCH vemps INTO vrow ;
14     -- 判断是否有数据 
15     IF vemps % notfound THEN 
16         -- 抛出例外
17         raise no_emp_found ; 
18     END IF ; 
19     -- 关闭游标
20     CLOSE vemps ; 
21 EXCEPTION 
22     WHEN no_emp_found THEN 
23         dbms_output.put_line('没有找到对应的员工') ; 
24 END ;
复制代码
 四、存储过程

1、概述　　

　　存储过程: 实际上是将一段已经编译好的PLSQL代码片断,封装在数据库中。

　　作用:

1. 提高执行效率

2. 提高代码复用性

　　语法:

复制代码
1 create [or replace] procedure 过程名称[(参数1 in|out 参数类型,参数2 in|out 参数类型)]
2 is | as
3 　　-- 声明　　　　
4 begin
5  　 -- 业务
6 end;
复制代码
2、使用示例

　　

复制代码
 1 -- 给指定员工涨薪,并打印涨薪前和涨薪后的工资
 2 -- 员工编号 : 输入参数
 3 -- 涨多少 : 输入参数
 4 /*
 5    1. 查询当前工
 6    2. 打印涨薪前工资
 7    3. 涨工资
 8    4. 打印涨薪后的工资
 9    5. 提交数据
10 */
11 create or replace procedure proc_updatesal(vempno in number,vcount in number)
12 is
13    -- 声明变量记录当前工资
14    vsal number;    
15 begin
16    --1. 查询当前工资
17    select sal into vsal from emp where empno=vempno;
18    --2. 打印涨薪前工资
19    dbms_output.put_line('涨薪前:'||vsal);
20    --3. 涨工资
21    update emp set sal=vsal+vcount where empno=vempno;
22    -- 4. 打印涨薪后的工资
23    dbms_output.put_line('涨薪后:'||(vsal+vcount));
24    --5. 提交数据
25    commit;
26 end;
27 
28 -- 调用存储过程
29 -- 方式1:
30 call proc_update_sal(7369,100);
31 
32 -- 方式2:
33 declare
34 
35 begin
36    proc_updatesal(7369,100);
37 end;
38 
39 s
40 -- 获取指定编号员工的年薪
41 /*
42    编号: in  输入
43    年薪: out 输出
44 */
45 create or replace procedure proc_getyearsal(vempno in number,vyearsal out number)
46 is
47        
48 begin
49   select sal*12+nvl(comm,0) into vyearsal from emp where empno=vempno;
50 end;
51 
52 -- plsql代码片断中调用
53 declare
54    yearsal number;
55 begin
56    proc_getyearsal(7369,yearsal);
57    dbms_output.put_line(yearsal);
58 end;
59 
60 
61 -- 封装存储过程,输出的是游标类型, 所有员工
62 /*
63    sys_refcursor : 系统引用游标
64 */
65 create or replace procedure proc_getemps(vemps out sys_refcursor)
66 is
67 
68 begin
69     -- 打开游标, 谁调用谁关闭
70     open vemps for select * from emp;   
71 end;
72 
73 declare
74   vemps sys_refcursor;
75   vrow emp%rowtype;
76 begin
77   -- 调用存储过程
78   proc_getemps(vemps);
79   
80   loop
81      fetch vemps into vrow; 
82      exit when vemps%notfound;  
83      dbms_output.put_line(vrow.ename);
84   end loop;
85   -- 关闭游标
86   close vemps;
87 end;
复制代码
五、存储函数

1、存储函数概述

　　存储函数: 实际上是将一段已经编译好的PLSQL代码片断,封装在数据库中。

　　作用:

1. 提高执行效率

2. 提高代码复用性

　　语法: 

复制代码
1 create [or replace] function 函数名称(参数1 in|out 参数类型) return 返回类型 
2 is 
3 
4 begin 
5 
6 end;
复制代码
　　存储过程和存储函数:

1. 函数有返回值,过程没有返回值

2. 函数可以直接在SQL语句中使用,过程不可以

3. 函数能实现的功能,过程能实现

4. 过程能实现的功能,函数也能实现

5. 函数和过程本质上没有区别 通常情况下,我们自己开发封装的是存储过程

2、使用示例

复制代码
 1 -- 存储函数:获取年薪
 2 create or replace function func_getyearsal(vempno number) return number
 3 is
 4   vyearsal number;
 5 begin
 6   select sal*12+nvl(comm,0) into vyearsal from emp where empno=vempno;
 7   return vyearsal;
 8 end;
 9 
10 -- 调用
11 declare
12    yearsal number;
13 begin
14    yearsal := func_getyearsal(7369);
15    dbms_output.put_line(yearsal);
16 end;
17 
18 select emp.*,func_getyearsal(emp.empno) from emp;
复制代码
 六、触发器

　　1、数据库触发器是一个与表相关的、存储的PL/SQL程序。每当一个特定的数据操作语句（insert，update，delete）在指定的表上发出是，Oracle自动地执行触发器中定义的语句序列。

　　2、作用：

监听表中的数据变化；
对表中的数据进行校验
　　3、语法：

复制代码
 1 CREATE [OR REPLACE] TRIGGER 触发器名称 
 2 {BEFORE | AFTER}
 3 {INSERT | UPDATE | DELETE} 
 4 ON 表名 
 5 [ FOR EACH ROW [WHEN(条件)]]
 6 DECLARE
 7     ....
 8 BEGIN
 9     PLSQL块
10 END 触发器名;
复制代码
　　4、触发器的类型

行级触发器：一条SQL语句，影响了多少行记录，触发器就会执行多少次；
两个内置对象：
:new 新的记录
:old 旧的记录
语句级触发器：一条SQL语句，无论影响了多少行记录，都只触发一次；
　　5、使用示例

复制代码
  1 -- 若用户向表中插入数据之后, 打印一句话
  2 create or replace trigger tri_test1
  3 after
  4 insert 
  5 on emp
  6 declare
  7 
  8 begin
  9    dbms_output.put_line('有人插入了....');
 10 end;
 11 
 12 insert into emp(empno,ename) values(9527,'华安');
 13 -- 执行一条更新工资的语句
 14 
 15 -- 周二老板不在,不能办理员工入职(不能向员工表中插入数据)
 16 -- 触发器
 17 -- before insert
 18 -- 判断今天是否是周二
 19 select trim(to_char(sysdate,'day')) from dual;
 20 
 21 create or replace trigger tri_checkday
 22 before
 23 insert
 24 on emp
 25 declare
 26    vday varchar2(20);
 27 begin
 28    -- 查询当前周几
 29    select trim(to_char(sysdate,'day')) into vday from dual;
 30    -- 判断是否为周二,若为周二,则需要中断插入操作
 31    if vday = 'tuesday' then
 32      --                   -20000 - -20999
 33      raise_application_error(-20001,'周二老板不在,不能插入');
 34    end if;
 35 end;
 36 
 37 insert into emp(empno,ename) values(9527,'华安');
 38 
 39 select * from emp;
 40 
 41 -- 语句级触发器
 42 create trigger tri_test3
 43 before
 44 update
 45 on emp
 46 declare
 47 
 48 begin
 49   dbms_output.put_line('语句级触发器'); 
 50 end;
 51 
 52 -- 行级触发器
 53 create or replace trigger tri_test4
 54 before
 55 update
 56 on emp
 57 for each row
 58 declare
 59 
 60 begin
 61   dbms_output.put_line('行级触发器,旧的工资:'||:old.sal||'  新的工资:'||:new.sal); 
 62 end;
 63 
 64 update emp set sal=sal+100;
 65 
 66 -- 6个月 ---> 人事 加薪 ---> 加10块钱 ---> 老板签字
 67 -- 校验员工薪资 调整后的工资一定要 大于 薪资调整前的工资
 68 -- 触发器:  before update on emp
 69 -- 行级触发器
 70 create or replace trigger tri_checksal
 71 before
 72 update
 73 on emp
 74 for each row
 75 declare
 76 
 77 begin
 78   -- 调整后的工资 <= 薪资调整前的工资 ,则中断更新操作
 79   -- :new.sal    <= :old.sal
 80   if :new.sal <= :old.sal then
 81      raise_application_error(-20002,'坑爹的,降薪啦!');
 82   end if;
 83 end;
 84 
 85 update emp set sal=sal-100;
 86 
 87 /*
 88      使用触发器模拟类似auto_increment功能
 89      当用户插入的时候,若为sid为null,则给sid赋值一个编号
 90 */
 91 create table stu(
 92      sid number primary key,
 93      name varchar2(20)
 94 );
 95 
 96 -- 创建一个序列
 97 create sequence seq_stu;
 98 
 99 -- 触发器: before insert on stu
100 -- 行级触发器
101 create or replace trigger tri_auto
102 before
103 insert 
104 on stu
105 for each row
106 declare
107 
108 begin
109    -- 从序列中查询一个数字出来,赋值给sid
110    select seq_stu.nextval into :new.sid from dual;
111 end;
112 
113 -- 同样一张表,有时候自己指定id, 有时候需要数据库自动生成id
114 insert into stu values(null,'zs');
115 insert into stu values(4,'zs');
116 select * from stu;