一、什么是PL/SQL?
PL/SQL全名Procedure Language/SQL,PL/SQL是Oracle对sql语言的过程化扩展语言,指在SQL命令语言中增加了过程处理语句(逻辑--如分支、循环等),使SQL语言具有过程处理能力。
特点:把SQL语言的数据操纵能力与过程语言的数据处理能力结合起来
二、PL/SQL的结构
DECLARE --声明块:理解为初始化(可选的) --定义变量、常量、类型、游标、异常、复杂数据类型(记录和表)、以及局部的存储过程和函数 BEGIN --执行块:程序体的开始端口(必须有) --要执行的pl/sql语句和sql语句(程序的主要部分) EXCEPTION --异常处理块(可以有) --处理各种运行时的错误 END; --程序体的结束声明
三、PL/SQL的使用
需求1:在控制台打印一行数据
DECLARE BEGIN DBMS_OUTPUT.put_line('hello pl/sql'); -- 说明:dbms_output叫程序包,相当于Java种的类,put_line()相当于Java中的方法,这里是存储过程或者函数 END;
说明1:了解块编程,理解程序包
注意:在黑窗口中执行pl/sql语句,必须加上"/"才能执行整个(pl/sql)过程,并且通过set serveroutput on; --开启屏幕输出开关(设置显示PLSQL程序的执行结果),默认情况下不显示PLSQL程序的执行结果;原因:dbms_output是Oracle中独有的一个输出对象,put_line是上述对象的一个方法,用于输出一个字符串自动换行。
小技巧:F8来执行pl/sql语句;desc dbms_output; 来查看某个程序包的详细的文档说明
需求2:按职工的职称涨工资,总裁涨1000元、经理涨800元、其他人员涨400元
说明:无法使用一条SQL来实现,需要借助其他程序来帮助完成,优先使用pl/sql
需求3:查询并打印员工号是7839员工的姓名和薪水
DECLARE -- 自定义变量(一般形式)和引用类型的变量 pname varchar(20); psalary emp.sal%type; -- 我们称之为引用型变量:表示引用sal字段的类型 BEGIN select ename,sal into pname,psalary from emp where empno=7839; dbms_output.put_line(pname||'的薪水为'||psalary); -- DBMS_OUTPUT.put_line('hello pl/sql'); END;
说明:将查询的数据传递给自定义变量(into--注意位置)、字符串的拼接(||)
注意:自定义变量一定要注意类型,在实际开发中常用的是引用类型的变量(注意书写),不容易出错。
需求4:查询并打印员工号是7839员工的所有信息
-- 需求:在控制台打印所有的数据 DECLARE -- 自定义变量 marry emp%rowtype;-- 记录类型的变量,接受一条数据(所有字段的值),相当于一个容器 BEGIN select * into marry from emp where empno=7839; -- 查询所有的信息保存到容器中 dbms_output.put_line(marry.ename||'的薪水是'||marry.sal); -- 注意取数据的方式 dbms_output.put_line(marry.ename||'的岗位是'||marry.job); -- 注意取数据的方式 END;
说明:理解记录型变量含义,会定义记录型变量(针对某个表中的数据),取数据的方式(通过emp中的字段名来获取数据)
需求5:判断用户从键盘输入的数字,并打印到屏幕上(if else 语句 进行逻辑判断)
分析:既然我们要接收用户从键盘上输入的数字,我们得使用键盘录入,那么程序中必须接受键盘录入的数据,用户需要输入数据时,给用户提示
set serveroutput ON; --语句1,环境配置,可以使用函数dbms_output.put_line()可以输出参数的值 -- 键盘录入时,用户提示 accept my prompt'请输入一个数字:';-- 语句2 -- 说明1:accept表示接受键盘录入的数据、 -- 说明2:mynumin是定义的一个变量(内存中的地址值)来保存数据 -- 说明3:prompt是文字提示 DECLARE -- 接受(键盘录入的数据),并保存到自定义的变量中 mynum number:=&my; -- 说明2:":="是一个赋值号,&是一个地址运算符 -- 表示获取此地址的值(键盘录入的数据) BEGIN if mynum=1 then dbms_output.put_line('今天是星期一'); elsif mynum=2 then dbms_output.put_line('今天是星期二'); elsif mynum=3 then dbms_output.put_line('今天是星期三'); elsif mynum=4 then dbms_output.put_line('今天是星期四'); elsif mynum=5 then dbms_output.put_line('今天是星期五'); elsif mynum=6 then dbms_output.put_line('今天是星期六'); elsif mynum=7 then dbms_output.put_line('今天是星期日'); else dbms_output.put_line('您输入的数字有误,请重新检查'); end if;-- 一定要加分号 END;
掌握:if else的逻辑判断结构
注意:elsif千万不要写成elseif(确实是少了一个e)!!!
补充:if else常用有三种形式(if、if else、if elsif else),但是都要与end if配对。
执行过程1:在Oracle自带的sqlplus中先执行语句1、2然后输入数字,最后再输入结构体,最后输入斜杠才能执行
执行过程2:在pl/sql的Command Window里执行即可
问题描述:在Oracle自带的sqlplus里能运行,但在pl/sql的测试窗口里不能运行。报错:无效SQL语句
set serveroutput on:点击打开链接
需求6:定义一个常量
c_mynum constant number:=100;-- 注意命名规范
说明:循环语句 while、for、loop的用法
语法1: WHILE 条件 LOOP .. . total : = total + salary; END LOOP;
需求7:输出1到10的数字--方式1
SQL> declare 2 step number := 1; --定义一个起始变量 3 begin 4 while step <= 10 --注意条件后面不要写分号 5 loop 6 dbms_output.put_line(step); 7 step := step + 1; -- pl/sql中没有加等的说法 8 end loop; 9 end; 10 /
需求8:同上--方式2
declare num number :=1; --注意赋值号是 := begin loop exit when num>10; --循环结束条件(有分号) dbms_output.put_line(num); num:=num+1; --控制条件语句,注意oracle中没有 ++ 和 -- 这两个运算符 end loop; --结束循环 注意后面要加分号 end;
说明:注意上面两种方式的细节实现,以及特点。方式1是首先判断while是否为ture,才决定执行不执行,而方式2会首先进入loop循环体中,至少要执行一次(依据exit_when的位置)。
需求9:同上--方式3
declare -- (1)定义变量默认是1 v_num NUMBER DEFAULT 1; BEGIN -- 表明:如果V_name是在[1,10]的范围则继续循环,否则退出 -- 注意:for in 的这种循环形式 FOR v_num IN 1..10 --传递变量,也是没有分号 LOOP dbms_output.put_line(v_num); -- 注意:v_num不能作为赋值目标(具体原因未知) -- v_num:=v_num+1; END LOOP; end;
需求10:使用游标方式输出emp表中的员工编号和姓名打印到屏幕上
概念:Oracle中的游标其实就是类似JDBC中的resultSet,就是一个指针的概念。既然是类似与resultSet,那么游标仅仅是在查询的时候有效的。cursor理解为多条记录所构成的集合(是一个结构体),游标可以存储查询返回的多条数据。
--定义游标的语法:cusor 游标名 [ (参数名 数据类型,参数名 数据类型,...)] is select 语句;
使用步骤:
--打开游标: open c1; (打开游标执行查询) --取一行游标的值: fetch c1 into v_job; (取一行数据到变量中) --关闭游标: close c1;(关闭游标释放资源) --游标的结束方式 exit when c1%notfound --数据结束的标志
相关代码(注意逻辑):
declare -- 定义游标(保存数据集) CURSOR all_cusor IS SELECT ename,sal FROM emp; -- 定义两个变量,保存姓名和工资 -- 为了避免数据类型的错误,采用引用类型的变量 v_ename emp.ename%TYPE; v_sal emp.sal%TYPE; begin --(1)打开游标中的光标 OPEN all_cusor; --(2)循环的从游标中获取数据 LOOP --(3)表示取出当前行的记录,赋值给我们定义的变量,并把光标下移一行,光标默认的位置就在第一行 FETCH all_cusor INTO v_ename,v_sal; --(4)啥时候将数据取完,总得有一个结束退出的标志吧!!! EXIT WHEN all_cusor%NOTFOUND; -- 光标没有取到记录的时候,结束循环 %notfound是光标的一个属性 dbms_output.put_line(v_ename||'的薪水是'||v_sal); END LOOP; --(5)数据取出完毕,则关闭光标 CLOSE all_cusor; end;
说明:也可以在外层通过while all_cusor%found来定义结束条件
补充:光标的常用属性有--%isopen(光标是否打开)、%rowcount(影响的行数)、%found、%notfound
需求10:给不同职位的员工涨工资,总裁涨工1000、经理800、其他员工400
思路:查询出各个员工的职位和部门封装到游标中存储,然后定义两个变量接受员工的职位和部门,if else条件根据职位对薪水进行调整
代码:有点复杂
declare -- 定义游标 cursor temp_cursor is select empno,job from emp; -- 定义两个变量存储游标取出来的值 v_empno emp.empno%type; v_job emp.job%type; begin open temp_cursor; --打开光标 loop fetch temp_cursor into v_empno,v_job; --从游标中取出值 赋值给变量 exit when temp_cursor%notfound; --循环结束条件,当游标中没有找到值时结束循环(带分号) if v_job='PRESIDENT' then update emp set sal=sal+1000 where empno=v_empno; --注意这里判断相等用一个等号 = elsif v_job='MANAGER' then update emp set sal=sal+800 where empno=v_empno;-- empno是独一无二的 else update emp set sal=sal+400 where empno=v_empno; end if; --结束if语句 end loop; --结束循环语句 close temp_cursor; --关闭光标 commit; --注意:这里要提交一下事务,不然我们在sqlplus 里面查询的时候 发现数据没改变 --因为:我们的Oracle 数据库的隔离级别是读未提交,我们在软件里面不提交,黑窗口会读不到 dbms_output.put_line('完成'); end;
需求11:查询某个部门的员工姓名,我们可以使用带有参数的游标来做
语法:
CURSOR 光标名 [ (参数名 数据类型[,参数名 数据类型]...)] IS SELECT 语句;
理解上:游标理解为带有行号(索引)的临时表。
代码:
declare -- (1)定义带参数的游标(引用类型的变量) CURSOR temp_cursor(v_cusor ename.deptno%type) IS SELECT ename FROM emp WHERE deptno=v_cusor; -- (2)定义一个变量,接受从光标中查询出来的数据 v_ename emp.ename%TYPE; begin -- (3)打开一个光标--在开启光标的时候,我们就传入具体的参数 比如查10号部门,我们就传入10 OPEN temp_cursor(10);--注意:这时光标位于第一条记录之前,也可以外部传入,此时:参数是&v_cusor,不需要再accept了 LOOP -- (4)获取光标中的数据赋给变量 FETCH temp_cursor INTO v_ename; -- (5)循环结束条件 EXIT WHEN temp_cursor%NOTFOUND;-- 让光标下移 -- (6)展示数据 dbms_output.put_line(v_ename); END LOOP; -- (7)关闭光标 -- CLOSE temp_cursor; end; /
需求12:当除数为0时 处理异常
概念:异常也叫做例外,异常是程序设计语言提供的一种功能,用来增强程序的健壮性和容错性。
1、系统定义好的常见异常
系统定义好的异常 no_data_found (没有找到数据) too_many_rows (select …into语句匹配多个行) zero_divide (被零除) value_error (算术或转换错误) timeout_on_resource (在等待资源时发生超时)
代码:
declare -- 定义一个变量接受运算结果 v_result NUMBER; begin v_result:=1/0;--计算出现异常 EXCEPTION --出现异常要处理异常 --判断是哪种异常 WHEN zero_divide THEN dbms_output.put_line('被除数不能为0'); WHEN value_error THEN dbms_output.put_line('数值转换错误'); WHEN others THEN --others 代表其他异常 其他异常也需要捕获 dbms_output.put_line('其他错误'); end;
2、自定义异常
需求13:在数据库中查询50号部门员工的姓名(其实我们emp表中没有50号部门),这时候就会查不到,就可以定义为发生异常。
常见的处理位置
--(1)在declare节中"定义"例外 out_of exception ; --(2)在begin节中可行语句中"抛出"例外 raise out_of ; --(3)在exception节"处理"例外 when out_of then …代码:
DECLARE -- 定义变量接受数据 v_ename emp.ename%TYPE; -- 定义游标接受数据带参数 CURSOR v_cursor(v_deptno NUMBER)IS SELECT ename FROM emp WHERE deptno=v_deptno; -- 定义一个异常 notfound_exception EXCEPTION; BEGIN --打开光标,同时输入数据 -- &v_deptno OPEN v_cursor(&my);-- 注意:不要与定义的变量与v_deptno相同 --循环获取数据,判断是否有这个数据 LOOP FETCH v_cursor INTO v_ename; --如果读到最后依然没有数据,说明没有50号的部门:则抛出异常 IF v_cursor%NOTFOUND THEN RAISE notfound_exception;-- raise是抛出异常的 ELSE --打印输出 dbms_output.put_line(v_ename);-- 注意:千万别写错 --结束每次循环的判断 END IF; --结束循环 END LOOP; --结束游标 CLOSE v_cursor; -- 处理异常 EXCEPTION WHEN notfound_exception THEN dbms_output.put_line('暂无该部门的员工'); END;
说明:有一点小bug(查询出来也会执行异常,回头处理)
需求14:统计每年的入职人数
代码(注意设计思路)
-- 分析 --(1)首先我得拿到日期 --(2)我每个年份定义一个变量,然后判断时那个年份的就累加 declare --(3)定义光标查出年份--并将其转换成字符的形式(好好体会!!!) cursor cemp is select to_char(hiredate,'yyyy') from emp; --(4)定义接收年份的变量 pdate varchar(4); --(4)定义四个年份变量(已知) count80 number :=0; count81 number :=0; count82 number :=0; count87 number :=0; begin open cemp; loop fetch cemp into pdate; --取出年份 exit when cemp%notfound;--结束条件 if pdate='1980' then count80:=count80+1; elsif pdate='1981' then count81:=count81+1; elsif pdate='1982' then count82:=count82+1; else count87:=count87+1; end if; end loop; close cemp; -- 输出展示 -- 注意这个几个变量相加要用括号括起来(表示是一个整体) dbms_output.put_line('总人数'||(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;
知识点:存储过程
存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应用程序都应该用到存储过程(复用性)。
语法:
create [or replace] PROCEDURE 过程名[(参数名 in/out 数据类型--多个参数类型)] AS begin PLSQL子程序体; end;
需求15:创建一个存储过程
CREATE OR REPLACE PROCEDURE hello AS BEGIN dbms_output.put_line('hello world'); END;
调用过程的三种方式:
(1)exec过程名---Oracle自带的SQLPLUS中使用 (2)PL/SQL程序调用--第三方使用 (3)Java调用
方式1:
-- exec hello();--注意:即使没有参数也要带上()
方式2:
BEGIN -- 说明存储过程是可以多次执行的 hello(); hello(); END;
需求16:带有参数的存储过程(in、out)
in 输入参数
需求:为7369号员工涨100工资
create or replace procedure test(myeno in number) is --注意in的位置 -- 定义一个变量用来接收涨前的工资 主要变量定义在begin前面,不要定义到begin 里面去了 oldsal emp.sal%type; begin --根据传过来的员工号查出原来的工资 赋值给变量 select sal into oldsal from emp where empno=myeno; --给工资加100 update emp set sal=sal+100 where empno=myeno; --打印输出 dbms_output.put_line('涨前工资'||oldsal||'涨后是'||(oldsal+100)); end test;
注意:in大小写不敏感,如果只有一个输入参数默认可以不写in。
需求:查询7788号员工的的姓名、职位、月薪,返回多个值
out 输出参数
CREATE OR REPLACE PROCEDURE find(pempno IN NUMBER, psal OUT VARCHAR2, pename OUT VARCHAR2, pjob OUT VARCHAR2) -- 注意:我们是直接使用的这些变量,还尚未定义 AS BEGIN SELECT ename,sal,job INTO pename, psal, pjob FROM emp WHERE empno = pempno; END;
说明:在调用的时候使用到的psal、pname、pjob是没有定义的,因此调用的时候我们需要先定义变量后使用!!!
调用存储函数的代码:
DECLARE psal emp.sal%TYPE; pename emp.ename%TYPE; pjob emp.job%TYPE; BEGIN find(7369, psal, pename, pjob);-- 调用函数后自定义变量接受获取的数据 dbms_output.put_line(psal || pename || pjob); END;/
--------------------------------------------
知识点:存储函数
语法:
CREATE [OR REPLACE] FUNCTION 函数名(Name in type, Name out type, ...) RETURN 返回值类型 AS PLSQL子程序体; BEGIN END;
说明:无论是过程还是函数,as关键字都代替了declare关键字
注意:函数也需要in、out的关键字
需求:查询7788号员工的的姓名(return)、职位(out)、月薪(out),返回多个值
CREATE OR REPLACE FUNCTION findEmpNameAndJobAndSal(pempno IN NUMBER, pjob OUT VARCHAR2, income OUT NUMBER) --这里指定的是返回值类型 RETURN VARCHAR AS /*查询出来的字段与列名相同,就使用列名相同的类型就行了。*/-- 多行注释 pename emp.ename%TYPE; --认真体会此处的作用(与存储过程比较下) BEGIN SELECT sal,ename,job INTO income, pename, pjob FROM emp WHERE empno = pempno; RETURN pename; END;
调用:
DECLARE /*输出的字段与列名的类型是相同的。*/ -- 接受函数中带出来(非非return)的数据 income emp.sal%TYPE; pjob emp.job%TYPE; pename emp.ename%TYPE; BEGIN -- pename是存储函数的返回值; pjob、income是存储函数带回来的值 -- 注意:与存储过程的比、 -- pl/sql特有的方式 := pename := findEmpNameAndJobAndSal(7369, pjob, income); dbms_output.put_line(pename || pjob || income); END;/
存储过程和存储函数的区别?
(1)一般来讲,过程和函数的区别在于函数可以有一个返回值;而过程没有返回值。 (2)但过程和函数都可以通过out指定一个或多个输出参数。我们可以利用out参数,在过程和函数中实现返回多个值。 (3)那也就是说,存储过程,没有返回值,但是可以通过out 输出参数来实现,那我们到底选用存储过程还是存储函数 (4)一般来说,如果有一个返回值,我们选存储函数,如果没有返回值,或者多个返回值,我们可以选存储过程.
补充:
SQL与过程函数使用场景 /* 【适合使用】过程函数: (1)需要长期保存在数据库中 (2)需要被多个用户重复调用 (3)业务逻辑相同,只是参数不一样 (4)批操作大量数据,例如:批量插入很多数据 【适合使用】SQL: (1)凡是上述反面,都可使用SQL (2)对表,视图,序列,索引,等这些还是要用SQL */
额外补充
begin -- Call the procedure :result :=findEmpNameAndJobAndSal(myenum =>7369, -- 传入一个具体的员工号 pename => :pename, --pename不需要声明 psal => :psal, --psal不需要声明(接受传回来的数据) pjob => :pjob); --pjob不需要声明 end;
说明:=> 也是一种赋值方式,:result 是一个绑定变量(不需要声明),调用完函数后,函数返回的结果赋值给了:result。
注意:打印绑定变量的方式
dbms_output.put_line(:result);-- 形如此
-----------------------------------
四、聊一聊变量命名规范
可以遵循如下的方式
未完待续。。。