PL/SQL_高级编程

一.PL/SQL基础知识

PL/SQL_高级编程图片

1.什么是PL/SQL

PL/SQL(Procedural Language/SQL,过程语言/SQL)是结合了Oracle过程语言和结构化查询语言(SQL)的一种扩展语言。使用PL/SQL可以编程具有很多高级功能的程序。

优点:

(1)具有编程语言的特点,能把一组SQL语句放到一个模块中,使其更具有模块化程序的特点,如判断循环

(2)可以采用过程性语言控制程序的结构,它能把一组SQL语句放到一个模块中,使其更具有模块化程序结构,如判断循环等程序结构

(3)和其他编程语言一样,PL/SQL可以对程序中的错误进行自动处理,使程序能够在遇到错误时不会中断,即它的异常处理机制。

(4)PL/SQL程序块具有更好的可移植性,可以移植到另一个Oracle数据库中。

(5)PL/SQL程序减少了网络的交互,有助于提高程序性能。

(1).PL/SQL体系结构

PL/SQL 是过程语言(Procedural Language)与结构化查询语言(SQL)结合而成的编程语言

PL/SQL的工作原理

PL/SQL引擎接受 PL/SQL 块并对其进行编译执行

该引擎执行所有过程语句

将SQL语句发送给Oracle的SQL语句执行器

PL/SQL_高级编程图片

(2)PL/SQL块简介

PL/SQL是一种块结构的语言,它将一组语句放在一个块中。

PL/SQL块将逻辑上相关的声明和语句组合在一起。

匿名块是一个未在数据库中命名的PL/SQL块,在运行时被传递到PL/SQL引擎以便执行。

在PL/SQL块中可以使用select、insert、update、delete等DML语句,事务控制语句及SQL函数等。

PL/SQL不允许直接使用create、drop或alter等DDL语句,但可以通过动态SQL来执行它们。

一个PL/SQL块由三部分组成,即声明、执行部分、异常处理部分。

PL/SQL的结构

[DECLARE]
	--声明部分:在此声明PL/SQL用到的变量、类型及游标,以及局部的存储过程和函数
BEGIN
	--执行部分:过程及SQL语句,即程序的主要部分
[ECEPTION]
	--异常处理部分:错误处理
END;

--在dos/sqlplus中运行时,输入完以上,还需要set serverout on
SQL>set serverout on
SQL>/

注意:其中执行部分不可省略

(3)运算符和表达式

PL/SQL支持的操作符包含关系运算符、一般运算符和逻辑运算符等,与SQL语言类似

1.关系运算符

运算符 意义
= 等于
<>、!=、~=、^= 不等于
< 小于
> 大于
<= 小于或等于
>= 大于或等于

2.一般运算符

运算符 意义
+ 加号
- 减号
* 乘号
/ 除号
:= 赋值符
=> 关系符
.. 范围运算符
|| 字符连接符

3.逻辑运算符

运算符 意义
IS NULL 是空值
BETWEEN AND 介于两者之间
IN 在一列值中间
AND 逻辑与
OR 逻辑或
NOT 取反,如IS NOT NULL,NOT NUll

4.常量和变量的声明

在PL/SQL块的可执行部分引用变量和常量前,必须先对其进行声明。常量和变量在PL/SQL块的部分声明,在PL/SQL块的可执行部分被使用。

语法:

--声明变量
variable_name data_type[(size)][:=init_value]
解释:
1.variable_name:变量名称
2.data_type:变量的SQL或PL/SQL数据类型
3.size:变量范围
4.init_value:变量初始值

--声明常量
variable_name CONSTANT data_type:=value

--引用型变量:即e_no的类型与emp表中的empno相同
--表.字段%type  赋给e_no
declare
	e_no emp.empno%type;
begin
	select empno into e_no from emp where empno=7369;
	--输出
	dba_output.put_line(e_no);
end;

--记录型变量:记录变量分量的引用
--表%rowtype  赋给emps,相当于对象
declare
	emps emp.empno%rowtype;
begin
	select * into emps from emp where empno=7369;
	--输出
	dba_output.put_line(emps.empno);
end;

