Upload
others
View
22
Download
0
Embed Size (px)
Citation preview
PL/SQL编程
启迪 想
引领成长
PL/SQL 简介
2
PL/SQL 是过程语言(Procedural Language)与结构化查询
语言(SQL)结合而成的编程语言
PL/SQL 是在SQL语言中扩充了面向过程语言中使用的程
序结构,其中的结构包括:
变量和类型,支持多种数据类型,如大对象和集合类型
控制语句,可使用条件和循环等控制结构
可用于创建存储过程、触发器和程序包,给SQL语句的执行添加
程序逻辑
对象类型和方法
启迪 想
引领成长
PL/SQL 的优点
3
支持 SQL,在 PL/SQL 中可以使用:
• 数据操纵命令
• 事务控制命令
• 游标控制
• SQL 函数和 SQL 运算符
支持面向对象编程 (OOP)
可移植性,可运行在任何操作系统和平台上的Oralce 数据库
更佳的性能,PL/SQL 经过编译执行
启迪 想
引领成长
PL/SQL 的体系结构
PL/SQL 引擎驻留在 Oracle 服务器中
该引擎接受 PL/SQL 块并对其进行编译执行
将PL/SQL 块发送给Oracle 服务器
用户执行过程语句
引擎将 SQL 语句发送给SQL 语句执行器
Oracle 服务器
PL/SQL引擎
SQL 语句执行器
过程语句执行器
执行 SQL 语句
将结果发送给用户
启迪 想
引领成长
PL/SQL 块简介
5
PL/SQL 块是构成
PL/SQL 程序的基本单元
将逻辑上相关的声明和
语句组合在一起
PL/SQL 分为三个部分:声明部分(可选)
执行部分(必需)
异常处理部分(可选)
注:DECLARE、BEGIN、EXCEPTION
后面没有分号(;),而END后必须带有分号(;)
[DECLARE
/*声明PL/SQL用到的变量、类型及游标*/
Declarations
]
BEGIN
/*过程及SQL语句*/
executable statements
[EXCEPTION
/*异常处理,检查及处理在块中可能发生的错误*/
handlers
]
END;
启迪 想
引领成长
PL/SQL 块示例
6
DECLARE
v_sal NUMBER(5);
BEGIN
SELECT sal INTO v_sal FROM emp
WHERE empno = 7369;
IF v_sal < 2000 THEN
UPDATE emp SET sal = sal + 500
WHERE empno = 7369;
END IF;
EXCEPTION /* 异常处理语句 */
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('出错:'|| SQLERRM);
END;
声明部分定义变量、游标和自定义异常
包含 SQL 和 PL/SQL语句的可执行部分
指定出现异常时需要执行的操作
启迪 想
引领成长
变量和常量
7
PL/SQL 块中可以使用变量和常量 在声明部分声明,使用前必须先声明
声明时必须指定数据类型,每行声明一个标识符
在可执行部分的 SQL 语句和过程语句中使用
声明变量和常量的语法:
Variable_name [CONSTANT] datatype [NOT NULL]
[:= value | DEFAULT value];
其中variable_name是要声明的变量名称
CONSTANT参数表明是一个常量
NOT NULL表明声明的变量不能为空,此时变量初始化时必须赋值
:=value|DEFAULT value是给变量赋予初始值
启迪 想
引领成长
变量和常量
8
DECLARE
v_deptno number(2,0) :=10;
v_totalSalary NUMBER(7,2);
BEGIN
...
SELECT sum(salary) INTO v_totalSalary
FROM employee WHERE deptno = v_deptno;
...
END;
给变量赋值有两种方法: 直接给变量赋值,使用赋值语句 :=
使用 SELECT INTO或FETCH INTO语句给变量赋值
启迪 想
引领成长
变量和常量
9
如果变量在声明时使用了constant,则该变量应被初始化,且以后不能改变它的值
DECLARE
v_rate constant number(1, 3):=0.02;
如果在声明时指明not null,那么应该给该变量赋初值,下面声明是错误的:
DECLARE
v_tempvar number not null; (×)
正确的声明为:
DECLARE
v_tempvar number not null:=1; (√)
注意:CONSTANT关键字是在数据类型之前列出的,而NOT NULL是在数
据类型之后列出的
启迪 想
引领成长
注 释
10
在PL/SQL里,可以使用两种符号来写注释,即:
使用双 '-' ( 减号) 加注释
PL/SQL允许用 - 来写注释,它的作用范围是只能在一行有
效。如:
v_sal NUMBER(12,2); - - 工资变量
使用 /* */ 来加一行或多行注释,如:
/* 文件名: statistcs_sal.sql */
启迪 想
引领成长
属性类型
用于引用数据库列的数据类型,以及表示表中一行的记录类型
属性类型有两种:
%TYPE - 引用变量和数据库列的数据类型
%ROWTYPE - 提供表示表中一行的记录类型
使用属性类型的优点:
不需要知道被引用的表列的具体类型
如果被引用对象的数据类型发生改变,PL/SQL 变量的数据类型也随之改变
启迪 想
引领成长
使用%TYPE
定义一个变量,其数据类型与已经定义的某个数据变量的类型相同,或者与数据库表或视图的某个列的数据类型相同,这时可以使用%TYPE
使用%TYPE特性的优点在于:
所引用的数据库列的数据类型可以不必知道
所引用的数据库列的数据类型可以实时改变
启迪 想
引领成长
使用%TYPE
%TYPE:
定义基于列的变量的数据类型
定义声明于前面已经声明的变量的数据类型
定义格式: variable_name table_name.column_name%TYPE
例:t_empno emp.empno%TYPE
定义的变量名称
引用的数据表名
引用的数据表的字段名
启迪 想
引领成长
使用%TYPE
DECLARE
-- 用 %TYPE 类型定义与表相配的字段
-- '&'表示empno的值从控制台输入
v_empno emp.empno%TYPE :=&empno;
v_ename emp.ename%TYPE;
BEGIN
SELECT ename INTO v_ename FROM emp
WHERE empno=v_empno;
DBMS_OUTPUT.PUT_LINE(v_ename);
END;
启迪 想
引领成长
使用%ROWTYPE
PL/SQL提供%ROWTYPE操作符, 返回一个记录类型, 其数据类型和数据库表、视图或游标的数据结构相一致
使用%ROWTYPE特性的优点在于: 所引用的数据库中列的个数和数据类型可以不必知道
所引用的数据库中列的个数和数据类型可以实时改变
启迪 想
引领成长
使用%ROWTYPE
%ROWTYPE:声明从表定义中导出的记录结构
定义格式:
record_variable_name table_name%rowtype
rec emp%ROWTYPE
定义的记录变量名称
引用的数据表名
DECLARE
v_empno emp.empno%TYPE :=&empno;
rec emp%ROWTYPE;
BEGIN
SELECT * INTO rec FROM emp
WHERE empno = v_empno;
DBMS_OUTPUT.PUT_LINE('姓名:'||rec.ename||
'工资:'||rec.sal);
END;
启迪 想
引领成长
PL/SQL的控制结构
17
PL/SQL 支持的主要流程控制结构:
条件控制
IF 语句
CASE 语句
循环控制
LOOP 循环
WHILE 循环
FOR 循环
启迪 想
引领成长
条件控制
18
IF 语句根据条件执行一系列语句,有三种形式:
IF-THEN
IF <表达式> THEN
SQL语句;
END IF;
IF-THEN-ELSE
IF <表达式> THEN
SQL语句;
ELSE
其它语句;
END IF;
IF-THEN-ELSIF
IF <表达式> THEN
SQL语句;
ELSIF < 其它表达式> THEN
其它语句;
ELSIF < 其它表达式> THEN
其它语句;
ELSE
其它语句;
END IF;
启迪 想
引领成长
IF 语句演示示例
19
DECLAREv_empno emp.empno%TYPE :=&empno;v_salary emp.sal%TYPE;v_comment VARCHAR2(35);
BEGINSELECT sal INTO v_salary FROM empWHERE empno = v_empno;IF v_salary < 1500 THEN
v_comment:= 'Fairly less';ELSIF v_salary < 3000 THEN
v_comment:= 'A little more';ELSE
v_comment:= 'Lots of salary';END IF;DBMS_OUTPUT.PUT_LINE(v_comment);
EXCEPTION WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('你需要的数据不存在!');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('发生其它错误!');END;
启迪 想
引领成长
条件控制----CASE语句
20
CASE 语句用于根据单个变量或表达式与多个值进行比较
执行 CASE 语句前,先计算选择器的值
CASE nWHEN 1 THEN Action1;
WHEN 2 THEN Action2;
WHEN 3 THEN Action3;
ELSE ActionOther;
END CASE; BEGIN
CASE '&grade'
WHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('优异');
WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE ('优秀');
WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE ('良好');
WHEN 'D' THEN DBMS_OUTPUT.PUT_LINE ('一般');
WHEN 'F' THEN DBMS_OUTPUT.PUT_LINE ('较差');
ELSE DBMS_OUTPUT.PUT_LINE ('没有此成绩');
END CASE;
END; 20
启迪 想
引领成长
循环控制
21
循环控制用于重复执行一系列语句
循环控制语句包括:
LOOP、EXIT 和 EXIT WHEN
循环控制的三种类型:
LOOP-EXIT-END - 无条件循环
WHILE-LOOP-END - 根据条件循环
FOR-IN-LOOP-END - 循环固定的次数
启迪 想
引领成长
LOOP-EXIT-END循环
22
简单循环语法
DECLARE
num NUMBER :=0;
BEGIN
LOOP
num := num + 1;
DBMS_OUTPUT.PUT_LINE('num 的当前值为:'|| num);
EXIT WHEN num = 10;
END LOOP;
END;
LOOP
要执行的语句;
EXIT WHEN <条件语句> /*条件满足,退出循环语句*/
END LOOP;
注:在使用LOOP语句时必须使用EXIT语句,强制循环结束
启迪 想
引领成长
WHILE-LOOP-END 循环
23
WHILE 循环语法
DECLARE
num NUMBER;
BEGIN
num:= 0;
WHILE num < 10 LOOP
num:= num+1;
DBMS_OUTPUT.PUT_LINE('num的当前值为:' || num);
END LOOP;
END;
WHILE <布尔表达式> LOOP
要执行的语句;
END LOOP;
启迪 想
引领成长
FOR-IN-LOOP-END循环
24
数字式循环语法
BEGIN
FOR num IN 1..10 LOOP
DBMS_OUTPUT.PUT_LINE('num 的当前值为: ' || num);
END LOOP;
END;
FOR 循环计数器 IN [ REVERSE ] 下限 .. 上限 LOOP
要执行的语句
END LOOP
每循环一次,循环变量自动加1
使用关键字REVERSE,循环变量自动减1。跟在IN REVERSE
后面的数字必须是从小到大的顺序,而且必须是整数,不能是变
量或表达式。可以使用EXIT 退出循环
启迪 想
引领成长
游标
PL/SQL用游标来管理SQL的SELECT语句,游标是为处理这些语句而分配的一大块内存,有时用户手工定义游标
游标定义类似于其他PL/SQL变量,并且必须遵守同样的命名规则
执行PL/SQL
程序
内存单元
保存到游标中
一次处理一行
检索行
提取
行
Oracle服务器
启迪 想
引领成长
游标类型
逐行处理查询结果,以编程的方式访问数据
游标的类型:
隐式游标 REF 游标显式游标
在 PL/SQL 程序中执行DML SQL 语句时自动创建隐式游标。显式游标用于处理返回多行的查询。REF 游标用于处理运行时才能确定的动态 SQL 查询的结果
游标类型
启迪 想
引领成长
隐式游标
在PL/SQL中使用DML语句时自动创建隐式游标
隐式游标自动声明、打开和关闭,其名为 SQL
通过检查隐式游标的属性可以获得最近执行的DML 语句的信息
启迪 想
引领成长
隐式游标的属性
28
隐式游标的属性有:
%FOUND:SQL 语句影响了一行或多行时为 TRUE
%NOTFOUND:SQL 语句没有影响任何行时为TRUE
%ROWCOUNT:SQL 语句影响的行数,如果DML语句没有影响任何行,则%ROWCOUNT属性将返回0
%ISOPEN:游标是否打开,始终为FALSE,因为隐式游标在DML语句执行时打开,结束时就立即关闭
启迪 想
引领成长
隐式游标----SQL%FOUND
--如果有行被更新,SQL%FOUND就返回true,并打印相应信息。BEGIN
UPDATE emp SET sal=sal+8 WHERE empno=7900;
IF sql%found THEN
dbms_output.put_line('表已更新');
ELSE
dbms_output.put_line('编号未找到');
END IF;
END;
只有在 DML 语句影响一行或多行时,才返回 True
启迪 想
引领成长
隐式游标---- SQL%NOTFOUND
--如果没有行被更新,SQL%NOTFOUND就返回true,并打印相应信息。BEGIN
UPDATE emp SET sal=sal+8 WHERE empno=7900;
IF sql%notfound THEN
dbms_output.put_line('编号未找到表已更新');
ELSE
dbms_output.put_line('表已更新');
END IF;
END;
如果 DML 语句不影响任何行,则返回 True
启迪 想
引领成长
隐式游标---- SQL%ROWCOUNT
BEGIN
UPDATE emp SET sal=sal+8 WHERE empno=7900;
IF sql%found THEN
dbms_output.put_line('表已更新,更新了' || SQL%ROWCOUNT || '行');
ELSE
dbms_output.put_line('编号未找到');
END IF;
END;
返回 DML 语句影响的行数
启迪 想
引领成长
显示游标
显式游标操作顺序 声明游标:使用查询来定义游标的列和行,该查询可返回多行
打开游标:使用PL/SQL命令OPEN来打开一个声明的游标
提取数据:从游标中重复提取每条记录到数据结构中,直到数据集合被提空
关闭游标:使用完游标之后将其关闭
数据库
打开游标
30George3
44Roger2
45James1
GRADESNAMESNO提取行
变量关闭游标
启迪 想
引领成长
显示游标
声明显式游标语法:
CURSOR cursor_name [(parameter [,parameter]...)]
[RETURN return_type] IS select_statement;
OPEN cursor_name [(parameters)];
FETCH cursor_name INTO variables;
CLOSE cursor_name;
打开游标语法:
关闭游标语法:
从游标中获取记录语法:
游标的名称为游标指定输入参数,参数只指定数据类型,
不指定大小
定义游标提取的行的类型
指游标定义的查询语句
变量名
启迪 想
引领成长
显示游标的属性
游标的属性
%FOUND:如果执行最后一条FETCH语句成功返回行 ,则值为TRUE
%NOTFOUND:如果执行最后一条FETCH语句未能提取行时,与%FOUND相反
%ISOPEN: 当游标已打开时返回TRUE
%ROWCOUNT:返回到目前为止游标提取的行数。在第一次获取之前,%ROWCOUNT为零。当FETCH语句返回一行时,则该数加1
启迪 想
引领成长
DECLARE
dept_name dept.dname%type;
dept_loc dept.loc%type;
CURSOR c1 is
SELECT dname, loc FROM dept WHERE deptno <= 30;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO dept_name, dept_loc;
EXIT WHEN c1%notfound;
dbms_output.put_line(dept_name||'---
'||dept_loc);
END LOOP;
CLOSE c1;
END;
输出部门号小于30的部门记录
不带参数的显示游标
启迪 想
引领成长
DECLARE
CURSOR cur_emp(c_sal in number) IS
SELECT * FROM emp where sal>c_sal;
emp_rec emp%rowtype;
e_sal emp.sal%type:=1000;
BEGIN
OPEN cur_emp(e_sal);
dbms_output.put_line('工资高于'||e_sal||'的员工清单如下:');
LOOP
FETCH cur_emp INTO emp_rec;
EXIT WHEN cur_emp%notfound;
dbms_output.put_line('员工编号:' || emp_rec.empno||
' 员工姓名:'|| emp_rec.ename||
' 员工工资:'|| emp_rec.sal);
END LOOP;
CLOSE cur_emp;
END;显示所有员工工资大于输入参数值的员工信息
带参数的显示游标
启迪 想
引领成长
DECLARE
v_empno emp.empno%TYPE;
v_sal emp.sal%TYPE;
CURSOR cur_sal IS SELECT empno, sal FROM emp;
BEGIN
OPEN cur_sal ;
LOOP
FETCH cur_sal INTO v_empno, v_sal;
EXIT WHEN cur_sal %NOTFOUND;
IF v_sal<=1200 THEN
UPDATE emp SET sal=sal+50 WHERE empno=v_empno;
DBMS_OUTPUT.PUT_LINE('编号为'||v_empno||'工资已更新!');
END IF;
COMMIT;
DBMS_OUTPUT.PUT_LINE('记录数:'|| cur_sal %ROWCOUNT);
END LOOP;
CLOSE cur_sal;
END;
给工资低于1200 的员工增加工资50
显示游标----%ROWCOUNT
启迪 想
引领成长
循环游标
循环游标用于简化游标处理代码
自动隐式打开游标,避免显式的OPEN语句,不需要open
自动完成FETCH,从结果集中获取行,不需要fetch
退出游标FOR循环时,自动关闭游标,与循环结束的方法无关,不需要close
循环游标自动创建%ROWTYPE类型的变量并将此变量用作记录索引
循环游标的语法如下:
FOR record_index IN cursor_name[(parameters)]
LOOP
executable_statements --游标数据处理代码END LOOP;
注:record_index是PL/SQL声明的记录变量,此变量的属性声明为%ROWTYPE类型,作用域在FOR循环之内。 parameters是用于为游标指定输入参数
启迪 想
引领成长
循环游标示例
DECLARE
CURSOR emp_cur IS
SELECT empno,ename,sal FROM emp WHERE job='MANAGER'
BEGIN
--隐含打开游标FOR emp_row IN emp_cur
LOOP
dbms_output.put_line('员工编号:' || emp_row.empno||
' 员工姓名:'|| emp_row.ename||
' 员工工资:'|| emp_row.sal);
END LOOP;
--隐含关闭游标END;
此示例声明了emp_cur游标,emp_row是记录索引。在处理完游标中的所有记录之后,循环游标将终止
启迪 想
引领成长
REF 游标和游标变量
REF 游标和游标变量用于处理运行时动态执行的 SQL 查询
创建游标变量需要两个步骤:
声明 REF 游标类型
声明 REF 游标类型的变量
用于声明 REF 游标类型的语法为:
注:RETURN语句为可选子句,用于指定游标提取结果集的返回类型。包括RETURN语句表示是强类型REF游标,否则是弱类型REF游标,可以获取任何结果集。
TYPE ref_cursor_name IS REF CURSOR
[RETURN record_type];
启迪 想
引领成长
REF 游标和游标变量
打开游标变量的语法如下:
OPEN cursor_name FOR select_statement;
用于提取和关闭游标变量的语法与显式游标相似
TYPE my_curtype IS REF CURSOR
RETURN student%ROWTYPE;
stu_cur my_curtype;
声明强类型的 REF 游标
TYPE my_ctype IS REF CURSOR;
stu_cur my_ctype;
声明弱类型的 REF 游标
启迪 想
引领成长
REF 游标和游标变量示例DECLARE
--ref_cur_emp是一个弱类型REF游标类型
TYPE ref_cur_emp IS REF CURSOR;
refcur ref_cur_emp; --refcur是游标变量
TYPE rec IS RECORD( --rec是自定义记录类型v_id emp.empno%type,
v_ename emp.ename%type,
v_salary emp.sal%type
);
rec_emp rec;
BEGIN
if &sal > 2000 then
OPEN refcur FOR select empno,ename,sal from emp where sal>2000;
Else
OPEN refcur FOR select empno,ename,sal from emp where sal<=2000;
end if;
LOOP
fetch refcur into rec_emp;
exit when refcur%notfound;
dbms_output.put_line('员工编号:' || rec_emp.v_id ||
' 员工姓名:' || rec_emp.v_ename ||
' 员工工资:' || rec_emp.v_salary);
END LOOP;
CLOSE refcur;
END;