最新Oracle基础教程(三)

Oracle学习笔记(三)

文章目录

  • Oracle学习笔记(三)
    • 1 Oracle
      • 1.1 多表查询
        • 1.1.1 基本查询
        • 1.1.2外连接
        • 1.1.3 自连接
      • 1.2 分组统计
      • 1.3 子查询
      • 1.4 exists和not exists关键字
      • 1.5 分页查询
      • 1.6 视图
      • 1.7 索引
        • 1.7.1 单列索引
        • 1.7.2 复合索引
        • 1.7.3 索引的使用原则:
      • 1.8 PL/SQL
        • 1.8.1 PL/SQL简介
        • 1.8.2 语法
        • 1.8.3 数据类型
        • 1.8.4 变量定义
        • 1.8.5 运算符
        • 1.8.6 if分支
        • 1.8.7 loop循环
        • 1.8.8 字符串
        • 1.8.9 字符串函数
        • 1.8.10 数组
        • 1.8.11游标
        • 1.8.12异常
        • 1.8.13 存储过程
        • 1.8.14 存储函数
        • 1.8.15 游标
        • 1.8.16 记录
      • 1.9 触发器
        • 1.9.1 触发器作用
        • 1.9.2 触发器类型
        • 1.9.3 触发器实际应用
      • 1.10 包
        • 1.10.1包规范
        • 1.10.2包体
        • 1.10.3使用包元素
      • 1.11 集合
        • 1.11.1索引表
        • 1.11.2嵌套表
        • 1.11.3 集合方法
        • 1.11.4 集合异常
      • 1.12 事务
        • 1.12.1 开始和结束事务
        • 1.12.2提交事务
        • 1.12.3 回滚事务
        • 1.12.4 保存点 - Savepoints
        • 1.12.5 自动事务控制
      • 1.13 日期和时间
        • 1.13.1 日期时间和间隔数据类型的字段值
        • 1.13.2 日期时间数据类型和函数
        • 1.13.3 区间数据类型和函数
      • 1.14 输出
      • 1.15 面向对象
        • 1.15.1 实例化对象
        • 1.15.2 成员方法
        • 1.15.3 使用Map方法
        • 1.15.4 使用排序方法
        • 1.15.5 PL/SQL对象的继承
        • 1.15.6 PL/SQL中的抽象对象

1 Oracle

1.1 多表查询

1.1.1 基本查询

使用一张以上的表做查询就是多表查询

语法: SELECT {DISTINCT} *|列名… FROM 表名 别名,表名1 别名

​ {WHERE 限制条件 ORDER BY 排序字段 ASC|DESC…}

范例:查询员工表和部门表

select * from emp,dept;

我们发现产生的记录数是56条,我们还会发现emp表是14条,dept表是4条,56正是emp表和dept表的记录数的乘积,我们称其为笛卡尔积。

如果多张表进行一起查询而且每张表的数据很大的话笛卡尔积就会变得非常大,对性能造成影响,想要去掉笛卡尔积我们需要关联查询。

在两张表中我们发现有一个共同的字段是depno,depno就是两张表的关联的字段,我们可以使用这个字段来做限制条件,两张表的关联查询字段一般是其中一张表的主键,另一张表的外键。

select * from emp e,dept d 
where e.deptno=d.deptno;

关联之后我们发现数据条数是14条,不在是56条。

范例:查询出每个员工编号,姓名,部门名称,工资等级和他的上级领导的姓名,工资等级

select e.empno,
       e.ename,
       decode(s.grade,
              1,'一级',
              2,'二级',
              3,'三级',
              4,'四级',
              5,'五级') grade,
       d.dname,
       e1.empno,
       e1.ename,
       decode(s1.grade,
              1,'一级',
              2,'二级',
              3,'三级',
              4,'四级',
              5,'五级') grade
  from emp e, emp e1, dept d, salgrade s, salgrade s1
 where e.mgr = e1.empno
   and e.deptno = d.deptno
   and e.sal between s.losal and s.hisal
   and e1.sal between s1.losal and s1.hisal

1.1.2外连接

当我们在做基本连接查询的时候,查询出所有的部门下的员工,我们发现编号为40的部门下没有员工,但是要求把该部门也展示出来,我们发现上面的基本查询是办不到的

使用(+)表示左连接或者右连接,当(+)在左边表的关联条件字段上时是左连接,也可以用left join,如果是在右边表的关联条件字段上就是右连接,也可以用right join。

范例:查询出所有员工的上级领导

分析:我们发现使用我们以前的做法发现KING的上级领导没有被展示,我们需要使用左右连接把他查询出来

select e.EMPNO,e.ename,m.empno,m.ename from emp e,emp m
where e.mgr=m.empno(+);
--或者
select e.EMPNO,e.ename,m.empno,m.ename from emp e left join emp m
on e.mgr=m.empno;
--结果
7902	FORD	7566	JONES
7900	JAMES	7698	BLAKE
7844	TURNER	7698	BLAKE
7654	MARTIN	7698	BLAKE
7521	WARD	7698	BLAKE
7499	ALLEN	7698	BLAKE
7934	MILLER	7782	CLARK
7782	CLARK	7839	KING
7698	BLAKE	7839	KING
7566	JONES	7839	KING
7369	SMITH	7902	FORD
7839	KING		

1.1.3 自连接

自连接是连接表与自身的连接。自连接对比较表中的行或查询分层数据非常有用。

自连接使用其他连接,如内连接和左连接。 另外,它使用表别名在同一查询中为表提供不同的名称。

请注意,在不使用表别名的情况下,在查询中多次引用同一个表会导致错误。

以下说明了表T如何与自身连接:

SELECT
    column_list
FROM
    T t1
INNER JOIN T t2 ON
    join_predicate;

请注意,除了INNER JOIN之外,还可以在上面的语句中使用LEFT JOIN。

1.2 分组统计

分组统计需要使用GROUP BY来分组

语法:语法:SELECT * |列名 FROM 表名 {WEHRE 查询条件} {GROUP BY 分组字段} ORDER BY 列名1 ASC|DESC,列名2…ASC|DESC

范例:查询每个部门的人数

select deptno,count(ename) from emp group by deptno;
--结果
30	6
20	3
10	3

注意:

1. 如果使用分组函数,SQL只可以把GOURP BY分组条件字段和分组函数查询出来,不能有其他字段。

2. 如果使用分组函数,不使用GROUP BY 只可以查询出来分组函数的值

范例:按部门分组,查询出部门名称和部门的员工数量

select d.DEPTNO,d.DNAME,count(ename) from emp e,dept d
where e.DEPTNO = d.DEPTNO
group by d.DEPTNO,d.DNAME;
--结果
10	ACCOUNTING	3
20	RESEARCH	3
30	SALES	6

范例:查询出部门人数大于5人的部门

分析:需要给count(ename)加条件,此时在本查询中不能使用where,可以使用HAVING

select d.DEPTNO,d.DNAME,count(ename) from emp e,dept d
where e.DEPTNO = d.DEPTNO
group by d.DEPTNO,d.DNAME
having count(ename)>5;

1.3 子查询

在一个查询的内部还包括另一个查询,则此查询称为子查询。

Sql的任何位置都可以加入子查询。

范例:查询比7654工资高的雇员

分析:查询出7654员工的工资是多少,把它作为条件

select * from emp t1
where t1.sal > (select t.sal from emp t where t.empno=7654);

所有的子查询必须在“()”中编写

子查询在操作中有三类:

单列子查询:返回的结果是一列的一个内容

单行子查询:返回多个列,有可能是一个完整的记录

多行子查询:返回多条记录

范例:查询出比雇员7654的工资高,同时从事和7788的工作一样的员工

select * from emp t1
where t1.sal > (select t.sal from emp t where t.empno=7654)
and t1.job=(select t2.job from emp t2 where t2.empno = 7788);

Insert中加入子查询

语法:

Insert into 表名(列1,列2,列3,….)

Select 列1,列2,列3,….

From 表名

Where条件

注意:

  1. 此时不要写values
  2. 插入的列名与查询的列名要一致

1.4 exists和not exists关键字

exists (sql 返回结果集为真)

not exists (sql 不返回结果集为真)

范例:查询出没有员工的部门

select * from dept d
where not exists (select * from emp e where e.deptno=d.deptno);

1.5 分页查询

ROWNUM:表示行号,实际上此是一个列,但是这个列是一个伪列,此列可以在每张表中出现。

范例:查询emp表带有rownum列

select rownum, t.* from emp t

我们可以根据rownum来取结果集的前几行,比如前5行

select rownum, t.* from emp t
where rownum<6;

但是我们不能取到中间几行,因为rownum不支持大于号,只支持小于号,如果想 实现我们的需求怎么办呢?答案是使用子查询,也正是oracle分页的做法。

第一种写法:

select *
  from (select rownum rm, a.* from (select * from emp) a where rownum < 11) b where b.rm > 5

第二种写法:

select * 
from (select rownum r ,emp.* from emp) b
where b.r >5 and b.r <11

1.6 视图

视图就是封装了一条复杂查询的语句。

语法1.:CREATE VIEW 视图名称 AS 子查询

范例:建立一个视图,此视图包括了20部门的全部员工信息

create view empvd20 as select * from emp t where t.deptno = 20

视图创建完毕就可以使用视图来查询,查询出来的都是20部门的员工

语法2:CREATE OR REPLACE VIEW 视图名称 AS 子查询

如果视图已经存在我们可以使用语法2来创建视图,这样已有的视图会被覆盖。

create or replace view empvd20 as select * from emp t where t.deptno = 20

修改视图但是发现是视图所查询的表的字段值被修改了。所以我们一般不会去修改视图。

我们可以设置视图为只读。

语法3:CREATE OR REPLACE VIEW 视图名称 AS 子查询 WITH READ ONLY

create or replace view empvd20 as select * from emp t where t.deptno = 20 with read only

1.7 索引

索引是用于加速数据存取的数据对象。合理的使用索引可以大大降低i/o 次数,从而

提高数据访问性能。索引有很多种我们主要介绍常用的几种:

为什么添加了索引之后,会加快查询速度呢?

图书馆:如果杂乱地放书的话检索起来就非常困难,所以将书分类,然后再建一个箱子,箱

子里面放卡片,卡片里面可以按类查询,按书名查或者类别查,这样的话速度会快很多很多,

这个就有点像索引。索引的好处就是提高你找到书的速度,但是正是因为你建了索引,就应该有人专门来维护索引,维护索引是要有时间精力的开销的,也就是说索引是不能乱建的,所以建索引有个原则:如果有一个字段如果不经常查询,就不要去建索引。现在把书变成我们的表,把卡片变成我们的索引,就知道为什么索引会快,为什么会有开销。

1.7.1 单列索引

单列索引是基于单个列所建立的索引,比如:

CREATE index 索引名 on 表名(列名)

1.7.2 复合索引

复合索引是基于两个列或多个列的索引。在同一张表上可以有多个索引,但是

要求列的组合必须不同,比如:

Create index emp_idx1 on emp(ename,job);

Create index emp_idx1 on emp(job,ename);

范例:给person表的name建立索引

create index pname_index on person(name);

范例:给person表创建一个name和gender的索引

create index pname_gender_index on person(name, gender);

1.7.3 索引的使用原则:

  • 在大表上建立索引才有意义
  • 在where子句后面或者是连接条件上的字段建立索引(经常使用的列才建索引)
  • 索引的层次不要超过4层

1.8 PL/SQL

1.8.1 PL/SQL简介

什么是PL/SQL?

PL/SQL(Procedure Language/SQL)

PLSQL是Oracle对sql语言的过程化扩展,指在SQL命令语言中增加了过程处理语句(如分支、循环等),使SQL语言具有过程处理能力。把SQL语言的数据操纵能力与过程语言的数据处理能力结合起来,使得PLSQL面向过程但比过程语言简单、高效、灵活和实用。

范例1:为职工涨工资,每人涨10%的工资。

update emp set sal=sal*1.1

范例2:例2: 按职工的职称长工资,总裁涨1000元,经理涨800元,其他人员涨400元。

这样的需求我们就无法使用一条SQL来实现,需要借助其他程序来帮助完成,也可以使用pl/sql。

1.8.2 语法

程序语法:

