本文共 15299 字,大约阅读时间需要 50 分钟。
pl/sql(procedural language/sql)是Oracle在标准的sql语言上的扩展,pl/sql不仅允许嵌入Sql语言,还可以定义变量和常量,允许使用条件语句和循环语句,允许使用例外处理各种错误,这样使得它的功能变得更加强大。
pl/sql是面向过程的语言。
1、提高应用程序的运行性能。
2、模块化的设计思想。 3、减少网络传输量。 4、提高安全性。单行注释–
sql>select * from emp where empno=7788; –取得员工信息多行注释
/………………../
(1)当定义变量时,建议用v_作为前缀 v_sal。
(2)当定义常量时,建议用c_作为前缀 c_rate。
(3)当定义游标时,建议用_cursor作为后缀emp_cursor。
(4)当定义例外时,建议用e_作为前缀 e_error。
块(block)是pl/sql的基本程序单元,编写pl/sql程序实际上 就是编写pl/sql块。要完成相对简单的应用功能,可能只需要编写一个pl/sql块;但是如果想要实现复杂的功能,可能需要在一个pl/sql块中嵌套其他的pl/sql块。
pl/sql块由三个部分构成:定义部分、执行部分、例外处理部分。
declear:定义部分是从declare开始的,这部分是可选的。定义常量、变量、游标例外、复杂数据类型。
begin:执行部分是从begin开始的,这部分是必须的。
exception:例外处理部分是exception开始的,该部分可选的。
PLSQL中的命令窗口
SQL> set serveroutput on ;--打开输出SQL> declare --定义部分 ,如果没有定义,declare可以省略。 可选 ,定义常量、变量、游标、例外、复杂数据类型 2 begin --程序 3 dbms_output.put_line('66666666'); 4 end; 5 /66666666PL/SQL procedure successfully completedSQL> /--表示执行上一个PL/SQL块。66666666PL/SQL procedure successfully completed
declare 说明部分(变量说明\游标声明\例外说明)begin 语句序列(DML语句)exception 例外处理语句end ;/
基本变量类型:
char 、 varchar2、date、number、boolean、long
var1 char(20);married boolean := false ;psal number(7,2);
SQL> set serveroutput on ;SQL> SQL> declare 2 -- 定义基本变量类型 3 v_name varchar2(20);--varchar2 字符串类型 4 v_num number(7,2);--number 数字类型 5 v_date date ;--date 日期类型 6 7 begin 8 9 v_name :='小工匠'; 10 dbms_output.put_line('name:'||v_name); 11 12 v_num :=999; 13 dbms_output.put_line('num:'||v_num); 14 15 v_date :=sysdate; 16 dbms_output.put_line('数据库时间:'||v_date); 17 18 dbms_output.put_line('明天的时间:'||(v_date+1)); 19 end ; 20 /name:小工匠num:999数据库时间:22-JUN-16明天的时间:23-JUN-16PL/SQL procedure successfully completedSQL>
使用%TYPE类型的变量
v_name emp.ename%type ;
打印7369员工的姓名和薪水信息
SQL> set serveroutput on ;SQL> declare 2 --打印 7369 员工的姓名和薪水信息 3 /* 可以使用基本类型来定义变量的类型,推荐使用引用型变量来定义变量类型*/ 4 --vname varchar2(20); 5 --v_sal number; 6 7 --定义引用变量 8 v_name emp.ename%type ; 9 v_sal emp.sal%type; 10 begin 11 --业务操作,变量赋值 (两种赋值方式 1. v_sal := 20 第二种 into的方式 ) 12 select ename, sal into v_name, v_sal from emp e where e.empno = 7369; 13 --输出信息 14 dbms_output.put_line(v_name||'的薪水是'||v_sal); 15 end; 16 /SMITH的薪水是800PL/SQL procedure successfully completedSQL>
%rowtype 记录型变量 代表表中的一行,而一行中有很多列。
emp_rec emp%rowtype
手工赋值emp_rec.ename := 'ADMIN' 或者 into 赋值之后, 使用 emp_rec.enmae获取
SQL> set serveroutput on ;SQL> declare 2 --打印 7369 员工的姓名和薪水信息 3 4 --定义记录型变量 ,得到 7369一行的所有列的信息 5 v_emp_rec emp%rowtype ; 6 begin 7 --业务sql 8 select * into v_emp_rec from emp a where a.empno=7369; 9 --获取姓名和薪水,并打印 10 dbms_output.put_line(v_emp_rec.ename||'的薪水是'||v_emp_rec.sal); 11 end ; 12 /SMITH的薪水是800PL/SQL procedure successfully completedSQL>
if 条件 then 语句1;语句2;end if;
if 条件 then 语句1;else 语句2;end if;
elsif没有e 且是连在一起的
if 条件 then 语句;elsif 条件 then 语句;else 语句;end if;
/*判断用户从键盘输入的数字1、如何使用if语句2、接收一个键盘输入(从键盘上获取的都是字符串)*/set serveroutput on ;--接收一个键盘输入--num :地址值,含义是:在该地址上保存了输入的值 ,获取地址值上对应的值,需要使用 &accept num prompt'请输入一个数字';declare --定义变量保存用户从键盘输入的数字 这里并没有对异常进行处理,输入字符串,会抛异常 pnum number := #begin --执行if 语句进行条件判断 提示信息 不能使用双引号,必须使用单引号,否则报错 if pnum = 0 then dbms_output.put_line('输入的数字是'||pnum); elsif pnum = 1 then dbms_output.put_line('输入的数字是'||pnum); elsif pnum = 2 then dbms_output.put_line('输入的数字是'||pnum); else dbms_output.put_line('其他数字'||pnum); end if;end;/
while 条件 loop .....end loop;
当条件满足时,执行循环体,不满足时,退出循环体。
set serveroutput on ;declare --定义循环变量 初始值为1 记得加上变量的类型 否则报错 v_num number :=1 ;begin while v_num <=5 loop --打印 v_num dbms_output.put_line(v_num); --变量+1 不能使用v_num++的形式,oracle不支持这种写法 v_num :=v_num+1 ; end loop;end;/
loopexit [when 条件];.....end loop;
条件成立时,退出循环体,不成立时执行循环体。
set serveroutput on ;declare --定义循环变量 v_num number :=1 ;begin loop exit when v_num>10 ; --记得加 标点符号 dbms_output.put_line(v_num); v_num := v_num+1; end loop ; end;/
推荐使用第二种,因为在控制游标的时候比较方便。
for i in 1 .. 10 loop 语句;end loop;
set serveroutput on ;declare v_num number :=1 ;begin for i in 1 .. 5 loop -- .. 前后可以有空格 也可以没有 dbms_output.put_line(v_num); v_num := v_num+1; end loop; end;/
游标(游标):一个结果集
不能把一个集合赋值给一个基本型变量,否则会抛出 too many rows的异常。可带参数 ,可不带参数。
cursor 游标名 [(参数名 数据类型[,参数名,数据类型]...)]is select 语句;
比如
cursor c1 is select enama from emp;
open c1;(打开游标,执行查询 即执行游标的查询语句)
fetch c1 into v_name;( 取一行到变量中)
最开始时,游标指向集合的第一条记录,记录返回后,指针移动到下一条记录。
close c1 ;(关闭游标释放资源)
使用游标查询员工姓名和工资,并打印。
推荐写法
.... Loop Fetch 游标名 InTo 临时记录或属性类型变量; Exit When 游标名%NotFound; End Loop; .....
/*使用游标查询员工姓名和工资,并打印*/set serveroutput on ;declare--定义 游标 推荐将游标和游标对应的变量写在一块,比较好维护cursor cemp is select e.ename , e.sal from emp e ;--定义游标对应的变量 这里使用引用型变量v_name emp.ename%type ;v_sal emp.sal%type;begin --打开游标 open cemp ; --循环获取游标中的值 loop --取一条数据 fetch cemp into v_name,v_sal ; -- into 后变量的顺序一定要和定义游标时select的字段对应 exit when cemp%notfound ; --打印 dbms_output.put_line(v_name||'的工资是'||v_sal); end loop; --关闭游标 if cemp%isopen then close cemp; dbms_output.put_line('Closing...'); end if;end;/
或者
循环游标隐式打开游标,自动滚动获取一条记录,并自动创建临时记录类型变量存储记录。处理完后自动关闭游标。
For 变量名 In 游标名 Loop 数据处理语句; End Loop;
/*使用游标查询员工姓名和工资,并打印*/set serveroutput on ;declare--定义 游标 推荐将游标和游标对应的变量写在一块,比较好维护cursor cemp is select e.ename , e.sal from emp e ;--定义游标对应的变量 这里使用引用型变量v_name emp.ename%type ;v_sal emp.sal%type;begin --循环获取游标中的值 for c in cemp LOOP select c.ename , c.sal into v_name,v_sal from dual; dbms_output.put_line(v_name||'的工资是'||v_sal); end loop;end;/
/*给员工涨工资。总裁涨1000,经理涨800,普通员工涨400;*/declare --定义游标 cursor c_emp is select empno,empjob from emp; --定义游标对应的变量 p_empno emp.empno%type; p_empjob emp.empjob%type;begin --打开游标 open c_emp; --取出一个员工 loop fetch c_emp into p_empno,p_empjob; exit when c_emp%notfound; --判断职位 if p_empjob = 'PRESIDENT' then update emp set sal=sal+1000 where empno=p_empno; elsif p_empjob = 'MANAGER' then update emp set sal= sal+800 where empno = p_empno; else update emp set sal=sal+400 where empno=p_empno; end if; end loop; --关闭游标 if c_emp%isopen then close c_emp; end if ; --提交事务,oracle默认的隔离级别是read committed ,不同连接只能读取提交之后的; commit; dbms_output.put_line('涨工资完成');end;/
游标的4个属性
Oracle默认的一个会话最多可以打开300个游标.
可以通过使用 show parameter cursors; (表示模糊查询 %cursors%)
使用DBA权限的用户
alter system set open_cursors=400 scope = both;
其中scope的取值:both,memory,spfile
注意 定义(带参数) 和打开游标(传递参数)时的区别。其余的和无参的游标一样。
set serveroutput on ;declare--定义带参数的游标cursor cemp (dno number) is select ename from emp where deptno =dno;--定义游标中对应的变量v_name emp.ename%type;begin --打开游标 传入对应的入参 open cemp(10); --loop循环 遍历游标 loop fetch cemp into v_name; exit when cemp%notfound; dbms_output.put_line(v_name); end loop; close cemp;end;/
上面介绍的是显式游标,下面说下隐式游标
DML操作和单行SELECT语句会使用隐式游标,它们是:
隐式游标的名字为SQL,这是由ORACLE 系统定义的。 对于隐式游标的操作,如定义、打开、取值及关闭操作,都由ORACLE 系统自动地完成,无需用户进行处理。用户只能通过隐式游标的相关属性,来完成相应的操作。
格式调用为: SQL%
隐式游标可以使用名字SQL来访问,但要注意,通过SQL游标名总是只能访问前一个DML操作或单行SELECT操作的游标属性。所以通常在刚刚执行完操作之后,立即使用SQL游标名来访问属性。
游标的属性有四种,如下所示。
隐式游标的属性 返回值类型 意 义SQL%ROWCOUNT 整型 代表DML语句成功执行的数据行数SQL%FOUND 布尔型 值为TRUE代表插入、删除、更新或单行查询操作成功SQL%NOTFOUND 布尔型 与SQL%FOUND属性返回值相反SQL%ISOPEN 布尔型 DML执行过程中为真,结束后为假
DECLARE v_rows NUMBER; BEGIN --更新数据 UPDATE employees SET salary = 30000 WHERE department_id = 90 AND job_id = 'AD_VP'; --获取默认游标的属性值 v_rows := SQL%ROWCOUNT; DBMS_OUTPUT.PUT_LINE('更新了'||v_rows||'个雇员的工资'); --删除指定雇员;如果部门中没有雇员,则删除部门 DELETE FROM employees WHERE department_id=v_deptno; IF SQL%NOTFOUND THEN DELETE FROM departments WHERE department_id=v_deptno; END IF; END;
在oracle中错误被叫做例外:分为系统例外和自定义例外。
系统例外 比如:
- No_data_found 没有找到数据 - Too_many_rows select..into语句匹配多个行 - Zero_Divide 被零除 - Value_error 算数或转换错误,算术错误比如说负数开平方 - Timeout_on_resource 在等待资源时发生超时,常见于分部署数据库。/*系统例外 no_data_found*/ set serveroutput on ;--查询empno为222的姓名declare --定义引用型变量v_name emp.ename%type;begin --业务sql select ename into v_name from emp where empno=222; exception when no_data_found then dbms_output.put_line('no data found'); when others then dbms_output.put_line('others exception');end;/
/*系统例外 too_many_rows */set serveroutput on ;--查询 10号部门的员工declare --定义引用型变量v_name emp.ename%type;begin --业务sql select ename into v_name from emp where deptno=10; exception when too_many_rows then dbms_output.put_line('too_many_rows'); when others then dbms_output.put_line('others exception');end;/
/*系统例外 zero_divide */set serveroutput on ;declare --定义引用型变量v_num number;begin --业务sql v_num := 1/0; exception when zero_divide then dbms_output.put_line('zero_divide');dbms_output.put_line('0不能做除数'); when others then dbms_output.put_line('others exception');end;/
/*系统例外 value_error */set serveroutput on ;declare --定义引用型变量v_num number;begin --业务sql v_num := 'xiaogongjiang'; exception when value_error then dbms_output.put_line('value_error'); when others then dbms_output.put_line('others exception');end;/
--自定义例外:(没有查找到的例外no_emp_found)declare cursor c_emp is select ename from emp where deptno=50; p_ename emp.ename%type; --定义一个例外 no_emp_found exception;begin open c_emp; --获取一条记录 fetch c_emp into p_ename; --如果没有查到则抛出自定义例外 if c_emp%notfound then raise no_emp_found; end if; --此处,当前一句抛出例外执行完exception后, --oracle会自动启动一个pmon(process monitor)的一个进程 --将pl/sql程序中未关闭的资源释放 --所以 close c_emp; 还是会执行的 close c_emp; --捕获例外 exception when no_emp_found then dbms_output.put_line('没有该部门下的员工'); when others then dbms_output.put_line('其他例外');end;/
可以在pl/sql工具中 新建测试窗口,调测过程和调测存过的方式一样,可以一步一步的跟踪sql执行的过程。
瀑布模型
拿到一个需求后,不找着急写程序,先分析明白了
分析过程:
每年入职的员工人数 1.所有的年份集合–>定义cursor保存 2.每个员工的入职年份–>定义v_hiredate保存 3.每年入住的人数总和–>定义v_count_XX保存先把大框架打起来
declare ---定义变量balabalabegin ---业务逻辑end;/
然后再一步一步的填充业务逻辑。
set serveroutput on ;declare --入职年份的游标cursor hiredate_cursor is select to_char(hiredate,'yyyy') from emp;--定义入职年份v_hiredate varchar2(4);--定义每个年份入职人员总数v_count_80 number :=0;v_count_81 number :=0;v_count_82 number :=0;v_count_87 number :=0;begin --打开游标 open hiredate_cursor ; --loop循环遍历游标 loop --取值 fetch hiredate_cursor into v_hiredate ; exit when hiredate_cursor%notfound ; if v_hiredate = '1980' then v_count_80 :=v_count_80+1 ; elsif v_hiredate = '1981' then v_count_81 :=v_count_81+1 ; elsif v_hiredate ='1982' then v_count_82 :=v_count_82+1; elsif v_hiredate ='1987' then v_count_87 :=v_count_87+1; end if ; end loop; --关闭游标 close hiredate_cursor; dbms_output.put_line('总共入职人数:'||(v_count_80+v_count_81+v_count_82+v_count_87));--加括号 dbms_output.put_line('80入职的:'||v_count_80); dbms_output.put_line('81入职的:'||v_count_81); dbms_output.put_line('82入职的:'||v_count_82); dbms_output.put_line('87入职的:'||v_count_87);end;/
案例2:涨工资问题,从最低工资的员工开始涨起,没人涨10%,工资总额不能超过50000,返回涨工资的人数和涨后的工资总额.
/*分析:1、用到的sql语句: select empno,sal from emp order by sal; select sum(sal) into totalsal from emp;2、需要声明的变量: 工资总额:totalsal 涨工资人数:count3、循环推出的条件: 工资总额>5W or 全部员工都涨完工资*/declare cursor cemp is select empno,sal from emp order by sal; p_no emp.empno%type; p_sal emp.sal%type; countemp number:=0;--涨工资人数 totalsal emp.sal%type;begin --获取初始工资总额 select sum(sal) into totalsal from emp; open cemp; --判断当前工资总额是否大于5W if totalsal<50000 then loop fetch cemp into p_no,p_sal; exit when cemp%notfound; --获取当前员工涨工资后的工资总额 --如果工资总额超过5W直接退出循环 exit when (totalsal+p_sal*0.1)>50000; update emp set sal=sal*1.1 where empno=p_no; --涨工资人数加1 countemp:=countemp+1; end loop; end if; close cemp; commit; dbms_output.put_line('共有'countemp'人涨工资,工资总额为:'totalsal);end;/
declare --获取所有部门 cursor c_dept is select deptno from dept; --各部门编号 p_dno dept.deptno%type; --各部门总金额 p_totalsal number; --各工资分段人数: num1 number; num2 number; num3 number; --定义一个游标存放该部门下所有员工(带参数) cursor c_emp(dno number) is select sal from emp where deptno = dno; --员工的薪水 p_sal number;begin --打开部门游标 open c_dept; loop --部门循环 fetch c_dept into p_dno; exit when c_dept%notfound; --初始化变量: p_totalsal := 0; num1 := 0; num2 := 0; num3 := 0; -- --获取本部门下所有员工,打开员工游标 open c_emp(p_dno); loop --员工循环 fetch c_emp into p_sal; exit when c_emp%notfound; if p_sal < 3000 then num1 := num1 + 1; elsif p_sal >= 3000 and p_sal <= 6000 then num2 := num2 + 1; elsif p_sal > 6000 then num3 := num3 + 1; end if; --获取总金额 p_totalsal := p_totalsal + p_sal; end loop; close c_emp; --保存统计结果到sal_msg insert into sal_msg values (p_dno, num1, num2, num3, p_totalsal); end loop; close c_dept; commit; dbms_output.put_line('统计完成');end;/