PL/SQL程序设计中的标识符定义与SQL的标识符定义的要求相同,要求限制如下:

(1)标识符名不能超过30个字符

(2)第一个字符必须为字母

(3)不区分大小写

(4)不能用“-”(减号)

(5)不能是SQL保留字段

标 识 符 命名规则 例 子
程序变量 v_name v_student_name
程序常量 c_name c_company_name
游标变量 cursor_name cursor_emp
异常标志 e_name e_too_many
表类型 name_table_type emp_record_type
name_table emp_table
记录类型 name_record emp_record
绑定变量 g_name g_year_sal

案例:

declare
	v_name varchar2(20);
	v_rate number(7,2);
	c_rate_incr CONSTANT number(7,2):=1.10
begin
	--方法一:通过select into 给变量赋值
	select ename,sal*c_rate_incr into v_ename.v_rate from employee where 		 empno='7788';
	
	--方法二:通过赋值操作符“:=”给变量赋值
	v_ename:='SCOTT';
end;

select 语句在pl/sql中有改变,为select into。查询结果只能返回一行并赋值到变量中保存。

Oracle 11g新增特性:可以使用pl/sql块中的赋值语句访问序列,提高灵活性。

v_no:=emp_seq.nextval;

5.注释

单行 --

多行 /* */

2.PL/SQL数据类型

PL/SQL变量,常量和参数都必须有一个有效的数据类型,指定存储格式,约束和值的有效范围。本教程介绍标量和LOB在PL/SQL,其他两种数据类型可用的数据类型将涵盖在后面的章节。

分类 描述
标量 单值没有内部组件,如NUMBER, DATE 或 BOOLEAN
大对象(LOB) 指向其他数据项,如文本,图形图像,视频剪辑和声音等存储大对象
复合 具有可单独访问的内部组件的数据项。例如,集合和记录
引用 指向其他数据项

1.PL/SQL标量数据类型和子类型

PL/SQL标量数据类型和子类型受到以下几大类:

数据类型 描述
数字 在其上执行的算术运算的数值
字符 代表单个字符或字符的字母数字字符串值
布尔 在其上执行的逻辑运算的逻辑值
日期时间 日期和时间

PL/SQL提供的数据类型的子类型。例如,数据类型数有一个叫作INTEGER的子类型。可以使用子类型在PL/SQL程序中,使数据类型与其他程序中的数据类型兼容,而嵌入PL/SQL代码在另一个程序,如Java程序。

2.PL/SQL数值数据类型和子类型

以下是PL/ SQL预定义的数字数据类型及其子类型的详细信息:

数据类型 描述
PLS_INTEGER 通过2,147,483,647到-2147483648范围内有符号整数,以32位表示
BINARY_INTEGER 通过2,147,483,647到-2147483648范围内的有符号整数,以32位表示
BINARY_FLOAT 单精度IEEE 754格式的浮点数
BINARY_DOUBLE 双精度IEEE 754格式的浮点数
NUMBER(prec, scale) 定点或浮点数在范围1E-130至(但不包括)绝对值1.0E126。 NUMBER变量也可以表示0
DEC(prec, scale) ANSI具体的定点类型使用38位小数最大精度
DECIMAL(prec, scale) IBM特定的固定点型具有38位小数最大精度
NUMERIC(pre, secale) 浮点类型具有38位小数最大精度。
DOUBLE PRECISION 具有126个二进制数字最大精度ANSI特定浮点型(约38位十进制数)
FLOAT 具有126个二进制数字(约38位十进制数)最大精度ANSI和IBM特定的浮点型
INT 具有38位小数最大精度ANSI具体的整数类型
INTEGER ANSI和IBM的38位小数最大精度具体的整数类型
SMALLINT ANSI和IBM的38位小数最大精度具体的整数类型
REAL 具有63位二进制数字最大精度浮点型(大约18位小数)

下面是一个有效的声明:

DECLARE
   num1 INTEGER;
   num2 REAL;
   num3 DOUBLE PRECISION;
BEGIN
   null;
END;
/

让我们编译和运行上面的程序,这将产生以下结果:

PL/SQL procedure successfully completed

3.PL/SQL字符数据类型和子类型

