一、PL/SQL 游标
PL/SQL 编程语言 Procedure Language/SQL
是ORACLE对SQL过程化的扩展,使SQL语言具有过程处理能力
面向过程语言:C PL/SQL
SCALA语言
HADOOP JAVA
SPARK SCALA
为什么要学习PL/SQL?
PL/SQL是对SQL语言的扩展创建的
PL/SQL操作数据库效率最高
学习内容
存储过程
存储函数
触发器
包
包体
开发模式:Spring mvc +存储过程
简单的PL/SQL
DECLARE
--说明部分
BEGIN
--程序
dbms_output.put_line(\'Hello world\');
END;
开始显示PL/SQL结果
SET SERVEROUTPUT ON;
PL/SQL中的变量与常量
数据类型:
char,
varchar2,
date,
number,
boolean,
long
var1 char(15)
married boolean:=true;
psal number(7,2)
PL/SQL中的:=为赋值,=为==
引用型变量
my_name emp.ename%type;
引用emp表上的ename列的类型为my_name变量的类型
引用型变量,
查询并打印7839的姓名和薪水:
DECLARE
--pname varchar2(20);
--psal number;
pname emp.ename%type;
psal emp.sal%type;
begin
--得到7839的姓名和薪水
select ename,sal into pname,psal from emp where empno=7839;
--PL/SQL赋值除了使用:= 还可以使用INTO,INTO后面变量的顺序需要和前面列的顺序一致
--打印
dbms_output.put_line(pname||\'的薪水是\'||psal);
end;
记录型变量
emp_rec emp%rowtype
引用表中的一行,可以把记录型变量理解为一个数组
记录型变量,
查询并打印7839的姓名和薪水:
DECLARE
--定义记录型变量:代表一行
emp_rec emp%rowtype
begin
select * into emp_rec from emp where empno=7839;
dbms_output.put_line(emp_rec.ename||\'的薪水是\'||emp_rec.sal);
end;
常量:Constant
PL/SQL中的条件判断
IF语句 IF 条件 THEN 语句1; 语句2; END IF; IF 条件 THEN 语句序列1; ELSE 语句序列2; END IF; IF 条件 THEN 语句; ELSIF 语句 THEN 语句; ELSE 语句; END IF; 例子:判断用户从键盘输入的数字 --接收键盘输入 --变量num:是一个地址值,在改地址上保存了输入的值 accept num prompt \'请输入一个数字\' DECLARE --定义变量保存输入的数字,&地址符 pnum number :=&num BEGIN IF pnum =0 then dbms_output.put_line(\'您输入的数字是0\'); ELSIF pnum =1 then dbms_output.put_line(\'您输入的数字是1\'); ELSIF pnum =2 then dbms_output.put_line(\'您输入的数字是2\'); END IF; END;
PL/SQL循环
循环 一、 WHILE total<=2500 LOOP ... total :=total +salary; END LOOP; 二、 LOOP EXIT [WHEN 条件]; ... end loop; 三、 FOR I IN 1..3 LOOP ... END LOOP; 打印1~10 declare --定义变量 pnum number :=1; begin loop --退出条件 exit when pnum>10; --打印 dbms_output.put_line(pnum); --加一 pnum :=pnum+1; end loop; end;
光标/游标(相当于JAVA中的ResultSet集合)
语法:
CURSOR 游标名 [(参数名 数据类型,参数名 数据类型...)] IS SELECT 语句
例如:CURSOR C1 IS SELECT ENAME FROM EMP;
游标的使用步骤:
打开游标:
OPEN C1;(打开游标前执行查询)
取一行游标的值:
FETCH C1 INTO PJOB;(取一行到变量中,指针移动到下一个位置)
关闭游标:
CLOSE C1;(关闭游标释放资源)
游标的结束方式:EXIT WHEN C1%NOTFOUND
注意:
上面的PJOB 必须与emp表中的job列类型一致;
定义:PJBO EMP.EMPJOB%TYPE;
游标的属性:
%isopen 是否打开 true/false
%rowcount 影响的行数(比如游标里面有100条记录,目前已经取值10个,直就为10)
%found true/false
%notfound true/false
例子:查询并打印员工的姓名和薪水
DECLARE
--定义一个游标
CURSOR cemp is select ename,sal from emp;
--定义好一个游标后紧接着定义变量
pename emp.ename%type;
psal emp.sal%type;
BEGIN
--打开游标
OPEN cemp;
loop
--取当前记录
fetch cemp into pename,psal;
exit when cemp%notfound; --没有取到记录
dbms_output.put_line(pename||\'的薪水是\'||psal);
end loop;
--关闭
close cemp;
END;
PL/SQL 给员工涨工资
DECLARE
--定义游标
CURSOR cemp is select empno,job form emp;
pempno emp.empno%type;
pjob emp.job%type;
begin
--打开游标
open cemp;
loop
--取一个员工
fetch cemp into pempno,pjob;
exit when cemp%notfound;
--判断职位
if pjob =\'PERSIDENT\' THEN update emp set sal=sal+1000 where empno=pempno;
elsif pjob =\'MANAGER\' THEN update emp set sal=sal+800 where empno=pempno;
else update set sal=sal+400 where empno=pempno;
end if;
end loop;
--关闭游标
close cemp;
--提交 事务的ACID
commit;
dbms_output.put_line(\'完成\');
end;
带参数的游标
--查询某个部门的员工姓名
DECLARE
cursor cemp(dno number) is select ename from emp where deptno=dno;--dno形式参数
pename emp.ename%type;
begin
open cemp(10);--开启游标时需要参数实际参数
loop
fetch cemp into pename;
exit when cemp%notfound;
dbms_output.put_line(pename);
end loop;
close cemp;
end;
PL/SQL异常/例外
异常是程序设计语言提供的一种功能,用来增强程序的健壮性和容错性
no_data_found 没有找到数据
too_many_rows (select ..into 匹配多个行)
zero_divide 被零除
value_error 算数转换错误(类型转换等)
timeout_resource 在等待资源时发生超时(分布式数据库容易发生)
分布式数据库:物理上不是一个整体,但逻辑上是一个整体
--被零除
DECLARE
pnum number;
begin
pnum :=1/0;
exception --相当于javaz中的catch 在PL/SQL中必须catch住所有的例外
when zero_divide then dbms_output.put_line(\'0不能做分母\');
dbms_output.put_line(\'0不能做分母111\');
when value_error then dbms_output.put_line(\'算术或者转换错误\');
when other then dbms_output.put_line(\'其他例外\');
end;
PL/SQL:自定义例外
DECLARE
--定义例外
No_Data exception;
--抛出例外
raise No_Data
例子:查询50号部门的员工(表中不存在50号部门)
declare
cursor cemp is select ename from emp where deptno=50;
pname emp.ename%type;
--自定义例外
no_emp_found exception;
begin
open cemp;
--取第一条记录
fetch cemp into pname;
if cemp%notfound then
--抛出例外
raise no_emp_found;
end if;
--进程:pmon (process monitor 监事程序运行)
close cemp;
exception
when no_emp_found then dbms_output.put_line(\'没有找到员工\');
when other then dbms_output.put_line(\'其他例外\');
end;
--进程
读、写
PMON进程
当用户进程失败时执行进程恢复
1、清除数据库缓冲区高速缓存
2、释放该用户进程使用的资源
监事会话:查看是否发生空闲会话超时
在监听程序中动态注册数据库服务
练习例子
例子:
统计每年入职的员工个数?
需要什么样的SQL语句?
需要员工入职年份
select to_char(hiredate,\'yyyy\') from emp;
集合==》游标==》循环==》退出:notfound
需要什么变量?
初始值/值最终如何得到
每年入职年数
count80 number :=0;
DECLARE
--定义一个游标
cursor cemp is select to_char(hiredate,\'yyyy\') from emp;
phiredate varchar2(4);
--每年入职人数
count80 number :=0;
count81 number :=0;
count82 number :=0;
count87 number :=0;
begin
open cemp;
loop
fetch cemp into phiredate;
exit when cemp%notfound;
--判断年份是哪一年
if phiredate =\'1980\' then count80=count80+1;
elsif phiredate =\'1981\' then count81=count81+1;
elsif phiredate =\'1982\' then count82=count82+1;
else count87=count87+1;
end loop;
close cemp;
dbms_output.put_line(\'Total:\'||(count80+count81+count82+count87));
dbms_output.put_line(\'1980\'||count80);
dbms_output.put_line(\'1981\'||count81);
dbms_output.put_line(\'1982\'||count82);
dbms_output.put_line(\'1987\'||count87);
end;
问题: 给员工涨工资,从最低工资调起每人涨10%,但工资总额不能超过5万元,请计算涨工资的人数和涨工资后的工资总额,并输出涨工资人数和工资总额 给哪些员工涨工资 select empno,sal from emp order by sal; ==>游标==》循环==》退出 1、总额》5万 2、NOTFOUND 变量 初始值 最终结果如何得到 涨工资的人数:countEmp number :=0; 涨后的工资总额:salTotal number; 1、select sum(sal) into salTotal from emp; 2、涨后=涨前+sal*01; 编程原则: 尽量不操作数据库,第二种方式好 DECLARE cursor cemp is select empno,sal from emp order by sal; pempno emp.empno%type; psal emp.sal%type; --涨工资的人数 countEmp number :=0; --涨后工资的总额: salTotal number; begin --得到工资总额的初始值 select sum(sal) into salTotal from emp; open cemp; loop --总额大于5万退出 exit when salTotal>50000; fetch cemp into pempno,psal; --没有找到 exit when cemp%notfound; exit when salTotal+psal*0.1)>50000; --涨工资 update emp set sal=sal+sal*0.1 where empno=pempno; --涨后工资的总额:涨后=涨前+sal*0.1 salTotal :=salTotal+psal*0.1; --人数+1 countEmp =countEmp+1; end loop; close cemp; dbms_output.put_line(\'人数\') end; 用PL/SQL语言编写一程序,实现按部门分段(6000以上、(6000、3000)、3000以下) 统计各工资段的职工人数、以及各部门的工资总额(工资总额中不包括奖金) create table msg ( deptno number, count1 number, count2 number, count3 number, total number ) 1、存在哪些部门 部门:select deptno from dept; --游标 部门中员工的薪水: select sal from emp where deptno=??; --带参数的游标 2、变量 初始值 最终结果如何得到 count1 number,count2 number,count3 number; 部门的工资总额 salTotal number :=0; (1)select sum(sal) into salTotal from emp where deptno (2)累加 declare --部门 cursor cdept is select deptno from dept; pdeptno dept.deptno%type; --部门中员工的薪水 cursor cemp(dno number) is select sal from emp where deptno=dno; psal emp.sal%type; --每个段的人数 count1 number;count2 number;count3 number; --部门的工资总额 salTotal :=0; begin open cdept; loop --取一个部门 fetch cdept into pdeptno; exit when cdept%notfound; --初始化 count1 :=0; count2 :=0; count3 :=0; --得到部门的工资总额 select sum(sal) into salTotal from emp where deptno=pdeptno; --取部门中的员工薪水 open cemp(pdeptno); loop --取一个员工的薪水 fetch cemp into psal; exit when cemp%notfound; --判断 ifpsal<3000 then count1 :=count1 +1; elsif psal>=300 and psal<6000 then count2 :=count2+1; else count3 :=count3+1; end if; end loop; close cemp; --保存结果 insert into msg values(pdeptno,count1,count2,count3,nvl(salTotal,0)); end loop; close cdept; commit; end;
存储过程、存储函数
定义:指存储在数据库中供所有用户调用的子程序
区别:存储函数可以通过return返回一个函数的值,存储过程没有返回值
创建存储过程
CREATE [OR REPLACE] PROCEDURE 过程名(参数列表)
AS(或IS)
PL/SQL子程序
打印Hello World
CREATE OR REPLACE PROCEDURE sayHelloWorld
as --相当于declare
--说明部分
begin
dbms_output.put_line(\'Hello World\');
end;
调用存储过程:
execute
1、exec sayHelloWorld()
2、在另外的PL/SQL程序中调用
begin
sayHelloWorld();
sayHelloWorld();
end;
--给指定员工涨100元工资,并打印涨前和涨后的薪水
--带参数的存储过程
create or replace procedure raiseSalary(eno in number)
is
--定义变量
psal emp.sal%type;
begin
--得到涨前的薪水
select sal into psal from emp where empno=eno;
--涨100
update emp set sal=sal+100 where empno=eno;
--要不要commit?
--一般不在存储过程和存储函数中提交
--一般谁来调用,谁来提交 和 回滚
dbms_output.put_line(\'涨前的薪水\'||psal ||\' 涨后\'||(psal+100))
end raiseSalary;
使用in out 表示参数是输入或输出参数
存储函数
为一个命名的存储程序,可以带参数,并返回一个计算值。函数和过程结构类似,但是
必须要有一个RETURN子句,用于返回函数值
CREATE OR REPLACE FUNCTION 函数名(参数列表)
RETURN 函数值类型
AS
PL/SQL子程序
--查询某个员工的年收入
CREATE OR REPLACE FUNCTION queryEmpIncome(eno in number){
return number
is
--定义变量保存月薪和奖金
psal number emp.sal%type;
pcomm emp.comm%type;
begin
--得到月薪和奖金
select sal,comm into psal,pcomm from emp where empno=eno;
--返回年收入
return psal*12+nvl(pcomm,0);
end queryEmpIncome;
}
过程和函数都可以通过OUT指定一个或者多个输出参数,可以利用OUT参数,在过程和函数中实现返回多个值
--如果只有一个返回值,用存储函数;否者,就用存储过程
--查询某个员工的姓名、薪水、职位
create or replace procedure queryEmpInformation(enp in number,
pename out varchar2,
psal out varchar2,
pjob out varchar2)
is
begin
select ename,sal,job into pename,psal,pjob from emp where empno=eno;
end queryEmpInformation;
=>也是赋值的语法
有了OUT后存储函数就多余了,因为存储过程可以有返回值,可以没有返回值
为什么ORACLE依然保留存储函数?
历史问题
ORACLE最早的版本中存储过程与存储函数是有区别的,为了做到新版本需要向下兼容,所以要保留存储函数
--问题 查询某个员工的所有信息
--out参数太多了,怎么解决参数过多
--问题 查询某个部门中的所有员工信息 返回的应该是一个集合
在Out参数中使用游标
申明包结构 CREATE OR REPLACE PACKAGE MYPACKAGE AS type empcursor is ref cursor procedure queryEmpList(dno in number,empList out empcursor) END MYPACKAGE; 创建包体 CREATE OR REPLACE PACKAGE BODY MYPACKAGE AS procedure queryEmpList (dno in number,empList out empcursor) AS BEGIN open empList for select * from emp where deptno=dnp; END queryEmpList; END MYPACKAGE; ------- --查询某个部门中的所有员工信息 create or replace package mypackage is type empcursor is ref cursor; procedure queryEmpList(dno in number,empList out empcursor); end mypackage;
create or replace package body mypackage is procedure queryEmpList(dno in number,empList out empcursor) as begin open empList for select * from emp where deptno=dno; end queryEmpList; end mypackage;
desc mypackage;//查看包结构
触发器
触发器 数据库触发器是一个与表相关联的、存储的PL/SQL程序。
每当一个特定的数据操作语句在指定的表上发出时,ORcale自动地执行触发器中定义的语句序列 第一个触发器 每当成功插入新员工后自动插入一句话:成功插入新员工 create trigger firsttriger after insert on emp declare begin dbms_output.put_line(\'成功插入新员工\') end; 触发器可用于 1、数据确认 2、实施复杂的安全性检查 3、做审计,跟踪表上所做的数据操作等(审计:记录日志,跟踪表上的数据操作等) 基于值的审计 强制审计 标准审计 细粒度审计 管理员审计 4、数据的备份和同步 CREATE OR REPLACE TRIGGER {BEFORE|AFTER} {DELETE|INSERT|UPDATE|OF 列名} ON 表名 [FOR EACH ROW [WHEN 条件]] PL/SQL块 触发器类型(针对的表): 语句级触发器:在指定的操作语句操作之前或之后执行一次,不管这条语句影响了多少行 行级触发器[FOR EACH ROW](针对的行): 触发语句作用的每一条记录都被出发。在行级触发器中使用:old 和:new 伪记录变量,识别值的状态 例子: 禁止在非工作时间插入数据 1、周末 :to_char(sysdate,\'day\') in (\'星期六\',\'星期日\') 2、上班前和下班后:to_number(to_char(sysdate,\'hh24\')) not between 9 and 17 (not between 在什么之外) --语句级触发器 create or replace trigger securityemp before insert on emp begin if to_char(sysdate,\'day\') in (\'星期六\',\'星期日\') or to_number(to_char(sysdate,\'hh24\')) not between 9 and 17 --禁止insert raise_application_error(-20001,\'不能在工作时间插入数据\') end if; end securityemp;
--检查涨工资不能越涨越少(涨后不能少于涨前) --数据确认/行级触发器 create or replace trigger checksalary before update on emp for each row begin -- 如果涨后薪水小于涨前薪水 then 抛出异常 if :new.sal<:old.sal then raise_application_error(\'-2002\',\'涨后薪水不能少于涨前薪水\'); end; end checksalary;
:old 操作这一行之前这一行的值 :new 之后 wm_contact 使用逗号在分组中将同一个分组的内容连接成字符串