PL/SQL : Procedure Language/SQL
PLSQL 是对 sql语言的过程化扩展,指在 SQL命令语言中添加了过程处理语句(如:分支、循环),使sql语言具有过程处理能力。把SQL语言的数据库操纵能力和过程语言的数据处理能力结合起来,使得SQL面向过程但比过程语言简单、高效、灵活和使用。
主要分为三个部分:声明部分、可执行部分、异常处理部分 (不区分大小写)
DECLARE
-- 声明变量、游标。
I INTEGER;
BEGIN
-- 执行语句
--[异常处理]
exception
END;
其中 DECLARE部分用来声明变量或游标(结果集类型变量),如果程序中无变量声明可以省略掉
示例:
BEGIN
--打印hello world
DBMS_OUTPUT.PUT_LINE('hello world');
END;
其中DBMS_OUTPUT 为oracle内置程序包,相当于Java中的System.out,而PUT_LINE()是调用的方法,相当于println()方法
环境准备:
scott 是给初学者学习的用户,学习者可以用 scott 登录系统, scott 用户登录后,就可以使用 Oracle 提供的数据库和数据表,这些都是 Oracle 提供的,学习者不需要自己创建数据库和数据表,直接使用这些数据库和数据表练习 SQL 。
或者从Scott中将emp表创建过来,方便下方示例练习:
create table emp as select * from scott.emp
变量名 变量类型(变量长度) 例如: v_name varchar2(20);
变量的基本类型就是 oracle 中的建表时字段的变量如 char, varchar2, date, number, boolean, long
变量赋值的方式有两种:
示例:
DECLARE
-- 名称
v_name VARCHAR ( 20 ) := 'xy';
--薪水
v_SAL NUMBER;
v_addr VARCHAR ( 200 );
BEGIN--在程序中直接赋值
V_SAL := 1111;
SELECT
'测试地址' INTO v_addr
FROM
dual;
--打印变量
DBMS_OUTPUT.PUT_LINE ( '姓名:' || V_NAME || ',薪水:' || V_SAL || ',地址:' || V_ADDR );
END;
通过表名.列名%TYPE指定变量的类型和长度,例如: v_name emp.ename%TYPE;
变量的类型和长度取决于表中字段的类型和长度
-- 查询emp表中7839号员工的个人信息,打印姓名和薪水
DECLARE
-- 姓名
V_NAME TBLUSER.USERNAME%TYPE; -- 声明变量直接赋值
BEGIN
--查询表中的姓名和薪水并赋值给变量
--注意查询的字段和赋值的变量的顺序、个数、类型要一致
SELECT ENAME, SAL INTO V_NAME, V_SAL FROM EMP WHERE EMPNO = 7839;
--打印变量
DBMS_OUTPUT.PUT_LINE('姓名:' || V_NAME || ',薪水:' || V_SAL);
END;
引用型变量的好处:
使用普通变量定义方式,需要知道表中列的类型,而使用引用类型,不需要考虑列的类型,使用%TYPE是非常好的编程风格,因为它使得PL/SQL更加灵活,更加适应于对数据库定义的更新。
接收一整行记录,相当于一个对象
语法: 变量名称 表名%ROWTYPE, 例如: v_emp emp%rowtype;
【示例】查询emp表中7839号员工的个人信息,打印姓名和薪水
-- 查询emp表中7839号员工的个人信息,打印姓名和薪水
DECLARE
-- 姓名
V_NAME EMP.ENAME%TYPE; -- 声明变量直接赋值
--薪水
V_SAL EMP.SAL%TYPE;
BEGIN
--查询表中的姓名和薪水并赋值给变量
--注意查询的字段和赋值的变量的顺序、个数、类型要一致
SELECT ENAME, SAL INTO V_NAME, V_SAL FROM EMP WHERE EMPNO = 7839;
--打印变量
DBMS_OUTPUT.PUT_LINE('姓名:' || V_NAME || ',薪水:' || V_SAL);
END;
变量名 constant 变量类型(变量长度) 例如: v_name constant varchar2(20):='xy';
语法:
BEGIN
IF 条件1 THEN 执行1
ELSIF 条件2 THEN 执行 2
ELSE 执行3
END IF;
END;
【示例】判断emp表中记录是否超过20条,10-20之间,或者10条以下:
DECLARE
--声明变量接受emp表中的记录数
V_COUNT NUMBER;
BEGIN
--查询emp表中的记录数赋值给变量
SELECT COUNT(1) INTO V_COUNT FROM EMP;
--判断打印
IF V_COUNT > 20 THEN
DBMS_OUTPUT.PUT_LINE('EMP表中的记录数超过了20条为:' || V_COUNT || '条。');
ELSIF V_COUNT >= 10 THEN
DBMS_OUTPUT.PUT_LINE('EMP表中的记录数在10~20条之间为:' || V_COUNT || '条。');
ELSE
DBMS_OUTPUT.PUT_LINE('EMP表中的记录数在10条以下为:' || V_COUNT || '条。');
END IF;
END;
语法1
WHILE total <=2300 LOOP
total :+=total+1;
END LOOP;
示例:
DECLARE
step number :=1;
begin
while step <= 10 Loop
DBMS_OUTPUT.PUT_LINE(step);
step := step +1;
end loop;
end;
语法2
Loop
EXIT [when 条件]; --退出循环的条件
End Loop;
示例
declare
step number :=1 ;
begin
loop
exit when step > 10;
DBMS_OUTPUT.PUT_LINE(step);
step := step +1;
end loop;
end;
语法3
FOR I IN 1..3 Loop
语句序列;
END Loop;
示例
declare
step number :=1;
begin
for step in 1..10 loop
DBMS_OUTPUT.PUT_LINE(step);
end loop;
end;
pl/sql 中多条记录使用游标临时存储,类似于java 中jdbc返回的ResultSet集合,通过遍历游标,可以逐行访问处理该结果集的数据。
游标使用方式: 声明—>打开—>读取—>关闭
语法:
CURSOR 游标名 [(参数名 数据类型,参数名 数据类型 ...)] IS SELECT 语句
-- 例如:
cursor cl is select ename from emp;
游标使用步骤:
1. 打开游标:`open cl;` (打开游标执行查询)
2. 取一行的游标的值: `fetch cl into pjob;` (取一行到变量中)
3. 关闭游标:`close cl;` (关闭游标释放资源)
4. 游标的结束方式:exit when c1%notfound
5. 注意: 上面的 pjob 必须与 emp 表中的 job 列类型一致:
定义:pjob emp.empjob%type;
游标的属性 | 返回值类型 | 说明 |
---|---|---|
%ROWCOUNT | 整型 | 获得FETCH语句返回的数据行数 |
%FOUND | 布尔型 | 最近的FETCH语句返回一行数据则为真,否则为假 |
%NOTFOUND | 布尔型 | 与%FOUND属性返回值相反 |
%ISOPEN | 布尔型 | 游标已经打开时值为真,否则为假 |
其中 %NOTFOUND是在游标中找不到元素的时候返回TRUE,通常用来判断退出循环
使用游标方式输出 emp 表中的员工编号和姓名
declare
cursor ps is
select * from emp;
pemp emp%rowtype;
begin
open ps;
loop
fetch ps into pemp;
exit when ps%notfound;
dbms_output.put_line(pemp.empno || ' ' || pemp.ename);
end loop;
close ps;
end;
按员工的工种涨工资,总裁 1000 元,经理涨 800 元其,他人员涨 400 元。
-- 备份出一张新表为 myemp;
create table myemp as select * from emp;
declare cursor pc is select * from myemp;
declare
cursor pc is
select * from myemp;
addsal myemp.sal%type;
pemp myemp%rowtype;
begin
open pc;
loop fetch pc into pemp;
exit when pc%notfound;
if pemp.job = 'PRESIDENT' then addsal := 1000;
elsif pemp.job = 'MANAGER' then addsal := 800;
else addsal := 400;
end if;
update myemp t set t.sal = t.sal + addsal where t.empno = pemp.empno;
end loop;
close pc;
end;
写一段 PL/SQL 程序,为部门号为 10 的员工涨工资。
declare
cursor pc(dno myemp.deptno%type) is
select empno from myemp where deptno = dno;
pno myemp.empno%type;
begin
open pc(20);
loop fetch pc into pno;
exit when pc%notfound;
update myemp t set t.sal = t.sal + 1000 where t.empno = pno;
end loop;
close pc;
end;
pl/sql 可以进行表的操作,判断,循环逻辑处理,但是无法重复调用。
可以理解之前的代码全都编写在了main方法中,是匿名程序. JAVA可以通过封装对象和方法来解决复用问题
PLSQL是将一个个PLSQL的业务处理过程存储起来进行复用,这些被存储起来的PLSQL程序称之为存储过程
存储过程作用:
在开发程序中,为了一个特定的业务功能,会向数据库进行多次连接关闭(连接和关闭是很耗费资源), 需要对数据库进行多次I/O读写,性能比较低。如果把这些业务放到PLSQL中,在应用程序中只需要调用PLSQL就可以做到连接关闭一次数据库就可以实现我们的业务,可以大大提高效率.
ORACLE官方给的建议:能够让数据库操作的不要放在程序中。在数据库中实现基本上不会出现错误,在程序中操作可能会存在错误.(如果在数据库中操作数据,可以有一定的日志恢复等功能.)
存储过程缺点:
CREATE OR REPLACE PROCEDURE 过程名称[(参数列表)] IS
BEGIN
END [过程名称];
参数:可不带参数,输入参数和输出参数
示例:创建存储过程打印helloworld
create or replace procedure HelloWorld is
begin
DBMS_OUTPUT.PUT_LINE('hello world');
end;
-- 调用
BEGIN
HelloWorld;
END HelloWorld;
示例:打印指定员工号的信息
create or replace procedure print_emp_info(I_EMPNO in EMP.EMPNO%type) is
--声明变量接受查询结果
V_ENAME EMP.ENAME%TYPE;
V_SAL EMP.SAL%TYPE;
begin
select ename ,sal into V_ENAME, V_SAL from emp where empno=I_EMPNO;
--打印结果
DBMS_OUTPUT.PUT_LINE('姓名:' || V_ENAME || ',薪水:' || V_SAL);
END print_emp_info;
-- 调用
BEGIN
print_emp_info(7839);
END;
示例:返回指定员工号的员工的薪水
create or replace procedure out_emp_sal (I_EMPNO in EMP.EMPNO%type,EMPSAL out EMP.SAL%type) is
--声明变量接受查询结果
V_ENAME EMP.ENAME%TYPE;
begin
select ename ,sal into V_ENAME, EMPSAL from emp where empno=I_EMPNO;
--打印结果
DBMS_OUTPUT.PUT_LINE('姓名:' || V_ENAME || ',薪水:' || EMPSAL);
END out_emp_sal;
-- 调用
declare
V_SAL EMP.SAL%TYPE;
BEGIN
out_emp_sal(7839,V_SAL);
DBMS_OUTPUT.PUT_LINE(V_SAL);
END;
注意:调用的时候,参数要与定义的参数的顺序和类型一致.
create or replace function 函数名(name in type,name in type,...) return 数据类型 is
var 数据类型;
begin
return var;
end 函数名;
使用存储函数来查询指定员工的年薪
create or replace function empincome(eno in emp.empno%type) return
number is
psal emp.sal%type;
pcomm emp.comm%type;
begin
select t.sal into psal from emp t where t.empno = eno;
return psal * 12 + nvl(pcomm, 0);
end;
数据库触发器是一个与表关联的、存储的 pl/sql 程序。每当一个特定的数据操作语句(insert、delete、update)在指定的表上发出时,oracle自动执行触发器中的语句序列。
触发器可用于 :
在指定语句操作之前或者之后执行一次,不管这条语句影响了多少行的数据。
触发语句作用的每一条记录,在行级触发器中使用old和new伪记录变量,识别值的状态。
create [or replace] trigger 触发器名称
{before|after}
{delete|insert|update[列名]}
on 表名
[for each row | when(条件)]
begin
plsql语句序列
end 触发器名称;
示例:插入员工成功之后打印一句话 ‘新增员工成功’
create or replace trigger testTrigger
after insert on person
declare
-- local variables here
begin
dbms_output.put_line('一个员工被插入');
end testTrigger;
-- 执行insert语句
INSERT INTO "MES"."EMP" ("EMPNO", "ENAME", "JOB", "MGR", "HIREDATE", "SAL", "COMM", "DEPTNO") VALUES ('7777', 'MILLER', 'CLERK', '7782', TO_DATE('1982-01-23 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), '1300', NULL, '10');
触发语句 | :old | :new |
---|---|---|
Insert | 所有字段都是空(null) | 将要插入的数据 |
update | 更新以前该行的值 | 更新后的值 |
delete | 删除以前该行的值 | 所有字段都是空(null) |
示例:判断员工涨工资之后的工资的值一定要大于涨工资之前的工资
create or replace trigger addsal4p
before update of sal on emp for each row
begin
if :old.sal >= :new.sal then raise_application_error(-20002, '涨前的工资不能大于涨后的工资');
end if;
end;
-- 调用
update emp t set t.sal = t.sal - 1;