plsql : procedure language sql 是Oracle特有的数据库编程语言,普通的sql可以实现对数据的增删改查,一条sql很难做到 即查询,又根据不同的条件筛选数据,修改数据,而PLSQL可以帮我们实现
基本语法
分成三部分:
- 声明部分 (声明一些临时变量,后续使用)
- 可执行部分 (我们的业务逻辑)
- 异常处理部分(可执行部分出现的异常信息,并处理)
1
2
3
4
5
6
7
8
9
10
11declare
-- 声明部分
自定义的变量名 变量类型;
-- 变量可以边声明变初始化
v_name varchar(2) :='zhangsan';
begin
-- 执行业务逻辑
exception
-- 异常处理部分
end;
Oracle 常用的 变量类型
- 定义数据表时使用的变量类型(varchar2 number) 都能用
- 列级变量– 表名.列名%type
- 行级变量– 表名%rowtype
PLSQL的 分支 选择 判断
分支判断
1 | if 条件1 then |
while循环:
1 | while 条件 loop |
例: 输出1-101
2
3
4
5
6
7
8declare
i number:=1;
begin
while i<=10 loop
dbms_output.put_line(i);
i:= i+1;
end loop;
end;
while 循环,不满足条件自动退出
for 循环
1 | for 自定义的变量X in [reverse反转终止值和初始值] 初始值..终止值 loop |
输出10 - 1:
1 | declare |
for循环中循环遍历的下标不能作为赋值的对象
for循环中,自定义的变量X 不用提前在 declare和begin之间声明
for循环的退出条件是 自定义的变量X 从起始值到终止值为止
loop 循环
1 | loop |
输出 1-101
2
3
4
5
6
7
8
9declare
i number := 1;
begin
loop
i := i+1;
dbms_output.put_line(i);
exit when i=10;
end loop;
end;
loop退出循环的条件是 exit when 我们手动控制
- 例: 查询7369的工资, 并且打印出来
– 知道什么是 引用类型(单列的值) 表名.列名%type
– 如何赋值 into
1 | declare |
- 例 查询7369 的员工信息, 并且打印出来
-- 关键是 找个变量把结果存起来 行级变量 %rowtype
1
2
3
4
5
6
7
8
9
10
11
12
--sql select * from EMP where EMPNO=7369;
declare
vrow emp%rowtype; -- 行级变量 表名%rowtype
begin
select * into vrow from EMP where EMPNO=7369;
-- dbms_output.put_line(vrow); 不能直接打印行!!! 而是 行.列 打印出来
dbms_output.put_line('员工名称'||vrow.ENAME||'员工编号'||vrow.EMPNO);
end;
注意点: – dbms_output.put_line(vrow); 不能直接打印行!!! 而是 行.列 打印出来
游标
什么是游标? 能给我们带来什么?
我们可以把游标理解成一个指针,或者一个箭头,他可以指向我们给定的结果集(这个结果集其实就是我们的sql select的查询结果),然后我们可以用plsql的代码,控制游标遍历这个结果集,在遍历的时候我们可以取出当前行,也能取出当前行的具体一列的值,然后我们根据取出的值进行判断,下一步是该 update表数据,还是delete表数据
最开始当游标指向这个结果集的时候,它指向的是结果集的表头部分,每当我们fetch一下,他就把下一行的数据(也就是第一行)给我们,再fetch 就给我们第二行
游标的分类
- 静态游标
- 隐式游标
- 没参数的,显示游标
- 动态游标
- 带参数的显示游标(ref)
- 系统游标
静态游标和动态游标的最大区别是啥?
- 静态游标在编译的时候就知道了结果集是什么, 因为它没有任何需要我们传递进去的参数
- 动态游标带参数,参数具体是几,只有真正运行的时候才知道,它的结果集是运行时才知道的,称它是动态游标
语法
1 | cursor 游标名 in 查询结果集 -- 不带参数 |
隐式游标的使用:
1 | begin |
每条sql默认自带一个隐式游标,游标的名字是 sql, 自动打开/关闭
没参数的静态游标
1 | 开发步骤; |
- 例 输出员工表中的员工姓名和工资(不带参数游标)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25-- select ename,SAL from emp ;
-- 游标 -- 结果集: 所有员工
-- 声明一个变量,用来记录一行数据 %rowtype
declare
-- 声明游标
cursor vrows is select * from emp ;
-- 声明一个中间标量, 中转上面游标中获取的每一行
vrow emp%rowtype;
begin
-- 开启游标
open vrows;
-- 遍历打印
loop
fetch vrows into vrow; -- 取一条数据
exit when vrows%notfound; -- 判断数据是否为空
--- 再次强调一下,不能直接打印行
-- dbms_output.out_line(vrow);
---
dbms_output.put_line('姓名'||vrow.ename||'员工号'||vrow.EMPNO); -- 这里输出的 内容,一定要存在于 上面的游标中
end loop;
-- 关闭游标
close vrows;
end;
如果没有判断下一次获取的是否为空,当游标循环遍历到结果集的最后一行的时候,再次fetch,依然是最后一行的数据,还可能是内存溢出,所以判断条件紧跟在fetch之后,然后再是我的业务逻辑
系统游标
— 系统 引用游标1
2
3
4
51. 声明游标
-- 游标名 游标类型 sys_refcursor (后面的 refcursor是个合成词)
2. 打开游标,关联结果集
3. 从游标中取数据
4. 关闭游标
例:
– 例 输出员工表中的员工姓名和工资(不带参数游标)
1 | declare |
带参数的动态游标
— 输出指定部门下的员工的姓名和工资( 带参数的游标 )
1 | /* |
系统游标 和 不带参的静态游标
- 相同点: 都是指向了某个结果集,可以一条一条的遍历结果集,获取出每一条数据
- 不同点: 获取结果集的位置不同
- 普通游标获取操作集合的位置是 declare 和 begin 之间
- 系统游标 在begin 和 end 之间 的打开游标位置 ,使用的关键字不再是 is 而是 for
- 声明方式不同!
- 普通游标可带参数
- 系统游标的声明固定写法; 名 sys_refcursor;
for 循环遍历游标
上面的例子都是使用loop循环遍历游标,控制退出的条件是 游标%notfound
下面使用for循环遍历游标
1 |
|
不用提前声明 需要中转每一行的变量
不用手动控制循环退出的条件
小案例
- 游标的小案例:
– .1 按照员工工作 给员工涨工资, 总裁涨1000 经理涨 800 其他人 400
1 | /* -- 查询经理信息 |
注意点 : plsql和sql混在一起写的时候,注意点就是 sql中的赋值运算符是 = 但是plsql中的赋值运算符是 := 别整混了
异常
有啥用?
通常异常用来处理我们在业务逻辑阶段可能出现的逻辑错误,我们捕获了相应的异常之后自己处理掉,不至于影响后续的操作
语法
1 | declare |
Oracle中异常的分类
- 系统异常
- zero_divide : 除零异常
- value_error : 类型转换异常
- too_many_row: 查询查了多行的记录,但是却把它赋值给一个行级的变量
- no_data_found: 没有找到记录
- raise_application_error(状态码,’异常信息’) : 运行时,系统异常
- 用户自定义异常
用户自定义异常,分三步
- 声明异常
- raise 抛出异常
- 在exception后面捕获异常
- 我们操作的结果集只能来自游标的结果集,(下面解释)
坑
- 看下面的代码,查询一个不存在的人的信息,看着貌似会捕获处理我们自定义的异常,但是其实并不会! 因为执行到 select的时候就会出现异常 no_data_found, 直接停止往下执行,我们的逻辑根本不会触发
1 | declare |
- 简单的select 查询没办法完成我们的需求, 因为它碰到异常会报错,但是游标不会!!! %found %notfound
- 解决
- 声明 行级变量
- 声明游标(带参数), 获取要操作的结果集
- 遍历游标, 判断,如果为空,抛出我们自定义的异常
1 | declare |
用户自定义异常的例子
1 | declare |
捕获系统异常的例子:
1 | declare |
储存过程/存储函数 –procedure
什么是存储过程:
存储过程就是一段已经封装好了的 plsql代码片段, 这个代码片段提前已经编译好了,用户在调用的时候就不用再编译大大提高了 数据库的吞吐效率
存储过程不编译是不能被调用的!!!
语法:
1 | create or replace procedure 存储过程的名(参数1 in|out 参数类型,.......) |
in 表示: 该参数是输入参数
out 表示: 该参数是输出参数
例子: 使用存储过程给员工涨工资,
1 | create or replace procedure pro_update_sal(vempno in number , vsal in number) |
一般存储过程中加上 事务的提交,和出现异常时回滚
如何调用1
2
3
4
5
6
7
8
9
10
11
12
13
14-- 调用方式一:
call pro_update_sal(7369,100);
-- 调用方式二:
declare
begin
pro_update_sal(7369,100);
end;
-- 方式三:
begin
pro_update_sal(7369,100);
end;
带返回值的存储过程
1 |
|
不用显示的声明,声明返回值,把查询的结果赋值给 入参位置的out类型的参数就行,她自动的返回
调用
1 | declare |
传递进去两个参数,不需要我们写
t_sal = get_pro(7369,t_sal)
(错误的写法) 她会自动的把数据传递进t_sal ,
什么是存储函数
同样是一段封装在oracle 服务器中的一段plsql 代码片段, 他是已经编译好了的代码片段
语法:
1 | create [or replace] function 存储函数的名称 (参数名 in|out 参数类型,参数名 in|out 参数类型.参数名 in|out 参数类型) return 参数类型 |
例: 使用存储函数 查询指定员工的年薪
1 | create or replace function fun_get_empsal(v_empno number) return number |
调用
1 | declare |
标准sql调用 存储函数1
select ename, fun_get_empsal(EMPNO) from EMP;
存储过程和存储函数的区别
- 其实他们本质上是没有区别的,两者都可以实现相互的功能
- 语法上的区别
- 存储函数参数的小括号后面紧跟着 return语句
- 关键字 一个数 procedure 一个是 function
- 存储函数大多数情况下是给存储过程调用的
- 存储函数可以给标准的sql语句调用,但是存储过程不行
触发器
什么是触发器 –trigger
Oracle 和 Mysql 中都有触发器,
他是一段代码,用来监听用户对数据表的具体某个操作,可以在这个操作前后进行拦截,执行我们添加进去的操作
分类
- 语句级触发器(表级别)
操作无论影响多好行, 触发器执行一次
- 行级触发器 – 关键字 for each row
操作影响多少行,触发多少次
简单的触发器 语法
1 | create or replace trigger -- 创建触发器 |
例: 插入员工之后,输出一句话 XXX – 每次有inset语句, 都会先执行我们的 输出XXX
1 | create or replace trigger trrigger_one |
- 行级触发器: for each row
行级触发器有两个内置的对象
:new 记录了表中某个字段更新前的值
:old 记录表中的某个字段更新后的值
在 on 表名 和 declare 之间添加 for each row , 使触发器升级成 行级触发器
触发器的练习— 模拟mysql的 id 自增长
思路: 触发器监听用户对表的insert操作,每当监听到有插入操作的时候,就会使用我们的 序列自增1,给它补全
1 | -- 创建表 |
注意点: :NEW这个内置对象只属于行级触发器
– create 关键字 :
– 创建表
– 创建view
– 创建index
– 创建synonym
– 创建存储过程
– 创建用户
– 创建触发器
– 创建序列