以下是PL/SQL预定义的字符数据类型及其子类型的详细信息:

数据类型 描述
CHAR 具有32,767个字节的最大尺寸固定长度字符串
VARCHAR2 具有32,767个字节的最大尺寸变长字符串
RAW 可变长度的二进制或字节字符串的32,767个字节的最大尺寸,而不是由PL/ SQL解释
NCHAR 具有32,767个字节的最大尺寸的固定长度国家字符串
NVARCHAR2 具有32,767个字节的最大尺寸可变长度国家字符串
LONG 具有32,760字节最大尺寸变长字符串
LONG RAW 可变长度的二进制或字节字符串的32,760字节的最大尺寸,而不是由PL/SQL解释
ROWID 物理行标识符,一行在一个普通的表中的地址
UROWID 物理通用行标识符(物理,逻辑,或外国的行标识符)

4.PL/SQL布尔数据类型

在逻辑操作中使用布尔数据类型存储的逻辑值。逻辑值为:true 和 false 的布尔值以及 NULL 值。

但是,SQL没有数据类型等同于布尔。因此,布尔值不能用于:

  • SQL语句
  • 内置SQL函数(如TO_CHAR)
  • 从SQL语句调用PL/ SQL函数

5.PL/SQL日期时间和间隔类型

DATE数据类型存储固定长度的日期时间,其中包括每天在几秒钟内从午夜开始的时间。有效的日期范围从公元前4712年1月1日至999912月31日。

默认的日期格式由Oracle初始化参数NLS_DATE_FORMAT设置。例如,默认的可以是“DD-MON-YY',它包括一个两位数字的月份中的日期,月份名称的缩写,以及年的最后两位数字,例如,01- OCT-12。

每个日期,包括世纪,年,月,日,时,分,秒。下表显示了每个字段的有效值:

字段名称 有效日期时间值 有效的间隔值
YEAR -4712 to 9999 (年除外 0) 任何非零整数
MONTH 01 to 12 0 to 11
DAY 01 to 31 (受制于年份和月份的数值,按日历的区域设置的规则) 任何非零整数
HOUR 00 to 23 0 to 23
MINUTE 00 to 59 0 to 59
SECOND 00 to 59.9(n), 其中,9(n)是时间小数秒精度 0 to 59.9(n), 其中,9(n)是区间小数秒精度
TIMEZONE_HOUR -12 to 14 (范围可容纳夏令时更改) 不适用
TIMEZONE_MINUTE 00 to 59 不适用
TIMEZONE_REGION 在动态性能视图找到V$TIMEZONE_NAMES 不适用
TIMEZONE_ABBR 在动态性能视图找到V$TIMEZONE_NAMES 不适用

###6.PL/SQL大对象(LOB)数据类型

大对象(LOB)数据类型是指大到数据项,例如文本,图形图像,视频剪辑和声音波形。 LOB数据类型允许高效的,随机的,分段访问这些数据。以下是预定义的PL/SQL LOB数据类型:

数据类型 描述 大小
BFILE 用于存储大型二进制对象在操作数据库之外的系统文件 依赖于系统,不能超过4千兆字节(GB)
BLOB 用于存储大型二进制对象在数据库中 8到128兆兆字节(TB)的
CLOB 用于存储字符大块数据在数据库中 8 - 128 TB
NCLOB 用于在数据库中存储大块NCHAR数据 8 - 128 TB

7.PL/SQL用户定义的子类型

子类型是另一种数据类型,这是所谓的基本类型的子集。 子类型具有其基本类型相同的操作,但其只是有效值的一个子集。

PL/SQL预定义了一些子类型的封装标准。例如,PL/SQL预定义的子类型CHARACTER和INTEGER如下:

SUBTYPE CHARACTER IS CHAR;
SUBTYPE INTEGER IS NUMBER(38,0);

可以定义和使用自己的子类型。下面的程序说明定义和使用一个用户定义的子类型:

DECLARE
   SUBTYPE name IS char(20);
   SUBTYPE message IS varchar2(100);
   salutation name;
   greetings message;
BEGIN
   salutation := 'Reader ';
   greetings := 'Welcome to the World of PL/SQL';
   dbms_output.put_line('Hello ' || salutation || greetings);