declare

      说明部分    (变量说明,游标申明,例外说明 〕 

begin

      语句序列   (DML语句〕… 

exception

      例外处理语句   

End;

分隔符是具有特殊含义的符号。以下是PL/SQL中的分隔符列表 -

分隔符 描述
+,-, *, / 加法,减法/负,乘法,除法
% 属性绑定
' 字符串分隔符
. 组件选择符
(,) 表达式或列表分隔符
: 主机变量指示符
, 项目分隔符
" 引用标识符分隔符
= 关系运算符
@ 远程访问指示符
; 声明或语句终止符
:= 赋值运算符
=> 关联运算符
|| 连接运算符
** 指数运算符
<<, >> 标签分隔符(开始和结束)
/*, */ 多行注释分隔符(开始和结束)
-- 单行注释指示符
.. 范围运算符
<, >, <=, >= 关系运算符
<>, '=, ~=, ^= 不同版本的”不等于”运算符

程序注释可以在编写的PL/SQL代码中包含的说明性文字,并帮助其他人阅读源代码。所有编程语言都允许某种形式的注释。

PL/SQL支持单行和多行注释。注释中的所有字符都被PL/SQL编译器忽略。 PL/SQL单行注释以分隔符开头 --(双连字符),多行注释由/**/括起来。

set serveroutput on;
DECLARE 
   -- variable declaration 
   message  varchar2(20):= 'Hello, World!'; 
BEGIN 
   /* 
   *  PL/SQL executable statement(s) 
   */ 
   dbms_output.put_line(message); 
END; 
/

当上述代码在SQL *Plus提示符下执行时,它会产生以下结果 -

Hello World

PL/SQL procedure successfully completed.

PL/SQL支持单行和多行注释。注释中的所有字符都被PL/SQL编译器忽略。 PL/SQL单行注释以分隔符开头 --(双连字符),多行注释由/**/括起来。

set serveroutput on;
DECLARE 
   -- variable declaration 
   message  varchar2(20):= 'Hello, World!'; 
BEGIN 
   /* 
   *  PL/SQL executable statement(s) 
   */ 
   dbms_output.put_line(message); 
END; 
/

当上述代码在SQL *Plus提示符下执行时,它会产生以下结果 -

Hello World
PL/SQL procedure successfully completed.

1.8.3 数据类型

PL/SQL变量,常量和参数必须具有有效的数据类型,它指定存储格式,约束和有效的值范围。本节将重点介绍SCALARLOB数据类型。其他两个数据类型(复合类型和引用类型)将在后面的章节中介绍。

  • 标量(SCALAR)类型 - 它是没有内部组件的单个值,例如:NUMBERDATEBOOLEAN等。
  • 大对象(LOB)类型 - 指向与其他数据项(例如:文本,图形图像,视频剪辑和声音波形)分开存储的大对象的指针。
  • 复合类型 - 具有可单独访问的内部组件的数据项。例如,集合和记录。
  • 引用类型 - 指向其他数据项。

PL/SQL标量数据类型和子类型分为以下几类:

序号 类型 描述
1 数字 执行算术运算的数值。
2 字符 表示单个字符或字符串的字母数字值。
3 布尔 执行逻辑运算的逻辑值。
4 日期时间 用于表示日期和时间的值

PL/SQL提供了数据类型的子类型。例如,NUMBER数据类型具有一个叫作INTEGER的子类型。 您可以使用PL/SQL程序中的子类型将数据类型与其他程序中的数据类型兼容,同时将PL/SQL代码嵌入到另一个程序(如Java程序)中。

下表列出了PL/SQL预定义的数字数据类型及其子类型 -

序号 类型 描述
1 PLS_INTEGER 带符号整数:-2,147,483,6482,147,483,647,以32位表示
2 BINARY_INTEGER 带符号整数:-2,147,483,6482,147,483,647,以32位表示
3 BINARY_FLOAT 单精度IEEE 754格式浮点数
4 BINARY_DOUBLE 双精度IEEE 754格式浮点数
5 NUMBER(prec, scale) 1E-130到(但不包括)1.0E126范围内的绝对值的定点或浮点数。NUMBER变量也可以表示0
6 DEC(prec, scale) ANSI特定定点类型,最大精度为38位十进制数字
7 DECIMAL(prec, scale) IBM具体定点类型,最大精度为38位十进制数字
8 NUMERIC(pre, secale) 浮点型,最大精度为38位十进制数
9 DOUBLE PRECISION ANSI特定浮点类型,最大精度为126位二进制数字(大约38位十进制数字)
10 FLOAT ANSI和IBM特定浮点类型,最大精度为126位二进制数字(大约38位十进制数字)
11 INT ANSI特定整数类型,最大精度为38位十进制数
12 INTEGER ANSI和IBM特定整数类型,最大精度为38位十进制数
13 SMALLINT ANSI和IBM特定整数类型,最大精度为38位十进制数
14 REAL 浮点型,最大精度为63位二进制数字(约十八位数)

以下是有效的声明 -

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

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

序号 类型 描述
1 CHAR 固定长度字符串,最大大小为32,767字节
2 VARCHAR2 最大大小为32,767字节的可变长度字符串
3 RAW 最大大小为32,767字节的可变长度二进制或字节字符串,不由PL/SQL解释
4 NCHAR 固定长度的国家字符串,最大大小为32,767字节
5 NVARCHAR2 可变长度的国家字符串,最大大小为32,767字节
6 LONG 最大长度为32,760字节的可变长度字符串
7 LONG RAW 最大大小为32,760字节的可变长度二进制或字节字符串,不由PL/SQL解释
8 ROWID 物理行标识符,普通表中的行的地址
9 UROWID 通用行标识符(物理,逻辑或外部行标识符)

BOOLEAN数据类型存储逻辑运算中使用的逻辑值。逻辑值为布尔值:TRUE,FALSE以及NULL值。

但是,SQL没有类似于BOOLEAN的数据类型。 因此,布尔值不能用于 -

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

DATE数据类型用于存储固定长度的数据日期时间,其包括自午夜以来以秒为单位的时间。 有效期为公元前4712年1月1日至公元9999年12月31日。

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

每个DATE类型的数据值包括世纪,年,月,日,时,分,秒。下表显示每个字段的有效值 -

字段名 有效的日期时间值 有效间隔值
YEAR -47129999(不包括第0年) 任意非零整数
MONTH 01 ~ 12 01 ~ 11
DAY 0131(限于MONTHYEAR的值,根据本地日历的规则) 任何非零整数
HOUR 00 ~ 23 00 ~ 23
MINUTE 00 ~ 59 00 ~ 59
SECOND 00 ~ 59.9(n),其中9(n)是时间分秒的精度 00 ~ 59.9(n),其中9(n)是间隔分数秒的精度
TIMEZONE_HOUR -1214(范围适应夏令时更改) 不适用
TIMEZONE_MINUTE 00 ~ 59 不适用
TIMEZONE_REGION 在动态性能视图V$TIMEZONE_NAMES找到 不适用
TIMEZONE_ABBR 在动态性能视图V$TIMEZONE_NAMES找到 不适用

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

数据类型 描述 大小
BFILE 用于在数据库外的操作系统文件中存储大型二进制对象。 取决于系统,但不得超过4GB
BLOB 用于在数据库中存储的大型二进制对象 8TB128TB
CLOB 用于在数据库中存储大字符数据。 8TB128TB
NCLOB 用于在数据库中存储大块NCHAR数据。 8TB128TB

类型是另一种数据类型的子集,它称为基本类型。子类型具有与其基本类型相同的操作,但只有基本类型有效值的子集。

PL/SQL预定义包STANDARD中的几个子类型。 例如,PL/SQL预先定义子类型CHARACTERINTEGER,如下所示:

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.

PL/SQL中的NULL值表示丢失或未知数据,它们不是整数,字符或任何其他特定数据类型。 请注意,NULL与空数据字符串或空字符值“\0”不同。可以将一个null值分配给其它变量,但不能等同于任何东西,包括其自身(null)。

1.8.4 变量定义

PL/SQL变量的名称由可选的字母,数字,美元($)符号,下划线和数字符号组成,不能超过30个字符。

必须在声明部分或包中声明PL/SQL变量作为全局变量。当声明一个变量时,PL/SQL为变量的值分配内存,并且存储位置由变量名称标识。

声明变量的语法是 -

variable_name [CONSTANT] datatype [NOT NULL] [:= | DEFAULT initial_value]

其中,variable_name是PL/SQL中的有效标识符,datatype必须是有效的PL/SQL数据类型或任何用户定义的数据类型,我们已在上一章中讨论过。一些有效的变量声明及其定义如下所示:

sales number(10, 2); 
pi CONSTANT double precision := 3.1415; 
name varchar2(25); 
address varchar2(100);

当使用数据类型提供了大小,比例或精度限制时,称为约束声明。有约束声明比无约束声明需要更少的内存。 例如 -

sales number(10, 2); 
name varchar2(25); 
address varchar2(100);

文字是一个不由标识符表示的显式数字,字符,字符串或布尔值。 例如,TRUE7788NULL'yiibai tutorials'分别是Booleannumberstring类型的文字。 PL/SQL,文字区分大小写。 PL/SQL支持以下几种文字 -

  • 数字文字
  • 字符文字
  • 字符串文字
  • 布尔文字
  • 日期和时间文字

下表提供了所有这些类别的文字值的示例。

序号 文字类型 示例
1 数字文字 2346,050 78 -14 0 +32767,6.6667 0.0 -12.0 3.14159 +7800.00,6E5 1.0E-8 3.14159e0 -1E38 -9.5e-3
2 字符文字 'A', '%', '9', ' ', 'z', '('
3 字符串文字 'Hello, world!','this is string','嗨喽'
4 布尔文字 TRUE, FALSE, NULL
5 日期和时间文字 '1998-08-25','2017-10-02 12:01:01'

要在字符串文字中嵌入单引号,请将两个单引号放在一起,如以下程序所示 -

DECLARE 
   message  varchar2(30):= 'What''s baidu.com!'; 
BEGIN 
   dbms_output.put_line(message); 
END; 
/

在程序的声明阶段可以来定义常量和变量。

  • 变量的基本类型就是ORACLE中的建表时字段的变量如char, varchar2, date, number, boolean, long

    定义语法:varl char(15);

Psal number(9,2);说明变量名、数据类型和长度后用分号结束说明语句。

​ 常量定义:married constant boolean:=true

  • 引用变量Myname emp.ename%type;引用型变量,即my_name的类型与emp表中ename列的类型一样,在sql中使用into来赋值

    declare emprec emp.ename%type;
    
    begin
    
      select t.ename into emprec from emp t where t.empno = 7369;
    
      dbms_output.put_line(emprec);
    
    end;
    
    

    将查询到的语句赋值给emprec

  • 记录型变量Emprec emp%rowtype 代表一行记录变量分量的引用

emp_rec.ename:='ADAMS'; 
declare
  p emp%rowtype;
begin
  select * into p from emp t where t.empno = 7369;
  dbms_output.put_line(p.ename || ' ' || p.sal);
end;

注意 := 赋值符号等价于java中的=号;

​ = 逻辑等,判断两个值是否相等,等价于java中的==号

1.8.5 运算符

PL/SQL语言中有丰富的内置运算符,提供有以下类型的运算符 -

  • 算术运算符
  • 关系运算符
  • 比较运算符
  • 逻辑运算符
  • 字符串运算符

下表显示了PL/SQL支持的所有算术运算符。假设变量A的值为10,变量B的值为5,那么 -

算术运算符示例

运算符 描述 示例
+ 两个操作数相加 A + B = 15
- 从第一个减去第二个操作数 A - B = 5
* 将两个操作数相乘 A * B = 50
/ 从第一个除以第二个操作数 A / B = 2
** 指数运算符,提出一个操作数到其他的幂值 A ** B = 100000

关系运算符

关系运算符比较两个表达式或值,并返回一个布尔结果。 下表显示了PL/SQL支持的所有关系运算符。假设变量A=10,变量B=20,则 -

运算符 描述 示例
= 检查两个操作数的值是否相等,如果是,则条件成立。 (A = B)为假
!=,<>~= 检查两个操作数的值是否相等,如果两个值不相等则条件成为真。 (A != B)为真
> 检查左操作数的值是否大于右操作数的值,如果是,则条件成为真。 (A > B) 为假
< 检查左操作数的值是否小于右操作数的值,如果是,则条件成为真。 (A < B) 条件为真。
>= 检查左操作数的值是否大于或等于右操作数的值,如果是,则条件成为真。 (A >= B) 为假
<= 检查左操作数的值是否小于或等于右操作数的值,如果是,则条件成为真。 (A <= B) 为真

比较运算符

比较运算符用于将一个表达式与另一个表达式作比较。结果始终为TRUEFALSENULL

运算符 描述 示例
LIKE LIKE运算符将字符,字符串或CLOB值与模式进行比较,如果值与模式匹配,则返回TRUE,否则返回FALSE 如果'Zara Ali' LIKE 'Z%A_i'返回一个布尔值true,而'Nuha Ali' LIKE'Z%A_i'返回一个布尔值。
BETWEEN BETWEEN运算符测试值是否在指定范围内。x BETWEEN a AND b表示x >= ax <= b 如果x = 10,那么在520之间则x返回truex510之间则x返回true,但是x1120之间返回false
IN IN运算符测试集成员数据。 x IN(set)表示x等于集合中的任何成员数据。 如果x ='m',则在('a','b','c')x返回false,而在('m','n','o')x返回true
IS NULL IS NULL运算符如果其操作数为NULL返回值为TRUE,如果不为NULL则返回FALSE。 涉及NULL值的比较总是产生NULL 如果x ='m',则is null'返回false

逻辑运算符

下表显示了PL/SQL支持的逻辑运算符。所有这些运算符都使用布尔运算符并产生布尔运算结果。假设变量A=TRUE,变量B=FALSE,那么 -

运算符 描述 示例
and 逻辑与运算符。如果两个操作数都为真,则条件成立。 (A and B) 为假
or 逻辑或运算符。如果两个操作数中的任何一个为真,则条件成为真。 (A or B)是真
not 逻辑非运算符。用于反转其操作数的逻辑状态。如果条件为真,则逻辑NOT运算符将使其为false (not A)结果为FALSE

PL/SQL运算符优先级

运算符优先级决定表达式中术语的分组。这会影响表达式的评估求值顺序。某些运算符的优先级高于其他运算符; 例如,乘法运算符的优先级高于加法运算符。

例如,x = 7 + 3 * 2; 这里,求值结果x的值为13,而不是20,因为运算符 *的优先级高于+,所以它首先被乘以3 * 2,然后再加上7

在这里,优先级最高的运算符出现在表的顶部,最底层的运算符出现在底部。在一个表达式中,将首先评估求值较高优先级的运算符。

运算符的优先级如下:=<><=>=<>!=〜=^=IS NULLLIKEBETWEENIN

运算符 操作描述
** 指数幂运算符
+, - 标识符,负数
*, / 乘法,除法
+, -, || 加,减,连接
NOT 逻辑否定
AND 连词(逻辑与)
OR 包含(逻辑或)

1.8.6 if分支

语法1IF   条件  THEN

 	   语句1;

       语句2; 

       END IF;

语法2IF  条件  THEN

  语句序列1****   

       ELSE   

语句序列 2****

       END   IF**** 

语法3IF   条件  THEN 语句;

ELSIF  条件  THEN  语句;

ELSIF  条件  THEN  语句;ELSE  语句;

END  IF; 

--像IF语句一样,CASE语句选择要执行的一个语句序列。 但是,要选择序列,CASE语句使用选择器而不是多个布尔表达式。选择器是一个表达式,其值用于选择几种替代方法之一。
CASE selector 
   WHEN 'value1' THEN S1; 
   WHEN 'value2' THEN S2; 
   WHEN 'value3' THEN S3; 
   ... 
   ELSE Sn;  -- default case 
END CASE;

--可搜索的CASE语句没有选择器,语句中的WHEN子句包含给出布尔值的搜索条件。
CASE 
   WHEN selector = 'value1' THEN S1; 
   WHEN selector = 'value2' THEN S2; 
   WHEN selector = 'value3' THEN S3; 
   ... 
   ELSE Sn;  -- default case 
END CASE;

范例1:如果从控制台输入1则输出我是1

accept num prompt ‘请输入一个数字’;
declare
  pnum number := &num;
begin
  if pnum = 1 then
    dbms_output.put_line('我是1');
  end if;
end;
--&代表地址符,意思是指向num的值
--接收的是字符串类型,此处会发生类型转换

范例3:判断人的不同年龄段18岁以下是未成年人,18岁以上40以下是成年人,40以上是老年人

accept num prompt ‘请输入一个年龄’;
declare
  mynum number := &num;
begin
  if mynum < 18 then
    dbms_output.put_line('未成年人');
  elsif mynum >= 18 and mynum < 40 then
    dbms_output.put_line('中年人');
  elsif mynum >= 40 then
    dbms_output.put_line('老年人');
  end if;
end;

1.8.7 loop循环

语法1WHILE  条件  LOOP
.. .
total : = total + salary;
END  LOOP; 

语法2Loop
EXIT  when   条件;
……
End loop

语法3:包括123
FOR   I   IN   1 . . 3    LOOP
语句序列 ;
END    LOOP ; 
--以下是PL/SQL for循环的一些特殊特性 -

--循环变量或计数器的initial_value和final_value可以是文字,变量或表达式,但必须对数字求值。 否则,PL/SQL引发预定义的异常VALUE_ERROR。
--initial_value不必为1; 但是,循环计数器增量(或减量)必须为1。
--PL/SQL允许在运行时动态地确定循环范围。


LOOP 
   Sequence of statements; 
END LOOP;
--这里,语句序列(Sequence of statements;)可以是单个语句或一组语句。需要一个EXIT语句或一个EXIT WHEN语句来中断循环。
SET SERVEROUTPUT ON SIZE 1000000;
DECLARE 
   x number := 10; 
BEGIN 
   LOOP 
      dbms_output.put_line(x); 
      x := x + 10; 
      IF x > 50 THEN 
         exit; 
      END IF; 
   END LOOP; 
   -- after exit, control resumes here  
   dbms_output.put_line('After Exit x is: ' || x); 
END; 


范例:使用语法1输出1到10的数字

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

1.8.8 字符串

PL/SQL中的字符串实际上是一个具有可选大小规格的字符序列。字符可以是数字,字母,空白,特殊字符或全部的组合。 PL/SQL提供三种字符串 -

  • 固定长度字符串 - 在这样的字符串中,程序员在声明字符串时指定长度。该字符串的右边填充规定的长度。
  • 可变长度字符串 - 在这样的字符串中,指定字符串的最大长度达32,767,并且不会填充。
  • 字符大对象(CLOB) - 这些可变长度字符串最多可达128TB

PL/SQL字符串可以是变量或文字。 字符串文字用引号括起来。 例如,

'This is a string literal.' 
--或者 
'hello world'

要在字符串文字中包含单引号,需要在彼此之间键入两个单引号。 例如,

'this isn''t what it looks like'

Oracle数据库提供了许多字符串数据类型,如:CHARNCHARVARCHAR2NVARCHAR2CLOBNCLOB。 以“N”为前缀的数据类型为“国家字符集”数据类型,用于存储Unicode字符数据。

如果需要声明一个可变长度的字符串,则必须提供该字符串的最大长度。例如,VARCHAR2数据类型。 以下示例说明声明和使用一些字符串变量 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   name varchar2(20); 
   company varchar2(30); 
   introduction clob; 
   choice char(1); 
BEGIN 
   name := 'Max Su'; 
   company := 'Hixiaoniu'; 
   introduction := ' Hello! I''m Max Su from Hixiaoniu.'; 
   choice := 'y'; 
   IF choice = 'y' THEN 
      dbms_output.put_line(name); 
      dbms_output.put_line(company); 
      dbms_output.put_line(introduction); 
   END IF; 
END; 
/

要声明一个固定长度的字符串,请使用CHAR数据类型。 在这里,不必为固定长度变量指定最大长度。 如果不考虑长度约束,Oracle数据库将自动使用所需的最大长度。以下两个声明是相同的 -

red_flag CHAR(1) := 'Y'; 
red_flag CHAR   := 'Y';

1.8.9 字符串函数

PL/SQL提供用于连接两个字符串的级联运算符(||)。 下表提供了PL/SQL提供的字符串函数 -

编号 函数 描述
1 ASCII(x); 返回字符x的ASCII值。
2 CHR(x); 返回ASCII值为x的字符。
3 CONCAT(x, y); 连接两个字符串xy,并返回连接后的字符串。
4 INITCAP(x); x中每个单词的初第一个字母转换为大写,并返回该字符串。
5 INSTR(x, find_string [, start] [, occurrence]); x字符串中搜索find_string子串并返回找到的位置。
6 INSTRB(x); 返回字符串x在另一个字符串中第一次再现的位置,但返回值(以字节为单位)。
7 LENGTH(x); 返回x中的字符数,也是计算字符串的长度。
8 LENGTHB(x); 返回单字节字符集的字符串长度(以字节为单位)。
9 LOWER(x); x字符串中的字母转换为小写,并返回此小写字符串。
10 LPAD(x, width [, pad_string]) ; 使用空格垫放在x字符串的左边,以使字符串的长度达到宽度字符。
11 LTRIM(x [, trim_string]); 修剪x字符串左边的字符。
12 NANVL(x, value); 如果x匹配NaN特殊值(而不是数字),则返回值,否则返回x字符串。
13 NLS_INITCAP(x); INITCAP(x)函数相同,只不过它可以使用NLSSORT指定的其他排序方法。
14 NLS_LOWER(x) ; LOWER(x)函数相同,除了可以使用NLSSORT指定的不同排序方法。
15 NLS_UPPER(x); UPPER()函数相同,除了可以使用NLSSORT指定的不同排序方法。
16 NLSSORT(x); 更改排序字符的方法。必须在任何NLS()函数之前指定; 否则,将使用默认排序。
17 NVL(x, value); 如果xnull则返回value值; 否则返回x
18 NVL2(x, value1, value2); 如果x不为null则返回值value1; 如果xnull,则返回value2
19 REPLACE(x, search_string, replace_string); x字符串中搜索search_string并将其替换为replace_string
20 RPAD(x, width [, pad_string]); 使用空格垫放在x字符串的右边,以使字符串的长度达到宽度字符。
21 RTRIM(x [, trim_string]); 从右边修剪x字符串。
22 SOUNDEX(x) ; 返回一个包含x的语音表示的字符串。
23 SUBSTR(x, start [, length]); 返回x字符串从指定start位置开始到一个可选指定长度(length)范围内的子字符串。
24 SUBSTRB(x); SUBSTR()相同,除了参数以字节表示,还支持单字节字符系统的字符。
25 TRIM([trim_char FROM) x); 修剪x字符串的左边和右边的字符。
26 UPPER(x); x中的字母转换为大写,并返回此大写后的字符串。

1.8.10 数组

PL/SQL编程语言提供了一种称为VARRAY的数据结构,它可以存储相同类型的元素的固定大小顺序集合。varray用于存储有序的数据集合,但通常最好将数组视为相同类型变量的集合。

所有varray是由连续的内存位置组成。最低的地址对应于第一个元素,而最后一个元素的地址最高。

数组是集合类型数据的一部分,表示可变大小的数组。 我们将在后面的“PL/SQL集合”这一章中学习其他集合类型。

varray中的每个元素都具有与之相关联的索引。它还具有可以动态更改的容量(大小)。

创建Varray类型

使用CREATE TYPE语句创建varray类型。必须指定存储在varray中的元素的最大容量(大小)和类型。

在模式(schema)级创建VARRAY类型的基本语法是 -

CREATE OR REPLACE TYPE varray_type_name IS VARRAY(n) of <element_type>

其中,

  • varray_type_name是一个有效的属性名称;
  • nvarray中元素的数量(最大值);
  • element_type是数组元素的数据类型。

可以使用ALTER TYPE语句更改变量的最大大小。

例如,

CREATE Or REPLACE TYPE namearray AS VARRAY(3) OF VARCHAR2(10); 

在PL/SQL块中创建VARRAY类型的基本语法是 -

TYPE varray_type_name IS VARRAY(n) of <element_type>

例如 -

TYPE namearray IS VARRAY(5) OF VARCHAR2(10); 
Type grades IS VARRAY(5) OF INTEGER;

实例-1

以下程序说明了如何使用varrays -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   type namesarray IS VARRAY(5) OF VARCHAR2(10); 
   type grades IS VARRAY(5) OF INTEGER; 
   names namesarray; 
   marks grades; 
   total integer; 
BEGIN 
   names := namesarray('Kavita', 'Pritam', 'Ayan', 'Rishav', 'Aziz'); 
   marks:= grades(98, 97, 78, 87, 92); 
   total := names.count; 
   dbms_output.put_line('Total '|| total || ' Students'); 
   FOR i in 1 .. total LOOP 
      dbms_output.put_line('Student: ' || names(i) || ' 
      Marks: ' || marks(i)); 
   END LOOP; 
END; 
/

请注意 -

  • 在Oracle环境中,varrays的起始索引始终为1
  • 可以使用varray类型的构造方法初始化varray元素,该方法与varray具有相同的名称。
  • varrays是一维数组。
  • varray在声明时自动为NULL,并且必须在引用元素之前初始化它。

示例-2

变量的元素也可以是任何数据库表的%ROWTYPE或任何数据库表字段的%TYPE表来引用表示。 以下示例说明了这个概念。

将使用数据库中存储的CUSTOMERS表,结构和数据如下所示 -

CREATE TABLE customers
( id number(10) NOT NULL,
  name varchar2(50) NOT NULL,
  age number(2) NOT NULL,
  address varchar2(50),
  salary float(2) NOT NULL,
  CONSTRAINT customers_pk PRIMARY KEY (id)
);

INSERT INTO customers (id,name,age,address,salary) VALUES(1, '罗大牛',32,'北京', 22999.00);
INSERT INTO customers (id,name,age,address,salary) VALUES(2, 'Maxsu',25,'海口', 5999.00);
INSERT INTO customers (id,name,age,address,salary) VALUES(3, 'Hinew',22,'广州', 9800.98);
INSERT INTO customers (id,name,age,address,salary) VALUES(4, '李小路',26,'北京', 18700.00);
INSERT INTO customers (id,name,age,address,salary) VALUES(5, '张友德',28,'上海', 18999.00);
INSERT INTO customers (id,name,age,address,salary) VALUES(6, '李连定',42,'深圳', 32999.00);

以下示例使用游标引用。参考以下代码 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   CURSOR c_customers is 
   SELECT  name FROM customers; 
   type c_list is varray (6) of customers.name%type; 
   name_list c_list := c_list(); 
   counter integer :=0; 
BEGIN 
   FOR n IN c_customers LOOP 
      counter := counter + 1; 
      name_list.extend; 
      name_list(counter)  := n.name; 
      dbms_output.put_line('Customer('||counter ||'):'||name_list(counter)); 
   END LOOP; 
END; 
/

1.8.11游标

在写java程序中有集合的概念,那么在pl/sql中也会用到多条记录,这时候我们就要用到游标,游标可以存储查询返回的多条数据。

语法:

​ 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列类型一致:

​ 定义:pjob emp.empjob%type;

范例1:使用游标方式输出emp表中的员工编号和姓名

declare
  cursor pc is
    select * from emp;
  pemp emp%rowtype;
begin
  open pc;
  loop
    fetch pc
      into pemp;
    exit when pc%notfound;
    dbms_output.put_line(pemp.empno || ' ' || pemp.ename);
  end loop;
  close pc;
end;

范例2:写一段PL/SQL程序,为部门号为10的员工涨工资。

declare
  cursor pc(dno myemp.deptno%type) is
    select empno from myemp where deptno = dno;
  pno myemp.empno%type;
begin
  open pc(10);
  loop
    fetch pc
      into pno;
    exit when pc%notfound;
    update myemp t set t.sal = t.sal + 1000 where t.empno = pno;
  end loop;
  close pc;
end;

1.8.12异常

异常是程序设计语言提供的一种功能,用来增强程序的健壮性和容错性。

系统定义异常 :

  • no_data_found (没有找到数据)
  • too_many_rows (select …into语句匹配多个行)
  • zero_divide ( 被零除)
  • value_error (算术或转换错误)
  • timeout_on_resource (在等待资源时发生超时)

预定义的异常

PL/SQL提供了许多预定义的异常,这些异常在程序违反任何数据库规则时执行。 例如,当SELECT INTO语句不返回任何行时,会引发预定义的异常NO_DATA_FOUND。下表列出了一些重要的预定义异常情况 -

异常 Oracle错误代码 SQLCODE 描述
ACCESS_INTO_NULL 06530 -6530 当一个空对象被自动分配一个值时会引发它。
CASE_NOT_FOUND 06592 -6592 当没有选择CASE语句的WHEN子句中的任何选项时,会引发这个错误,并且没有ELSE子句。
COLLECTION_IS_NULL 06531 -6531 当程序尝试将EXISTS以外的集合方法应用于未初始化的嵌套表或varray时,或程序尝试将值分配给未初始化的嵌套表或varray的元素时,会引发此问题。
DUP_VAL_ON_INDEX 00001 -1 当尝试将重复值存储在具有唯一索引的列中时引发此错误。
INVALID_CURSOR 01001 -1001 当尝试进行不允许的游标操作(例如关闭未打开的游标)时会引发此错误。
INVALID_NUMBER 01722 -1722 当字符串转换为数字时失败,因为字符串不代表有效的数字。
LOGIN_DENIED 01017 -1017 当程序尝试使用无效的用户名或密码登录到数据库时引发。
NO_DATA_FOUND 01403 +100 SELECT INTO语句不返回任何行时会引发它。
NOT_LOGGED_ON 01012 -1012 当数据库调用没有连接到数据库时引发。
PROGRAM_ERROR 06501 -6501 当PL/SQL遇到内部问题时会引发。
ROWTYPE_MISMATCH 06504 -6504 当游标在具有不兼容数据类型的变量中获取值时引发。
SELF_IS_NULL 30625 -30625 当调用成员方法时引发,但对象类型的实例未初始化。
STORAGE_ERROR 06500 -6500 当PL/SQL用尽内存或内存已损坏时引发。
TOO_MANY_ROWS 01422 -1422 SELECT INTO语句返回多行时引发。
VALUE_ERROR 06502 -6502 当发生算术,转换,截断或者sizeconstraint错误时引发。
ZERO_DIVIDE 01476 1476 当尝试将数字除以零时引发。

范例1:写出被0除的异常的plsql程序

declare
  pnum number;
begin
  pnum := 1 / 0;
exception
  when zero_divide then
    dbms_output.put_line('被0除');
  when value_error then
    dbms_output.put_line('数值转换错误');
  when others then
    dbms_output.put_line('其他错误');
end;

用户也可以自定义异常,在声明中来定义异常

DECLARE

My_job   char(10);

v_sal   emp.sal%type;

No_data    exception;

cursor c1 is select distinct job from emp    order by job;

如果遇到异常我们要抛出raise no_data;

范例2:查询部门编号是50的员工

declare
  no_emp_found exception;
  cursor pemp is
    select t.ename from emp t where t.deptno = 50;
  pename emp.ename%type;
begin
  open pemp;
  fetch pemp
    into pename;
  if pemp%notfound then
    raise no_emp_found;
  end if;
  close pemp;
exception
  when no_emp_found then
    dbms_output.put_line('没有找到员工');
  when others then
    dbms_output.put_line('其他错误');
end;

1.8.13 存储过程

存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应用程序都应该用到存储过程。

子程序是执行特定任务的程序单元/模块。 这些子程序组合起来形成更大的程序。这种做法被称为“模块化设计”。 子程序可以被称为调用程序的另一个子程序或程序调用。

可以在以下几个地方中创建一个子程序 -

  • 在模式(schema)级别中
  • 一个程序包中
  • 在PL/SQL块中

在模式(schema)级别中,子程序是一个独立的子程序。它是使用CREATE PROCEDURECREATE FUNCTION语句创建的。它存储在数据库中,可以使用DROP PROCEDUREDROP FUNCTION语句进行删除。

在包中创建的子程序是打包的子程序。它存储在数据库中,只有当使用DROP PACKAGE语句删除程序包时,才能将其删除。我们将在“PL/SQL程序包”一章中讨论程序包的应用。

PL/SQL子程序被命名为可以使用一组参数调用的PL/SQL块。 PL/SQL提供两种子程序 -

  • 函数 - 这些子程序返回单个值; 主要用于计算和返回值。
  • 存储过程(程序) - 这些子程序不直接返回值; 主要用于执行动作。

创建存储过程语法:

create [or replace] PROCEDURE 过程名[(参数名 in/out 数据类型)]  
AS 
begin
        PLSQL子程序体;
End;

或者

create [or replace] PROCEDURE 过程名[(参数名 in/out 数据类型)]  
is
begin
        PLSQL子程序体;
End  过程名;


CREATE [OR REPLACE] PROCEDURE procedure_name 
[(parameter_name [IN | OUT | IN OUT] type [, ...])] 
{IS | AS} 
BEGIN 
  < procedure_body > 
END procedure_name;
--其中,procedure-name是要创建的存储过程的名称。
--[OR REPLACE]选项允许修改现有的过程。
--可选参数列表包含参数的名称,模式和类型。IN表示将从外部传递的值,OUT表示将用于返回过程外的值的参数。
--procedure-body包含可执行部分。
--使用AS关键字而不是IS关键字来创建存储过程。

下表列出了PL/SQL子程序中的参数模式 -

编号 参数模式 描述
1 IN IN参数允许将值传递给子程序。它是一个只读参数。在子程序中,IN参数的作用如常数,它不能被赋值。可以将常量,文字,初始化的变量或表达式作为IN参数传递。也可以将其初始化为默认值; 然而,在这种情况下,从子程序调用中省略它。 它是参数传递的默认模式。参数通过引用传递。
2 OUT OUT参数返回一个值给调用程序。在子程序中,OUT参数像变量一样。 可以更改其值并在分配该值后引用该值。实际参数必须是可变的,并且通过值传递。
3 IN OUT IN OUT参数将初始值传递给子程序,并将更新的值返回给调用者。 它可以分配一个值,该值可以被读取。对应于IN OUT形式参数的实际参数必须是变量,而不是常量或表达式。正式参数必须分配一个值。实际参数(实参)通过值传递。

传递参数的方法

实际参数(实参)可以通过三种方式传递 -

  • 位置符号
  • 命名符号
  • 混合符号

位置符号

在位置符号中,可以调用存储过程如下 -

findMin(a, b, c, d);

在位置符号中,第一个实际参数代替第一个形式参数; 第二个实际参数代替第二个形式参数,依此类推。 因此,a代替xb代替yc代替zd代替m

命名符号

在命名符号中,实际参数与使用箭头符号(=>)的形式参数相关联。调用存储过程如下所示 -

findMin(x => a, y => b, z => c, m => d);

混合符号

在混合符号表示中,可以在过程调用中混合使用符号; 然而,位置符号应在命名符号之前。

以下调用存储过程的方式是合法的 -

findMin(a, b, c, m => d);

但是,以下这种是不合法的:

findMin(x => a, b, c, d);

范例1:给指定的员工涨100工资,并打印出涨前和涨后的工资

分析:我们需要使用带有参数的存储过程

create or replace procedure addSal1(eno in number) is
  pemp myemp%rowtype;
begin
  select * into pemp from myemp where empno = eno;
  update myemp set sal = sal + 100 where empno = eno;
  dbms_output.put_line('涨工资前' || pemp.sal || '涨工资后' || (pemp.sal + 100));
end addSal1;

调用

begin
  -- Call the procedure
  addsal1(eno => 7902);     
  commit;
end;
或者
EXECUTE addsal1(eno => 7902);   

使用DROP PROCEDURE语句删除独立存储过程。删除程序的语法是 -

DROP PROCEDURE procedure-name;

1.8.14 存储函数

存储过程和存储函数的区别

一般来讲,过程和函数的区别在于函数可以有一个返回值;而过程没有返回值。

但过程和函数都可以通过out指定一个或多个输出参数。我们可以利用out参数,在过程和函数中实现返回多个值。

create or replace function 函数名(Name in type, Name out type, ...) return 数据类型 is
  结果变量 数据类型;
begin
  return(结果变量);
end[函数名];
或者
CREATE [OR REPLACE] FUNCTION function_name 
[(parameter_name [IN | OUT | IN OUT] type [, ...])] 
RETURN return_datatype 
{IS | AS} 
BEGIN 
   < function_body > 
END [function_name];

其中,

  • function-name是指定要创建的函数的名称。
  • *[OR REPLACE]*选项指示是否允许修改现有的函数。
  • 可选参数列表包含参数的名称,模式和类型。 IN表示将从外部传递的值,OUT表示将用于返回过程外的值的参数。
  • 函数必须包含一个返回(RETURN)语句。
  • RETURN子句指定要从函数返回的数据类型。
  • function-body包含可执行部分。
  • 使用AS关键字代替IS关键字,用来创建独立的函数

范例:使用存储函数来查询指定员工的年薪

create or replace function empincome(eno in emp.empno%type) return number is
  psal  emp.sal%type;
  pcomm emp.comm%type;
begin
  select t.sal into psal from emp t where t.empno = eno;
  return psal * 12 + nvl(pcomm, 0);
end;

使用存储过程来替换上面的例子

create or replace procedure empincomep(eno in emp.empno%type, income out number) is
  psal emp.sal%type;
  pcomm emp.comm%type;
begin
  select t.sal, t.comm into psal, pcomm from emp t where t.empno = eno;
  income := psal*12+nvl(pcomm,0);
end empincomep;

调用:

declare
  income number;
begin
  empincomep(7369, income);
  dbms_output.put_line(income);
end;

PL/SQL递归函数

我们在前面已经看到程序或子程序可能会调用另一个子程序。当子程序调用自身时,它被称为递归调用,该过程称为递归。

为了更好地说明递归这个概念,让我们来看看计算一个给定数字的阶乘示例。 数字n的因子被定义为 -

n! = n*(n-1)! 
   = n*(n-1)*(n-2)! 
      ... 
   = n*(n-1)*(n-2)*(n-3)... 1

以下过程是通过递归调用本身来计算给定数字的阶乘 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   num number; 
   factorial number;  

FUNCTION fact(x number) 
RETURN number  
IS 
   f number; 
BEGIN 
   IF x=0 THEN 
      f := 1; 
   ELSE 
      f := x * fact(x-1); 
   END IF; 
RETURN f; 
END;  

BEGIN 
   num:= 10; 
   factorial := fact(num); 
   dbms_output.put_line(' 数字 '|| num || ' 的阶乘积是: ' || factorial); 
END; 
/

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

数字 10 的阶乘积是: 3628800

PL/SQL 过程已成功完成。

1.8.15 游标

Oracle创建一个称为上下文区域的内存区域,用于处理SQL语句,它包含处理该语句所需的所有信息; 例如,处理的行数等。

游标是指向此上下文区域的指针。PL/SQL通过游标控制上下文区域,游标保存SQL语句返回的行(一个或多个)。 游标所在的行集称为活动集。

可以命名一个游标,以便在程序中引用它来获取和处理SQL语句返回的行,一次处理一个(行)。PL/SQL中有两种类型的游标 -

  • 隐式游标
  • 显式游标

隐式游标

当执行SQL语句时,如果语句没有显式游标,则Oracle会自动创建隐式游标。程序员无法控制隐式游标及其信息。

每当发出DML语句(INSERTUPDATEDELETE)时,隐式游标与此语句相关联。 对于INSERT操作,游标保存需要插入的数据。对于UPDATEDELETE操作,游标标识将受到影响的行。

在PL/SQL中,可以将最近的隐式游标引用为SQL游标,它始终具有%FOUND%ISOPEN%NOTFOUND%ROWCOUNT等属性。 SQL游标具有额外的属性%BULK_ROWCOUNT%BULK_EXCEPTIONS,旨在与FORALL语句一起使用。下表提供了游标中最常用属性的描述 -

编号 属性 描述
1 %FOUND 如果INSERTUPDATEDELETE语句影响一行或多行,或老兄SELECT INTO语句返回一行或多行,则返回TRUE,否则返回FALSE
2 %NOTFOUND %FOUND的逻辑相反。 如果INSERT,UPDATE或DELETE语句没有影响任何行,或SELECT INTO语句未返回任何行,则返回TRUE。 否则返回FALSE。
3 %ISOPEN 由于Oracle在执行关联的SQL语句后会自动关闭SQL游标,因此总是为隐式游标返回FALSE
4 %ROWCOUNT 返回受INSERTUPDATEDELETE语句,或者受SELECT INTO语句影响的行数。

任何SQL游标属性将被访问为sql%attribute_name,如下例所示。

示例

这里将使用CUSTOMERS表,表结构和数据参考:

CREATE TABLE CUSTOMERS( 
   ID   INT NOT NULL, 
   NAME VARCHAR (20) NOT NULL, 
   AGE INT NOT NULL, 
   ADDRESS CHAR (25), 
   SALARY   DECIMAL (18, 2),        
   PRIMARY KEY (ID) 
);

-- 插入数据
INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (1, 'Ramesh', 32, 'Ahmedabad', 2000.00 );  

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (2, 'Khilan', 25, 'Delhi', 1500.00 );  

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (3, 'kaushik', 23, 'Kota', 2000.00 );

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (4, 'Chaitali', 25, 'Mumbai', 6500.00 ); 

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (5, 'Hardik', 27, 'Bhopal', 8500.00 );  

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (6, 'Komal', 22, 'MP', 4500.00 );

以下程序将表中每个客户的工资增加500,并使用SQL%ROWCOUNT属性来确定受影响的行数 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE  
   total_rows number(2); 
BEGIN 
   UPDATE customers 
   SET salary = salary + 500; 
   IF sql%notfound THEN 
      dbms_output.put_line('没有找到客户信息~'); 
   ELSIF sql%found THEN 
      total_rows := sql%rowcount;
      dbms_output.put_line('一共有:' || total_rows || ' 个客户的工资被更新! '); 
   END IF;  
END; 
/

显式游标

显式游标是用于获得对上下文区域的更多控制的程序员定义的游标。应在PL/SQL块的声明部分中定义一个显式游标。它是在一个返回多行的SELECT语句中创建的。

创建显式游标的语法是 -

CURSOR cursor_name IS select_statement;

使用显式游标包括以下步骤 -

  • 声明游标初始化内存
  • 打开游标分配内存
  • 从游标获取数据
  • 关闭游标以释放分配的内存

声明游标

声明游标使用名称和相关的1SELECT1语句来定义游标。 例如 -

CURSOR c_customers IS 
   SELECT id, name, address FROM customers;

打开游标

打开游标将为游标分配内存,并使其准备好将SQL语句返回的行记录数据提取到其中。例如,打开上面定义的游标,如下所示:

OPEN c_customers;

获取游标
获取游标一次仅访问一行。 例如,从上面打开的游标中获取行,如下所示代码:

FETCH c_customers INTO c_id, c_name, c_addr;

关闭游标

关闭游标意味着释放分配的内存。例如,关闭上面打开的游标,如下所示:

CLOSE c_customers;

示例

以下是一个完整的例子来说明显式游标的概念。

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   c_id customers.id%type; 
   c_name customers.name%type; 
   c_addr customers.address%type; 
   CURSOR c_customers is 
      SELECT id, name, address FROM customers; 
BEGIN 
   OPEN c_customers; 
   LOOP 
   FETCH c_customers into c_id, c_name, c_addr; 
      EXIT WHEN c_customers%notfound; 
      dbms_output.put_line(c_id || ' ' || c_name || ' ' || c_addr); 
   END LOOP; 
   CLOSE c_customers; 
END; 
/

1.8.16 记录

记录是可以容纳不同种类的数据项的数据结构。 记录由不同的字段组成,类似于数据库表的一行。

例如,想要在图书馆中跟踪记录图书信息。可能希望跟踪每本书的以下属性,例如标题,作者,主题,图书ID。 包含每个这些项目的字段的记录允许将图书视为逻辑单元,并允许以更好的方式组织和表示其信息。

PL/SQL可以处理以下类型的记录 -

  • 基于表的记录
  • 基于游标的记录
  • 用户定义的记录

基于表的记录

%ROWTYPE属性使程序员能够创建基于表和基于游标的记录。
以下示例说明了基于表的记录的概念。这里将使用前面章节中创建和使用的customers表,表结构和数据如下 -

CREATE TABLE CUSTOMERS( 
   ID   INT NOT NULL, 
   NAME VARCHAR (20) NOT NULL, 
   AGE INT NOT NULL, 
   ADDRESS CHAR (25), 
   SALARY   DECIMAL (18, 2),        
   PRIMARY KEY (ID) 
);
-- 插入示例数据
INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (1, 'Ramesh', 32, 'Ahmedabad', 2000.00 );  

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (2, 'Khilan', 25, 'Delhi', 1500.00 );  

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (3, 'kaushik', 23, 'Kota', 2000.00 );

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (4, 'Chaitali', 25, 'Mumbai', 6500.00 ); 

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (5, 'Hardik', 27, 'Bhopal', 8500.00 );  

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (6, 'Komal', 22, 'MP', 4500.00 );

使用表记录示例代码 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   customer_rec customers%rowtype; 
BEGIN 
   SELECT * into customer_rec 
   FROM customers 
   WHERE id = 5;  
   dbms_output.put_line('客户ID: ' || customer_rec.id); 
   dbms_output.put_line('客户姓名: ' || customer_rec.name); 
   dbms_output.put_line('客户地址: ' || customer_rec.address); 
   dbms_output.put_line('客户薪资: ' || customer_rec.salary); 
END; 
/

基于游标的记录

以下示例说明了基于游标的记录的概念,下面将使用在前面创建和使用的CUSTOMERS表,参考示例代码如下 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   CURSOR customer_cur is 
      SELECT id, name, address  
      FROM customers; 
   customer_rec customer_cur%rowtype; 
BEGIN 
   OPEN customer_cur; 
   LOOP 
      FETCH customer_cur into customer_rec; 
      EXIT WHEN customer_cur%notfound; 
      DBMS_OUTPUT.put_line(customer_rec.id || ' ' || customer_rec.name); 
   END LOOP; 
END; 
/

用户定义的记录

PL/SQL提供了一个用户定义的记录类型,允许程序员定义不同的记录结构。这些记录由不同的字段组成。假设要跟踪记录图书信息,例如可能要跟踪每本书的以下属性 -

  • 标题
  • 作者
  • 学科
  • 图书ID

定义一个记录

记录类型被定义为如下 -

TYPE 
type_name IS RECORD 
  ( field_name1  datatype1  [NOT NULL]  [:= DEFAULT EXPRESSION], 
   field_name2   datatype2   [NOT NULL]  [:= DEFAULT EXPRESSION], 
   ... 
   field_nameN  datatypeN  [NOT NULL]  [:= DEFAULT EXPRESSION); 
record-name  type_name;

图书(Book)记录按以下方式声明 -

DECLARE 
TYPE books IS RECORD 
(
   title  varchar(50), 
   author  varchar(50), 
   subject varchar(100), 
   book_id   number
); 
book1 books; 
book2 books;

访问字段

要访问记录的任何字段,可通过使用点(.)运算符。成员访问操作符被编码为记录变量名称和希望访问的字段。看盾以下一个例子中如何使用记录 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   type books is record 
      (title varchar(50), 
      author varchar(50), 
      subject varchar(100), 
      book_id number); 
   book1 books; 
   book2 books; 
BEGIN 
   -- Book 1 specification 
   book1.title  := 'C Programming'; 
   book1.author := 'TanHao';  
   book1.subject := 'C Programming Tutorial'; 
   book1.book_id := 1920122;  
   -- Book 2 specification 
   book2.title := 'Telecom Billing'; 
   book2.author := 'LiDawei'; 
   book2.subject := 'Telecom Billing Tutorial'; 
   book2.book_id := 2032942;  

  -- Print book 1 record 
   dbms_output.put_line('Book 1 title : '|| book1.title); 
   dbms_output.put_line('Book 1 author : '|| book1.author); 
   dbms_output.put_line('Book 1 subject : '|| book1.subject); 
   dbms_output.put_line('Book 1 book_id : ' || book1.book_id); 

   -- Print book 2 record 
   dbms_output.put_line('Book 2 title : '|| book2.title); 
   dbms_output.put_line('Book 2 author : '|| book2.author); 
   dbms_output.put_line('Book 2 subject : '|| book2.subject); 
   dbms_output.put_line('Book 2 book_id : '|| book2.book_id); 
END; 
/

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

Book 1 title : C Programming
Book 1 author : TanHao
Book 1 subject : C Programming Tutorial
Book 1 book_id : 1920122
Book 2 title : Telecom Billing
Book 2 author : LiDawei
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 2032942

PL/SQL 过程已成功完成。

将记录作为子程序参数

可以像传递任何其他变量一样将记录作为子程序参数传递。还可以像访问上面的示例一样访问记录字段,参考下示例代码 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   type books is record 
      (title  varchar(50), 
      author  varchar(50), 
      subject varchar(100), 
      book_id   number); 
   book1 books; 
   book2 books;  
PROCEDURE printbook (book books) IS 
BEGIN 
   dbms_output.put_line ('Book  title :  ' || book.title); 
   dbms_output.put_line('Book  author : ' || book.author); 
   dbms_output.put_line( 'Book  subject : ' || book.subject); 
   dbms_output.put_line( 'Book book_id : ' || book.book_id); 
END; 

BEGIN 
   -- Book 1 specification 
   book1.title  := 'C Programming'; 
   book1.author := 'Haoqiang Tang';  
   book1.subject := 'C Programming Tutorial'; 
   book1.book_id := 8321407;

   -- Book 2 specification 
   book2.title := 'Telecom Billing'; 
   book2.author := 'Maxsu'; 
   book2.subject := 'Telecom Billing Tutorial'; 
   book2.book_id := 921300;  

   -- Use procedure to print book info 
   printbook(book1); 
   printbook(book2); 
END; 
/

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

Book  title :  C Programming
Book  author : Haoqiang Tang
Book  subject : C Programming Tutorial
Book book_id : 8321407
Book  title :  Telecom Billing
Book  author : Maxsu
Book  subject : Telecom Billing Tutorial
Book book_id : 921300

PL/SQL 过程已成功完成。

1.9 触发器

数据库触发器是一个与表相关联的、存储的PL/SQL程序。每当一个特定的数据操作语句(Insert,update,delete)在指定的表上发出时,Oracle自动地执行触发器中定义的语句序列。

1.9.1 触发器作用

  • 数据确认
  • 实施复杂的安全性检查
  • 做审计,跟踪表上所做的数据操作等
  • 数据的备份和同步

1.9.2 触发器类型

语句级触发器 :在指定的操作语句操作之前或之后执行一次,不管这条语句影响了多少行 。

行级触发器(FOR EACH ROW) :触发语句作用的每一条记录都被触发。在行级触发器中使用old和new伪记录变量, 识别值的状态。

语法:

CREATE [OR REPLACE ] TRIGGER trigger_name  
{BEFORE | AFTER | INSTEAD OF }  
{INSERT [OR] | UPDATE [OR] | DELETE}  
[OF col_name]  
ON table_name  
[REFERENCING OLD AS o NEW AS n]  
[FOR EACH ROW]  
WHEN (condition)   
DECLARE 
   Declaration-statements 
BEGIN  
   Executable-statements 
EXCEPTION 
   Exception-handling-statements 
END;



其中,

  • CREATE [OR REPLACE] TRIGGER trigger_name - 使用trigger_name创建或替换现有的触发器。
  • {BEFORE | AFTER | INSTEAD OF} - 指定何时执行触发器。INSTEAD OF子句用于在视图上创建触发器。
  • {INSERT [OR] | UPDATE [OR] | DELETE} - 这指定了DML操作。
  • [OF col_name] − 这指定了将要更新的列名称。
  • [ON table_name] - 这指定了与触发器关联的表的名称。
  • [REFERENCING OLD AS o NEW AS n] - 这允许各种DML语句(如INSERTUPDATEDELETE)引用新值和旧值。
  • [FOR EACH ROW] - 这指定了一个行级别的触发器,即触发器将被执行的每一行受到影响。否则触发器将在执行SQL语句时执行一次,这称为表级触发器。
  • WHEN(condition) - 这为触发器触发的行提供了一个条件。该子句仅对行级触发器有效。

范例:插入员工后打印一句话“一个新员工插入成功”

create or replace trigger testTrigger
  after insert on person  
declare
  -- local variables here
begin
  dbms_output.put_line('一个员工被插入');
end testTrigger;

范例:不能在休息时间插入员工

create or replace trigger validInsertPerson
  before insert on person

declare
  weekend varchar2(10);
begin
  select to_char(sysdate, 'day') into weekend from dual;
  if weekend in ('星期一') then
    raise_application_error(-20001, '不能在非法时间插入员工');
  end if;
end validInsertPerson;

在触发器中触发语句与伪记录变量的值

触发语句 :old :new
Insert 所有字段都是空(null) 将要插入的数据
Update 更新以前该行的值 更新后的值
delete 删除以前该行的值 所有字段都是空(null)

范例:判断员工涨工资之后的工资的值一定要大于涨工资之前的工资 "

create or replace trigger addsal4p
  before update of sal on myemp
  for each row
begin
  if :old.sal >= :new.sal then
    raise_application_error(-20002, '涨前的工资不能大于涨后的工资');
  end if;
end;


调用 update myemp t set t.sal = t.sal - 1;

1.9.3 触发器实际应用

需求:使用序列,触发器来模拟mysql中自增效果

创建序列

  1. 建立表

    create table user  
    
    (   
    
        id   number(6) not null,   
    
        name   varchar2(30)   not null primary key  
    
    )  
    
    
  2. 建立序列SEQUENCE

    create sequence user_seq increment by 1 start with 1 minvalue 1 maxvalue 9999999999999 nocache order;
    

创建自增的触发器

分析:创建一个基于该表的before insert 触发器,在触发器中使用刚创建的SEQUENCE。

代码如下:

create or replace trigger user_trigger   
before insert on user  
for each row   
begin  
      select   user_seq.nextval  into:new.id from sys.dual ;   
end;  

测试效果 l

insert into itcastuser(name) values('aa');
commit;
insert into itcastuser(name) values('bb');
commit;

1.10 包

包是模式对象,将逻辑上相关的PL/SQL类型,变量和子程序分组。

一个包将有两个强制性的部分 -

  • 包规范/格式
  • 包体或定义

1.10.1包规范

规范是包的接口。它只是声明可以从包外部引用的类型,变量,常量,异常,游标和子程序。 换句话说,它包含有关包的内容的所有信息,但不包括子程序的代码。

所有放置在规范中的对象被称为公共对象。任何不在包规范中但在包体中编码的子程序称为私有对象。

以下代码片段显示了包含单个过程的包规范。可以在一个包中定义许多全局变量和多个过程或函数。

SET SERVEROUTPUT ON SIZE 99999;
CREATE PACKAGE cust_sal AS 
   PROCEDURE find_sal(c_id customers.id%type); 
END cust_sal; 
/

1.10.2包体

包体具有包规范中声明的各种方法代码和其他私有声明,这些声明对包之外的代码是隐藏的。

CREATE PACKAGE BODY语句用于创建包体。以下代码片段显示了上面创建cust_sal包的包体声明。假定已经在数据库中创建了CUSTOMERS表,有关customers表的结构和数据,可参考以下SQL语句 -

CREATE TABLE CUSTOMERS( 
   ID   INT NOT NULL, 
   NAME VARCHAR (20) NOT NULL, 
   AGE INT NOT NULL, 
   ADDRESS CHAR (25), 
   SALARY   DECIMAL (18, 2),        
   PRIMARY KEY (ID) 
);  

-- 数据
INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (1, 'Ramesh', 32, 'Ahmedabad', 2000.00 );  

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (2, 'Khilan', 25, 'Delhi', 1500.00 );  

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (3, 'kaushik', 23, 'Kota', 2000.00 );

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (4, 'Chaitali', 25, 'Mumbai', 6500.00 ); 

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (5, 'Hardik', 27, 'Bhopal', 8500.00 );  

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (6, 'Komal', 22, 'MP', 4500.00 );

基于上述customers表,创建一个简单的包体 -

SET SERVEROUTPUT ON SIZE 99999;
CREATE OR REPLACE PACKAGE BODY cust_sal AS  

   PROCEDURE find_sal(c_id customers.id%TYPE) IS 
   c_sal customers.salary%TYPE; 
   BEGIN 
      SELECT salary INTO c_sal 
      FROM customers 
      WHERE id = c_id; 
      dbms_output.put_line('Salary: '|| c_sal); 
   END find_sal; 
END cust_sal; 
/

1.10.3使用包元素

包元素(变量,过程或函数)是用下面的语法来访问的 -

package_name.element_name;

考虑一下,假设已经在数据库模式中创建了上面的包,下面的程序中需要调用cust_sal包趾的find_sal方法 -

SET SERVEROUTPUT ON SIZE 9999;
DECLARE 
   code customers.id%type := &cc_id; 
BEGIN 
   cust_sal.find_sal(code); 
END; 
/

示例

以下程序提供了一个更完整的包体。使用存储在数据库中的CUSTOMERS表和以下记录 -

Select * from customers;  

+----+----------+-----+-----------+----------+ 
| ID | NAME     | AGE | ADDRESS   | SALARY   | 
+----+----------+-----+-----------+----------+ 
|  1 | Ramesh   |  32 | Ahmedabad |  3000.00 | 
|  2 | Khilan   |  25 | Delhi     |  3000.00 | 
|  3 | kaushik  |  23 | Kota      |  3000.00 | 
|  4 | Chaitali |  25 | Mumbai    |  7500.00 | 
|  5 | Hardik   |  27 | Bhopal    |  9500.00 | 
|  6 | Komal    |  22 | MP        |  5500.00 | 
+----+----------+-----+-----------+----------+

包规范

SET SERVEROUTPUT ON SIZE 99999;
CREATE OR REPLACE PACKAGE c_package AS 
   -- Adds a customer 
   PROCEDURE addCustomer(c_id customers.id%TYPE, 
   c_name  customers.name%TYPE, 
   c_age  customers.age%TYPE, 
   c_addr customers.address%TYPE,  
   c_sal  customers.salary%TYPE); 

   -- Removes a customer 
   PROCEDURE delCustomer(c_id  customers.id%TYPE); 
   --Lists all customers 
   PROCEDURE listCustomer; 

END c_package; 
/

创建程序包体

CREATE OR REPLACE PACKAGE BODY c_package AS 
   PROCEDURE addCustomer(c_id  customers.id%type, 
      c_name customers.name%type, 
      c_age  customers.age%type, 
      c_addr  customers.address%type,  
      c_sal   customers.salary%type) 
   IS 
   BEGIN 
      INSERT INTO customers (id,name,age,address,salary) 
         VALUES(c_id, c_name, c_age, c_addr, c_sal); 
   END addCustomer; 

   PROCEDURE delCustomer(c_id   customers.id%type) IS 
   BEGIN 
      DELETE FROM customers 
      WHERE id = c_id; 
   END delCustomer;  

   PROCEDURE listCustomer IS 
   CURSOR c_customers is 
      SELECT  name FROM customers; 
   TYPE c_list is TABLE OF customers.name%type; 
   name_list c_list := c_list(); 
   counter integer :=0; 
   BEGIN 
      FOR n IN c_customers LOOP 
      counter := counter +1; 
      name_list.extend; 
      name_list(counter) := n.name; 
      dbms_output.put_line('Customer(' ||counter|| ')'||name_list(counter)); 
      END LOOP; 
   END listCustomer;

END c_package; 
/

上面的例子使用了嵌套表。

当上面的代码在SQL提示符下执行时,它会产生以下结果 -

程序包体已创建。

使用程序包

以下程序使用程序包:c_package 中声明和定义的方法。

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   code customers.id%type:= 8; 
BEGIN 
   c_package.addcustomer(7, 'Andy Liu', 25, 'Chennai', 3500); 
   c_package.addcustomer(8, 'Kobe Bryant', 32, 'Delhi', 7500); 
   c_package.listcustomer; 
   c_package.delcustomer(code); 
   c_package.listcustomer; 
END; 
/

当上面的代码在SQL提示符下执行时,它会产生以下结果 -

Old salary:
New salary: 3500
Salary difference:
Old salary:
New salary: 7500
Salary difference:
Customer(1)Ramesh
Customer(2)Khilan
Customer(3)kaushik
Customer(4)Chaitali
Customer(5)Hardik
Customer(6)Komal
Customer(7)Andy Liu
Customer(8)Kobe Bryant
Customer(1)Ramesh
Customer(2)Khilan
Customer(3)kaushik
Customer(4)Chaitali
Customer(5)Hardik
Customer(6)Komal
Customer(7)Andy Liu
PL/SQL 过程已成功完成。

1.11 集合

在本章中,我们将讨论PL/SQL中的集合。集合是具有相同数据类型的有序元素组。 每个元素都由一个唯一的下标来表示它在集合中的位置。

PL/SQL提供了三种集合类型 -

  • 索引表或关联数组
  • 嵌套的表
  • 可变大小的数组或Varray类型

Oracle的每种类型的集合有以下特征 -

集合类型 元素个数 下标类型 密集或稀疏 在哪创建 是否为对象类型属性
关联数组(或索引表) 无界 字符串或整数 任意一种 只在PL/SQL块中 No
嵌套表 无界 整数 开始密集,可以变得稀疏 在PL/SQL块或模式级别 Yes
可变大小数组(Varray) 有界 整数 总是密集 在PL/SQL块或模式级别 Yes

两种类型的PL/SQL表(即索引表和嵌套表)具有相同的结构,并且使用下标符号来访问它们的行。 但是,这两种表在一个方面有所不同, 嵌套表可以存储在数据库列中,索引表不能。

1.11.1索引表

索引表(也称为关联数组)是一组键 - 值对。 每个键都是唯一的,用来定位相应的值。键可以是整数或字符串。
使用以下语法创建索引表。 在这里,正在创建一个名为table_name的索引表,其中的键是subscript_type,关联的值是element_type,参考以下语法 -

TYPE type_name IS TABLE OF element_type [NOT NULL] INDEX BY subscript_type; 

table_name type_name;

示例

以下示例显示了如何创建一个表来存储整数值以及名称,然后打印出相同的名称列表。

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   TYPE salary IS TABLE OF NUMBER INDEX BY VARCHAR2(20); 
   salary_list salary; 
   name   VARCHAR2(20); 
BEGIN 
   -- adding elements to the table 
   salary_list('Rajnish') := 62000; 
   salary_list('Minakshi') := 75000; 
   salary_list('Martin') := 100000; 
   salary_list('James') := 78000;  

   -- printing the table 
   name := salary_list.FIRST; 
   WHILE name IS NOT null LOOP 
      dbms_output.put_line 
      ('Salary of ' || name || ' is ' || TO_CHAR(salary_list(name))); 
      name := salary_list.NEXT(name); 
   END LOOP; 
END; 
/

执行上面示例代码,得到以下结果 -

Salary of James is 78000
Salary of Martin is 100000
Salary of Minakshi is 75000
Salary of Rajnish is 62000

PL/SQL 过程已成功完成

示例2

索引表的元素也可以是任何数据库表的%ROWTYPE或任何数据库表字段的%TYPE。 以下示例说明了这个概念。我们将使用存储在数据库中的CUSTOMERS表及数据 -

CREATE TABLE CUSTOMERS( 
   ID   INT NOT NULL, 
   NAME VARCHAR (20) NOT NULL, 
   AGE INT NOT NULL, 
   ADDRESS CHAR (25), 
   SALARY   DECIMAL (18, 2),        
   PRIMARY KEY (ID) 
);
INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (1, 'Ramesh', 32, 'Ahmedabad', 2000.00 );  

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (2, 'Khilan', 25, 'Delhi', 1500.00 );  

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (3, 'kaushik', 23, 'Kota', 2000.00 );

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (4, 'Chaitali', 25, 'Mumbai', 6500.00 ); 

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (5, 'Hardik', 27, 'Bhopal', 8500.00 );  

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (6, 'Komal', 22, 'MP', 4500.00 );

存储过程代码如下 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   CURSOR c_customers is 
      select name from customers; 

   TYPE c_list IS TABLE of customers.name%type INDEX BY binary_integer; 
   name_list c_list; 
   counter integer :=0; 
BEGIN 
   FOR n IN c_customers LOOP 
      counter := counter +1; 
      name_list(counter) := n.name; 
      dbms_output.put_line('Customer('||counter||'):'||name_list(counter)); 
   END LOOP; 
END; 
/

1.11.2嵌套表

嵌套表就像一个具有任意数量元素的一维数组。但是,嵌套表与数组在以下几个方面不同 -

  • 数组是一个有声明数量的元素集合,但是一个嵌套的表没有。嵌套表的大小可以动态增加。
  • 数组总是密集的,即它总是具有连续的下标。 嵌套数组最初是密集的,但是当从其中删除元素时,它可能变得稀疏。

使用以下语法创建嵌套表 -

TYPE type_name IS TABLE OF element_type [NOT NULL]; 

table_name type_name;

这个声明类似于索引表的声明,只是没有INDEX BY子句。

嵌套表可以存储在数据库列中。 它可以进一步用于简化SQL操作,您可以使用更大的表来连接单列表。关联数组不能存储在数据库中。

示例1
下面的例子说明了嵌套表的使用 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   TYPE names_table IS TABLE OF VARCHAR2(10); 
   TYPE grades IS TABLE OF INTEGER;  
   names names_table; 
   marks grades; 
   total integer; 
BEGIN 
   names := names_table('Kavita', 'Pritam', 'Ayan', 'Rishav', 'Aziz'); 
   marks:= grades(98, 97, 78, 87, 92); 
   total := names.count; 
   dbms_output.put_line('Total '|| total || ' Students'); 
   FOR i IN 1 .. total LOOP 
      dbms_output.put_line('Student:'||names(i)||', Marks:' || marks(i)); 
   end loop; 
END; 
/

当上面的代码在SQL提示符下执行时,它会产生以下结果 -

Total 5 Students
Student:Kavita, Marks:98
Student:Pritam, Marks:97
Student:Ayan, Marks:78
Student:Rishav, Marks:87
Student:Aziz, Marks:92

PL/SQL 过程已成功完成。


Shell

示例2

嵌套表的元素也可以是任何数据库表的%ROWTYPE或任何数据库表字段的%TYPE。以下示例说明了这个概念。我们将使用存储在数据库中的CUSTOMERS表,参考以下代码的实现 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   CURSOR c_customers is  
      SELECT  name FROM customers;  
   TYPE c_list IS TABLE of customers.name%type; 
   name_list c_list := c_list(); 
   counter integer :=0; 
BEGIN 
   FOR n IN c_customers LOOP 
      counter := counter +1; 
      name_list.extend; 
      name_list(counter)  := n.name; 
      dbms_output.put_line('Customer('||counter||'):'||name_list(counter)); 
   END LOOP; 
END; 
/

1.11.3 集合方法

PL/SQL提供了内置的集合方法,使集合更易于使用。下表列出了方法及其用途 -

编号 方法 目的
1 EXISTS(n) 如果集合中的第n个元素存在,则返回TRUE; 否则返回FALSE
2 COUNT 返回集合当前包含的元素的数量。
3 LIMIT 检查集合的最大容量(大小)。
4 FIRST 返回使用整数下标的集合中的第一个(最小)索引编号。
5 LAST 返回使用整数下标的集合中的最后(最大)索引编号。
6 PRIOR(n) 返回集合中索引n之前的索引编号。
7 NEXT(n) 返回索引n成功的索引号。
8 EXTEND 追加一个空(null)元素到集合。
9 EXTEND(n) n个空(null)元素追加到集合中。
10 EXTEND(n,i) 将第i个元素的n个副本追加到集合中。
11 TRIM 删除一个集合末尾的元素。
12 TRIM(n) 删除集合末尾的n个元素。
13 DELETE 删除集合中的所有元素,将COUNT设置为0
14 DELETE(n) 使用数字键或嵌套表从关联数组中删除第n个元素。 如果关联数组有一个字符串键,则删除键值对应的元素。 如果n为空,则DELETE(n)不执行任何操作。
15 DELETE(m,n) 从关联数组或嵌套表中移除m..n范围内的所有元素。 如果m大于n,或者mn为空,则DELETE(m,n)将不执行任何操作。

1.11.4 集合异常

下表提供了集合异常情况以及何时引发 -

编号 集合异常 引发的情况
1 COLLECTION_IS_NULL 尝试在一个原子空集合上进行操作。
2 NO_DATA_FOUND 下标指定被删除的元素或关联数组中不存在的元素。
3 SUBSCRIPT_BEYOND_COUNT 下标超出了集合中元素的数量。
4 SUBSCRIPT_OUTSIDE_LIMIT 下标超出允许的范围。
5 VALUE_ERROR 下标为空或不能转换为键类型。如果键定义为PLS_INTEGER范围,并且下标超出此范围,则可能会发生此异常。

1.12 事务

数据库事务是由一个或多个相关SQL语句组成的原子工作单元。它被称为原子操作,因为构成事务的SQL语句带来的数据库修改可以共同提交,即永久化到数据库或从数据库回滚(撤销)。

成功执行的SQL语句和提交的事务不一样。即使成功执行SQL语句,除非提交包含语句的事务,否则可以回滚该语句,并且可以撤消语句所做的所有更改。

1.12.1 开始和结束事务

事务有开始和结束。当发生以下事件之一时,事务即开始 -

  • 连接到数据库后执行第一个SQL语句。
  • 在事务完成后发出的每个新的SQL语句。

事务在下列事件之一发生时结束 -

  • 发出了COMMITROLLBACK语句。
  • 发出DDL语句,例如:CREATE TABLE语句; 因为在这种情况下,自动执行COMMIT
  • 发布DCL语句,如:GRANT声明; 因为在这种情况下,自动执行COMMIT
  • 用户从数据库断开连接。
  • 用户通过发出EXIT命令从SQL * PLUS退出,COMMIT自动执行。
  • SQL * Plus异常终止,会自动执行ROLLBACK
  • DML语句失败; 在这种情况下,会自动执行ROLLBACK来撤消该DML语句。

1.12.2提交事务

通过发出SQL命令COMMIT将事务永久化。COMMIT命令的一般语法是 -

COMMIT;

例如,

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (1, 'Ramesh', 32, 'Ahmedabad', 2000.00 ); 

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (2, 'Khilan', 25, 'Delhi', 1500.00 ); 

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (3, 'kaushik', 23, 'Kota', 2000.00 ); 

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (4, 'Chaitali', 25, 'Mumbai', 6500.00 ); 

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (5, 'Hardik', 27, 'Bhopal', 8500.00 ); 

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (6, 'Komal', 22, 'MP', 4500.00 ); 

COMMIT;

1.12.3 回滚事务

使用ROLLBACK命令可以撤销对不带COMMIT的数据库所做的更改。

ROLLBACK命令的一般语法是 -

ROLLBACK [TO SAVEPOINT < savepoint_name>];

当事务由于某种前所未有的情况而中止,如系统故障时,自提交以来整个事务被自动回滚。 如果不使用保存点(savepoint),那么只需使用以下语句来回滚所有更改。

ROLLBACK;

1.12.4 保存点 - Savepoints

保存点(Savepoints)是有助于通过设置一些检查点将长事务拆分成更小的单元的标记。通过在长事务中设置保存点,如果需要,可以回滚到检查点。这是通过发出SAVEPOINT命令完成的。

SAVEPOINT命令的一般语法是 -

SAVEPOINT < savepoint_name >;

例如,

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (7, 'Rajnish', 27, 'HP', 9500.00 ); 

INSERT INTO CUSTOMERS (ID,NAME,AGE,ADDRESS,SALARY) 
VALUES (8, 'Riddhi', 21, 'WB', 4500.00 ); 
SAVEPOINT sav1;

UPDATE CUSTOMERS 
SET SALARY = SALARY + 1000; 
ROLLBACK TO sav1;

UPDATE CUSTOMERS 
SET SALARY = SALARY + 1000 
WHERE ID = 7; 
UPDATE CUSTOMERS 
SET SALARY = SALARY + 1000 
WHERE ID = 8; 

COMMIT;

ROLLBACK TO sav1 - 此语句回滚直到保存点sav1的所有更改。

之后,所做新的改变将重新开始。

1.12.5 自动事务控制

要在执行INSERTUPDATEDELETE命令时自动执行COMMIT,可以将AUTOCOMMIT环境变量设置为 -

SET AUTOCOMMIT ON;

也可以使用以下命令关闭事务自动提交模式 -

SET AUTOCOMMIT OFF;

1.13 日期和时间

PL/SQL中有两种与日期和时间相关的数据类型 -

  • 日期时间数据类型
  • 间隔数据类型

日期时间(Datetime)数据类型是 -

  • DATE
  • TIMESTAMP
  • TIMESTAMP WITH TIME ZONE
  • TIMESTAMP WITH LOCAL TIME ZONE

间隔数据类型是 -

  • INTERVAL YEAR TO MONTH
  • INTERVAL DAY TO SECOND

1.13.1 日期时间和间隔数据类型的字段值

日期时间和时间间隔数据类型都由字段组成。这些字段的值决定了数据类型的值。下表列出了日期时间和间隔的字段及其可能的值。

字段名称 有效的日期时间值 有效的区间值
YEAR -47129999(不包括0年) 任何四位非零整数
MONTH 0112 011
DAY 0131(受MONTHYEAR的值限制,根据地区的日历规则) 任何非零整数
HOUR 0023 023
MINUTE 0059 059
SECOND 0059.9(n),其中9(n)是时间小数秒的精确度,9(n)部分不适用于DATE 059.9(n),其中9(n)是间隔小数秒的精确度
TIMEZONE_HOUR -1214(范围适应夏时制的变化),不适用于DATETIMESTAMP 不适用
TIMEZONE_MINUTE 0059,不适用于DATETIMESTAMP 不适用
TIMEZONE_REGION 不适用于DATETIMESTAMP 不适用
TIMEZONE_ABBR 不适用于DATETIMESTAMP 不适用

1.13.2 日期时间数据类型和函数

以下是Datetime数据类型 -

DATE

它以字符和数字数据类型存储日期和时间信息。它由世纪,年,月,日,时,分,秒等信息组成。它被指定为 -

TIMESTAMP

它是DATE数据类型的扩展。它存储DATE数据类型的年份,月份和日期以及小时,分钟和秒值。这对于存储精确的时间值非常有用。

TIMESTAMP WITH TIME ZONE

它是TIMESTAMP的一个变体,其中包含时区名称或时区偏移量。时区偏移量是本地时间与UTC之间的时差(小时和分钟)。此数据类型对于收集和评估跨地理区域的日期信息非常有用。

TIMESTAMP WITH LOCAL TIME ZONE

它是TIMESTAMP的另一个变体,它的值包括一个时区偏移量。下表提供了日期时间函数(其中,x具有日期时间值) -

编号 函数名称 描述
1 ADD_MONTHS(x, y); y个月添加到x
2 LAST_DAY(x); 返回月份的最后一天。
3 MONTHS_BETWEEN(x, y); 返回xy之间的月数。
4 NEXT_DAY(x, day); 返回x之后的第二天的日期时间。
5 NEW_TIME; 返回用户指定的时区的时间/日期值。
6 ROUND(x [, unit]); 舍入x
7 SYSDATE(); 返回当前的日期时间。
8 TRUNC(x [, unit]); 截断x

时间戳函数(其中,x有时间戳值) -

编号 函数名称 描述
1 CURRENT_TIMESTAMP(); 返回包含当前会话时间以及会话时区的TIMESTAMP WITH TIME ZONE
2 EXTRACT({ YEAR / MONTH / DAY / HOUR / MINUTE / SECOND } / { TIMEZONE_HOUR / TIMEZONE_MINUTE } / { TIMEZONE_REGION } TIMEZONE_ABBR ) FROM x) x中提取并返回年,月,日,小时,分钟,秒或时区。
3 FROM_TZ(x, time_zone); TIMESTAMP xtime_zone指定的时区转换为TIMESTAMP WITH TIMEZONE
4 LOCALTIMESTAMP(); 返回包含会话时区中本地时间的TIMESTAMP
5 SYSTIMESTAMP(); 返回包含当前数据库时间的TIMESTAMP WITH TIME ZONE以及数据库时区。
6 SYS_EXTRACT_UTC(x); TIMESTAMP WITH TIMEZONE x转换为包含UTC中的日期和时间的TIMESTAMP
7 TO_TIMESTAMP(x, [format]); 将字符串x转换为TIMESTAMP
8 TO_TIMESTAMP_TZ(x, [format]); 将字符串x转换为TIMESTAMP WITH TIMEZONE

例子

以下代码片段说明了上述函数的使用 -

示例1

SQL> SELECT SYSDATE FROM DUAL;

SYSDATE
--------------
07-11月-17


SQL

示例2

SQL> SELECT TO_CHAR(CURRENT_DATE, 'YYYY-MM-DD HH:MI:SS') FROM DUAL;

TO_CHAR(CURRENT_DAT
-------------------
2017-11-07 05:26:07


SQL

示例3

SQL> SELECT ADD_MONTHS(SYSDATE, 5) FROM DUAL;

ADD_MONTHS(SYS
--------------
07-4月 -18


SQL

示例4

SQL> SELECT LOCALTIMESTAMP FROM DUAL;

LOCALTIMESTAMP
---------------------------------------------------------------------------
07-11月-17 05.27.13.752000 上午


SQL

1.13.3 区间数据类型和函数

以下是区间数据类型 -

  • INTERVAL YEAR TO MONTH - 它使用YEARMONTH日期时间字段存储一段时间。第二天至第二天 - 它以天,小时,分钟和秒存储一段时间。
  • INTERVAL DAY TO SECOND - 它以天,小时,分钟和秒的形式存储一段时间。

区间函数

编号 函数 描述
1 NUMTODSINTERVAL(x, interval_unit); 将数字x转换为INTERVAL DAY TO SECOND
2 NUMTOYMINTERVAL(x, interval_unit); 将数字x转换为INTERVAL YEAR TO MONTH
3 TO_DSINTERVAL(x); 将字符串x转换为INTERVAL DAY TO SECOND
4 TO_YMINTERVAL(x); 将字符串x转换为INTERVAL YEAR TO MONTH

1.14 输出

DBMS_OUTPUT包有以下子程序 -

编号 子程序 目的
1 DBMS_OUTPUT.DISABLE; 禁用消息输出。
2 DBMS_OUTPUT.ENABLE(buffer_size IN INTEGER DEFAULT 20000); 启用消息输出。buffer_size设置为NULL值表示无限制的缓冲区大小。
3 DBMS_OUTPUT.GET_LINE (line OUT VARCHAR2, status OUT INTEGER); 检索一行缓冲的信息。
4 DBMS_OUTPUT.GET_LINES (lines OUT CHARARR, numlines IN OUT INTEGER); 从缓冲区中检索一行数组。
5 DBMS_OUTPUT.NEW_LINE; 放置一个行尾标记
6 DBMS_OUTPUT.PUT(item IN VARCHAR2); 在缓冲区中放置一个部分行。
7 DBMS_OUTPUT.PUT_LINE(item IN VARCHAR2); 在缓冲区中放置一行。

示例

请参考以下示例代码的用法 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   lines dbms_output.chararr; 
   num_lines number; 
BEGIN 
   -- enable the buffer with default size 20000 
   dbms_output.enable; 

   dbms_output.put_line('Hello Reader!'); 
   dbms_output.put_line('Hope you have enjoyed the tutorials!'); 
   dbms_output.put_line('Have a great time exploring pl/sql!'); 

   num_lines := 3; 

   dbms_output.get_lines(lines, num_lines); 

   FOR i IN 1..num_lines LOOP 
      dbms_output.put_line(lines(i)); 
   END LOOP; 
END; 
/

执行上面示例代码,得到以下结果 -

Hello Reader!
Hope you have enjoyed the tutorials!
Have a great time exploring pl/sql!

PL/SQL 过程已成功完成。

1.15 面向对象

PL/SQL允许定义一个对象类型,这有助于在Oracle中设计面向对象的数据库。 对象类型允许创建复合类型。使用对象可实现具有特定数据结构的真实世界对象以及操作它的方法。对象具有属性和方法。对象的属性用于存储对象的状态; 并使用方法来建模其行为。

对象是使用CREATE [OR REPLACE] TYPE语句创建的。 以下是创建一个由几个属性组成的简单地址(address)对象的例子 -

CREATE OR REPLACE TYPE address AS OBJECT 
(
  house_no varchar2(10), 
  street varchar2(30), 
  city varchar2(20), 
  state varchar2(10), 
  pincode varchar2(10) 
); 
/

当上面的代码在SQL提示符下执行时,它会产生以下结果 -

类型已创建。

下面再创建另一个对象:customer,将属性和方法包装在一起,以具有面向对象的感觉 -

CREATE OR REPLACE TYPE customer AS OBJECT 
(
 code number(5), 
 name varchar2(30), 
 contact_no varchar2(12), 
 addr address, 
 member procedure display 
); 
/

当上面的代码在SQL提示符下执行时,它会产生以下结果 -

类型已创建。

1.15.1 实例化对象

定义对象类型为对象提供模板(或蓝图)。要使用这个对象,需要创建这个对象的实例。可以使用实例名称和访问运算符(.)来访问对象的属性和方法,如下所示 -

SET SERVEROUTPUT ON SIZE 9999;
DECLARE 
   residence address; 
BEGIN 
   residence := address('1502A', '人民大道', '海口', '海南','201901'); 
   dbms_output.put_line('House No: '|| residence.house_no); 
   dbms_output.put_line('Street: '|| residence.street); 
   dbms_output.put_line('City: '|| residence.city); 
   dbms_output.put_line('Province: '|| residence.state); 
   dbms_output.put_line('Pincode: '|| residence.pincode); 
END; 

1.15.2 成员方法

成员方法用于操作对象的属性。在声明对象类型的同时提供成员方法的声明。 对象体定义了成员方法的代码。对象正文是使用CREATE TYPE BODY语句创建的。

构造函数是返回一个新对象作为其值的函数。每个对象都有一个系统定义的构造方法。构造函数的名称与对象类型相同。 例如 -

residence := address('1502A', '人民大道', '海口', '海南','201901');

比较方法用于比较对象。 有两种方法来比较对象 -

映射方法

Map方法是一个函数,它的值取决于属性的值。 例如,对于客户对象,如果客户代码对于两个客户是相同的,则两个客户可以是相同的。 所以这两个对象之间的关系将取决于代码的值。

排序方法

排序方法实现了一些用于比较两个对象的内部逻辑。例如,对于一个矩形对象,如果矩形的两边都较大,则矩形比另一个矩形大。

1.15.3 使用Map方法

下面尝试使用以下矩形对象来了解上述概念 -

SET SERVEROUTPUT ON SIZE 999999;
CREATE OR REPLACE TYPE rectangle AS OBJECT 
(
  length number, 
  width number, 
  member function enlarge( inc number) return rectangle, 
  member procedure display, 
  map member function measure return number 
); 
/

当上面的代码在SQL提示符下执行时,它会产生以下结果 -

类型已创建。

接下来,创建类型主体 -

CREATE OR REPLACE TYPE BODY rectangle AS 
   MEMBER FUNCTION enlarge(inc number) return rectangle IS 
   BEGIN 
      return rectangle(self.length + inc, self.width + inc); 
   END enlarge;  
   MEMBER PROCEDURE display IS 
   BEGIN  
      dbms_output.put_line('Length: '|| length); 
      dbms_output.put_line('Width: '|| width); 
   END display;  
   MAP MEMBER FUNCTION measure return number IS 
   BEGIN 
      return (sqrt(length*length + width*width)); 
   END measure; 
END; 
/

执行上面示例代码,得到以下输出结果 -

类型主体已创建。
Shell

现在使用矩形对象及其成员函数 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   r1 rectangle; 
   r2 rectangle; 
   r3 rectangle; 
   inc_factor number := 5; 
BEGIN 
   r1 := rectangle(3, 4); 
   r2 := rectangle(5, 7); 
   r3 := r1.enlarge(inc_factor); 
   r3.display;  
   IF (r1 > r2) THEN -- calling measure function 
      r1.display; 
   ELSE 
      r2.display; 
   END IF; 
END; 
/

1.15.4 使用排序方法

现在,使用排序方法可以达到同样的效果。下面来看看如使用排序方法重新创建矩形对象 -

CREATE OR REPLACE TYPE rectangle AS OBJECT 
(
  length number, 
  width number, 
  member procedure display, 
  order member function measure(r rectangle) return number 
); 
/

当上面的代码在SQL提示符下执行时,它会产生以下结果 -

类型已创建。

创建类型主体 -

CREATE OR REPLACE TYPE BODY rectangle AS 
   MEMBER PROCEDURE display IS 
   BEGIN 
      dbms_output.put_line('Length: '|| length); 
      dbms_output.put_line('Width: '|| width); 
   END display;  
   ORDER MEMBER FUNCTION measure(r rectangle) return number IS 
   BEGIN 
      IF(sqrt(self.length*self.length + self.width*self.width)> 
         sqrt(r.length*r.length + r.width*r.width)) then 
         return(1); 
      ELSE 
         return(-1); 
      END IF; 
   END measure; 
END; 
/

执行上面示例代码,得到以下结果 -

类型主体已创建。
Shell

使用矩形对象及其成员函数 -

SET SERVEROUTPUT ON SIZE 99999;
DECLARE 
   r1 rectangle; 
   r2 rectangle; 
BEGIN 
   r1 := rectangle(23, 44); 
   r2 := rectangle(15, 17); 
   r1.display; 
   r2.display; 
   IF (r1 > r2) THEN -- calling measure function 
      r1.display; 
   ELSE 
      r2.display; 
   END IF; 
END; 
/

1.15.5 PL/SQL对象的继承

PL/SQL允许从现有的基础对象创建对象。为了实现继承,基类对象应该声明为NOT FINAL。默认是FINAL

以下程序演示了PL/SQL对象中的继承。首先创建另一个名为TableTop对象,它是从Rectangle对象继承的。因此,需要创建这个基础的Rectangle对象,参考以下代码 -

CREATE OR REPLACE TYPE  rectangle FORCE AS OBJECT 
(
  length number, 
  width number, 
  member function enlarge( inc number) return rectangle, 
  NOT FINAL member procedure display) NOT FINAL 
/

执行上面示例代码,得到以下结果 -

类型已创建。
Shell

创建基本类型的主体 -

CREATE OR REPLACE TYPE BODY rectangle AS 
   MEMBER FUNCTION enlarge(inc number) return rectangle IS 
   BEGIN 
      return rectangle(self.length + inc, self.width + inc); 
   END enlarge;  
   MEMBER PROCEDURE display IS 
   BEGIN 
      dbms_output.put_line('Length: '|| length); 
      dbms_output.put_line('Width: '|| width); 
   END display; 
END; 
/

执行上面示例代码,得到以下结果 -

类型主体已创建。
Shell

创建子对象tabletop -

CREATE OR REPLACE TYPE tabletop UNDER rectangle 
(   
   material varchar2(20), 
   OVERRIDING member procedure display 
) 
/

执行上面示例代码,得到以下结果 -

类型已创建。
Shell

为子对象tabletop创建类型主体 -

CREATE OR REPLACE TYPE BODY tabletop AS 
OVERRIDING MEMBER PROCEDURE display IS 
BEGIN 
   dbms_output.put_line('Length: '|| length); 
   dbms_output.put_line('Width: '|| width); 
   dbms_output.put_line('Material: '|| material); 
END display; 
/

执行上面示例代码,得到以下结果 -

类型主体已创建。
Shell

使用tabletop对象及其成员函数 -

DECLARE 
   t1 tabletop; 
   t2 tabletop; 
BEGIN 
   t1:= tabletop(20, 10, 'Wood'); 
   t2 := tabletop(50, 30, 'Steel'); 
   t1.display; 
   t2.display; 
END;
/

当执行上面示例代码时,得到以下结果 -

Length: 20 
Width: 10 
Material: Wood 
Length: 50 
Width: 30 
Material: Steel
Shell

1.15.6 PL/SQL中的抽象对象

NOT INSTANTIABLE子句用来声明一个抽象对象。不能直接使用抽象对象, 必须创建抽象对象的子类型或子类型才能使用它的功能。

例如,

CREATE OR REPLACE TYPE rectangle AS OBJECT 
(length number, 
 width number, 
 NOT INSTANTIABLE NOT FINAL MEMBER PROCEDURE display)  
 NOT INSTANTIABLE NOT FINAL 
/

当上面的代码在SQL提示符下执行时,它会产生以下结果 -

类型已创建。

你可能感兴趣的:(Oracle)