END;
/

当上述代码在SQL提示符执行时,它产生了以下结果:

Hello Reader Welcome to the World of PL/SQL

PL/SQL procedure successfully completed.

8.PL/SQL NULL

PL/SQL NULL值表示缺少或未知的数据,蛤它不是一个整数,字符,或任何其他特定的数据类型。需要注意的是NULL不是一样的空数据串或空字符值'\0'。NULL可以被分配,但它不能与任何东西等同,包括其本身。

二.PL/SQL控制语句

条件控制,循环控制,顺序控制

(1)条件控制

语法:

--if语句
if <布尔表达式> then
	pl/sql和sql语句;
end if;
-------------------
if <布尔表达式> then
	pl/sql和sql语句;
elsif
	其他语句;
end if;

--case语句
--格式一
case 条件表达式
	when 条件表达式结果1 then
		语句段1
	when 条件表达式结果2 then
		语句段2
	......
	when 条件表达式结果n then
		语句段n
	[else 语句段]
end case;
--格式二
case
	when 条件表达式结果1 then
		语句段1
	when 条件表达式结果2 then
		语句段2
	......
	when 条件表达式结果n then
		语句段n
	[else 语句段]
end case;

案例:

--sqldeveloper 相当于dos
set serverout on
--键盘输入
--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');
  else dbms_output.put_line('输入为其他');
  end if;
end;
/

(2)循环控制

循环控制包括LOOP和EXIT语句,使用exit语句可以立即退出循环,使用exit when语句可以根据条件结束循环

循环共三种:loop、while、for

1.loop循环

语法:

loop
	要执行的语句
	exit when <条件语句> --条件满足,退出循环语句
end loop;

案例:

declare
num number:=1;
begin
  loop
    num:=num+1;
    exit when num=10;
    dbms_output.put_line(num);
  end loop;
end;

2.while循环

语法:

while <布尔表达式> loop
	要执行的语句
end loop;

案例:

declare
num number:=1;
begin
  while num<10 loop
        num:=num+1;
        dbms_output.put_line(num);
  end loop;
end;

3.for循环

语法:

for 循环计数器 in [reverse] 下限...下限 loop
	要执行的语句
end loop;

案例:

declare
  i number;
begin
	--循环遍历1-10
  for i in 1..10 loop
     dbms_output.put_line(i);
  end loop;
  	--反向循环遍历1-10
  for i in reverse 1..10 loop
    dbms_output.put_line(i);
  end loop;
end;

(3)顺序控制

顺序控制用于顺序执行语句。顺序控制包括null语句和goto语句。goto不推荐

null语句:null语句是一个可执行语句,相当于一个占位符或不可执行任何操作的空语句,提高可读性。

三.异常

#四.游标(光标Cursor)

游标是用来处理使用select语句从数据库检索到的多行记录的工具

游标分类:

​ 1.显示游标:返回多条记录时,使用显示游标逐行读取

​ 2.隐式游标:PL/SQL自动为DML语句创建隐式游标,包含一条返回记录

1.显式游标

(1)游标的使用

(1)声明光标:

语法:

cursor cursor_name [(parameter[,patameter]...)]
[return return_type] is select_statement;
解释:
1.cursor_name:游标的名称
2.parameter:用于为游标指定输入参数,不能使用长度约束
3.return_type:用于定义游标提取的行的类型
4.select_statement:指定游标定义的查询语句

(2)打开光标:

语法:

open curosr_name [(parameters)];

(3)提取游标

语法:

fetch cursor_name into variables;
解释:
1.cursor_name:游标名称
2.variables:变量名

(4)关闭光标

语法:

close cursor_name;

案例:

declare 
  name emp.ename%type;
  sal emp.sal%type;
  cursor emp_cursor is select ename,sal from emp;
begin
  open emp_cursor;
  loop
  fetch emp_cursor into name,sal;
  exit when emp_cursor%notfound;
  dbms_output.put_line('第'||emp_cursor%rowcount||'个雇员:'||name||'的月薪是'||sal);
  end loop;
  close emp_cursor;
end;
----------
第1个雇员:SMITH的月薪是800
第2个雇员:ALLEN的月薪是1600
第3个雇员:WARD的月薪是1250
第4个雇员:JONES的月薪是2975
第5个雇员:MARTIN的月薪是1250
第6个雇员:BLAKE的月薪是2850
第7个雇员:CLARK的月薪是2450
第8个雇员:KING的月薪是5000
第9个雇员:TURNER的月薪是1500
第10个雇员:JAMES的月薪是950
第11个雇员:FORD的月薪是3000
第12个雇员:MILLER的月薪是1300

(2)显式子游标的属性

属性名称 说 明
%found 用于检验游标是否成功,通常在FETCH语句前使用,当游标按照条件查询出一条记录时,返回true
%isopen 判断游标是否处于打开状态,视图打开一个已经打开或者已经关闭的游标,将会出现错误
%notfound 与%found的作用相反,当按照条件无法查询到记录时,返回true
%rowcount 循环执行游标读取数据时,返回检索出的记录数据的行数

(3)使用显式游标删除或更新

使用游标时,如果处理过程中需要删除或更新行,在定义游标时必须使用select ...for update语句,而在执行delete和update时使用where current of子句指定游标的当前行。

声明更新游标语法:

update table_name is select_sataement for update [of columns];
解释:
1.for update [of columns]为更新查询,锁定选择的行。
(1)当前单表更新查询时,可以省略of子句
(2)当选择多个表更新查询时,被锁定的行来源于of子句后声明的列所在的表中的行。

在使用for update子句声明游标之后,可以使用以下更新行:

update table_name 
	set columns_name = columns_value 
where current of cursor_name;
解释:
多表查询更新时,更新表为锁定行所在的表。

案例:给所有员工增加200工资

declare 
  cursor emp_cursor is select ename,sal from emp e inner join dept d on e.deptno=d.deptno for update of sal;
  --v_num记录行数
  v_num emp_cursor%rowtype;
begin
  open emp_cursor;
  if not emp_cursor%isopen then
    open emp_cursor;
  end if;
  loop
  fetch emp_cursor into v_num;  
  exit when emp_cursor%notfound;
  update emp set sal=sal+200 where current of emp_cursor;
  end loop;
  close emp_cursor;
end;

###(4)使用循环游标简化游标的读取

循环游标隐式打开游标自动从活动集获取行,然后再处理完所有行时关闭游标。循环游标自动创建%rowtype类型的记录型变量用作记录索引。

语法:

for record_index in cursor_name
loop
	exectable_statements
end loop;
解释:
1.record_index:是PL/SQL声明的记录变量,此变量的属性为%rowtype类型,作用域在for循环之内,即在for循环之外不能访问此变量。

循环游标特性:

(1)在从游标中提取了所有记录之后自动终止

(2)提取和处理游标中的每一条记录

(3)如果在提取记录之后%notfound属性返回true,则终止循环。

(4)如果未返回行,则不进入循环。

案例:

--显示员工表所有员工的姓名和薪水
declare 
  cursor emp_cursor is select ename,sal from emp;
begin
  for emp_record in emp_cursor loop
      dbms_output.put_line('第'||emp_cursor%rowcount||'个员工:'||emp_record.ename||'的工资是'||emp_record.sal);
  end loop;
end;
--------------
第1个员工:SMITH的工资是1000
第2个员工:ALLEN的工资是1800
第3个员工:WARD的工资是1450
第4个员工:JONES的工资是3175
第5个员工:MARTIN的工资是1450
第6个员工:BLAKE的工资是3050
第7个员工:CLARK的工资是2650
第8个员工:KING的工资是5200
第9个员工:TURNER的工资是1700
第10个员工:JAMES的工资是1150
第11个员工:FORD的工资是3200
第12个员工:MILLER的工资是1500

(5)no_data_found和%notfound区别

1.select...into语句返回0条和多条记录时触发not_data_found

2.当update或delete语句的where子句未找到时,触发%notfound

3.在提取循环中用%notfound或%found来确定循环的退出条件,而不用not_data_found

其他

1.&

--手动给a赋值
select empno,&a from emp;  
select * from &a;
insert into emp(empno,sal) values(&a,&b);