第四章 - PL/SQL基础

第四章 - PL/SQL基础

4.1 PL/SQL程序设计概述

SQL语言与过程化的程序语言相比,比较简单,不需要关心底层的数据结构和算法。

然而,对于有些复杂的业务流程,SQL语言就有些无能为力了,比如业务处理中需要循环,需要逻辑判断、分支、需要异常处理等。

PL/SQL的出现正是为了解决这一问题。

4.1.1 PL/SQL

PL/SQL是(Procedure Language & Structured Query Language)的缩写。

它与C,Java等语言一样关注于处理细节,因此可以用来实现比较复杂的业务逻辑。PL/SQL通过增加了用在其它过程性语言中的结构来对SQL进行了扩展,把SQL语言的易用性、灵活性同过程化结构融合在一起。

PL/SQL是一种高级数据库程序设计语言,由于该语言集成于数据库服务器中,所以PL/SQL代码可以对数据进行快速高效的处理。

目前的PL/SQL包括两部分:

一部分是数据库引擎部分;
另一部分是可嵌入到许多产品(如C语言,Java语言等)工具中的独立引擎。可以将这两部分称为数据库PL/SQL和工具PL/SQL。

4.1.2 PL/SQL的优点

PL/SQL具备了SQL的功能和SQL语言所不具备的过程化功能。优点如下:

模块化
可移植性(可重用性)
安全性(错误处理)
便利性
提高应用程序的运行性能

4.1.3 PL/SQL 块结构

1. PL/SQL程序块

PL/SQL程序的基本结构是块。所有的PL/SQL程序都是由块组成的,一般由三部分组成:声明部分,可执行部分,错误处理部分。

PL/SQL的块结构如下所示:

[DECLARE]
          /* 声明部分 -- 这部分包括PL/SQL变量,常量,游标, 
          用户自定义异常等的定义 */
BEGIN
         /* 可执行部分 -- 这部分包括SQL语句及过程化的语句,   
         这部分是程序的主体 */
[EXCEPTION]
          /* 错误处理部分 -- 这部分包括错误处理语句  */
END;

PL/SQL块可以嵌套使用,嵌套块结构如下所示:

[DECLARE]
    ......          /*说明部分*/
BEGIN
    ......          /*主块的语句执行部分*/
      BEGIN
          ......    /*子块的语句执行部分*/
      [EXCEPTION]
          ......     /*子块的出错处理程序*/
      END;
[EXCEPTION]
    ......           /*主块的出错处理程序*/
END;

4.1.4 PL/SQL 的注释样式

PL/SQL 支持两种注释样式。

1.单行注释
  单行注释以两个连字符“--”开始,可以扩展到行尾。
例如:
v_department_name  VARCHAR2(30); --这个变量用来处理部门名称

2.多行注释
这些注释以“/*”开始并以“*/”结束 ,可以跨越多行。建议采用多行注释。

【例4-1】编写一个简单的PL/SQL程序,该程序输出两行文字:“我喜欢学习数据库课程”和“我尤其喜欢Oracle数据库”。程序运行效果如下图所示。

BEGIN
	dbms_output.put_line('我很喜欢学习数据库课程!');
	dbms_output.put_line('我尤其喜欢Oracle数据库!');
END;

【例4-2】编写一个PL/SQL程序,该程序输出长方形的面积,其中长和宽的值由键盘随机输入。程序运行效果如下图所示。

DECLARE
	v_length NUMBER := &length;
	v_width NUMBER := &width;
	v_area NUMBER;
BEGIN
	v_area := v_length * v_width;
	dbms_output.put_line('该长方形的面积为:'||v_area);
END;

【例4-3】编写带有嵌套块的PL/SQL程序。
编写一个PL/SQL程序,在PL/SQL主块中输出长方形的面积,在PL/SQL子块中输出长方形的周长,其中长和宽的值由键盘随机输入。程序运行效果如下图所示。

DECLARE                         /*主块的说明部分*/
v_length NUMBER:=&length;
v_width NUMBER:=&width;
v_area  NUMBER;
BEGIN                            /*主块的语句执行部分*/
DECLARE                     /*子块的说明部分*/
v_cir number;
BEGIN                        /*子块的语句执行部分*/
v_cir:=(v_length+v_width)*2;
DBMS_OUTPUT.PUT_LINE('子块中:该长方形的周长为:'||v_cir);
END;                         /*子块的语句结束*/
v_area:=v_width*v_length;
DBMS_OUTPUT.PUT_LINE('主块中:该长方形的面积为:'||v_area);
END;                             /*主块的语句结束*/

4.2 变量

4.2.1 PL/SQL 字符集

在 使用PL/SQL进行程序设计时,可以使用的有效字符包括以下3类:

1.所有的大写和小写英文字母。
2. 数字0—9。
3. 符号()、+、-、*、/等。

4.2.2 标识符定义

PL/SQL程序设计中的标识符定义与SQL的标识符定义的要求相同。要求和限制有:

1.不能超过30个字符。

2.首字符必须为字母。

3.不区分大小写。

4.不能使用SQL保留字。

5.对标识符的命名最好遵循实际项目中相关命名规范。采用的命名规范要求变量以“v_”开头,常量以“c_”开头,以标识符用途来为其命名。

例如:v_lastname表示一个处理名字的变量, c_birthday表示一个处理出生日期的常量。

4.2.3 常量和变量的声明语法

PL/SQL中出现的变量在DECLARE部分定义。
语法如下:

变量名 [CONSTANT] 数据类型 [NOT NULL][ := | DEFAULT  PL/SQL表达式]

说明:

1.声明常量时必须加关键字CONSTANT,常量在声明时必须初始化,否则在编译时会出错。

例如:

c_pi CONSTANT NUMBER(8,7) := 3.1415926; 
-- 如果没有后面的 “:= 3.1415926” 是没有办法通过编译的。

2.如果一个变量没有进行初始化,它将缺省的被赋值为NULL。如果使用了非空约束,就必须给这个变量赋一个值。

例如:

v_flag  VARCHAR2(20) NOT NULL :=‘true’;

-- 而不能
v_flag VARCHAR2(20) NOT NULL; 

-- 声明标识符时,要注意每行声明一个标识符。
v_firstname  VARCHAR2(20);
v_job VARCHAR2(20);

-- 而不能
v_firstname, v_job  VARCHAR2(20);

3.初始化变量可以用 “:=” 或“DEFAULT”,如果没有设置初始值,变量默认被赋值为NULL。

4.变量名称不要和数据库中表名或字段名相同,否则可能会产生意想不到的结果。

建议的命名方法如下表所示:

标识符 命名规则 例子
变量 v_name v_name
常量 c_name c_name
游标类型变量 name_cursor emp_cursor
异常类型变量 e_name e_too_many
记录类型变量 name_record emp_record

4.2.4 数据类型

PL/SQL编程中常用的数据类型有标量类型、参考类型、 LOB类型和用户自定义类型。

1.标量类型

可以分为以下四种:

(1)数值型

NUMBER [(precision, scale)]:可存储整数或实数值,这里precision是精度,即数值中所有数字位的个数, scale是刻度范围,即小数点右边数字位的个数。

常用的数值类型为NUMBER。

例如:

v_a1  NUMBER;
v_a2  NUMBER(2):=12;  
v_a3  NUMBER(3) DEFAULT  12;
c_a4  CONSTANT  NUMBER:=0;

(2)字符型

CHAR [(maximum_length)] :描述定长的字符串。

VARCHAR2 (maximum_length):描述变长的字符串。

常用的数值类型为CHAR,VARCHAR2。

例如:

v_c1 CHAR(10);
v_c2 VARCHAR2(10):=‘hello’;

(3)日期型

常用的日期类型为DATE。日期默认格式为DD-MON-YY,分别对应日、月、年。

例如:

v_d1  DATE;
v_d2  DATE :=‘20-12月-2023’;

(4)布尔型

存储逻辑值TRUE或FALSE。

例如:

v_b1 	BOOLEAN;
v_b2 	BOOLEAN := FALSE;
v_b3 	BOOLEAN := TRUE;

2.参考类型

参考类型分为两种,%TYPE和%ROWTYPE。

(1)%TYPE类型

定义一个变量,其数据类型可以与已经定义的某个数据变量的类型相同,或者与数据库表的某个列的数据类型相同,这时可以使用%TYPE。

例如:

v_a1  NUMBER;
v_a2  v_a1%TYPE; --v_a2参照自v_a1变量的类型         
v_sno  student.sno%TYPE;  
/* v_sno参照自student.sno表中sno列的类型。*/

(2)%ROWTYPE类型

定义一个变量的类型参照自基本表或视图中记录的类型、或游标的结构类型,这时可以使用%ROWTYPE。

%ROWTYPE前面是表名或游标名。

例如向SC表中插入一条学生的选课成绩信息

Declare
    v_sc   sc%rowtype; /*v_sc参照自sc表中记录的类型*/
begin
    v_sc.sno:='20180001';
    v_sc.cno:='c2';
    v_sc.grade:=95;
    insert into sc
    values(v_sc.sno,v_sc.cno,v_sc.grade);
    Commit;
end;

说明:由于v_sc能代表sc表中的某一条记录类型,所以在访问该记录中某个特定字段时,可以通过变量名.字段名的方式调用。

【例10-5】 从学生表中查询05880102学生的信息付给变量v_sc并输出

Declare
    v_sc   student%rowtype;
begin
    select * into v_sc from student 
    where sno='05880102';
    dbms_output.put_line('学号='||v_sc.sno||‘
    ,姓名为'||v_sc.sname);
end;

3.LOB类型

用于存储大的数据对象的类型。Oracle目前主要支持BFILE, BLOB, CLOB 及 NCLOB 类型。

(1)BFILE

存放大的二进制数据对象,这些数据文件不放在数据库里,而是放在操作系统的某个目录里,数据库的表里只存放文件的目录。

(2)BLOB

存储大的二进制数据类型。变量存储大的二进制对象的位置。大二进制对象的大小<=4GB。

(3)CLOB

存储大的字符数据类型。每个变量存储大字符对象的位置,该位置指到大字符数据块。大字符对象的大小<=4GB。

(4)NCLOB

存储大的NCHAR字符数据类型。每个变量存储大字符对象的位置,该位置指到大字符数据块。大字符对象的大小<=4GB。

4.用户自定义类型

根据用户自己的需要,用现有的PL/SQL标量类型组合成一个用户自定义的类型。

例如:定义用户自定义的数据类型student_type

create or replace type student_type 
as object(
            id number(5),
            firstname  varchar2(20),
            lastname  varchar2(20));

例:引用用户自定义的数据类型 v_s1 STUDENT_TYPE;

4.2.5 变量赋值

在PL/SQL程序中可以通过两种方式给变量赋值。

1.直接赋值

-- 变量名 := 常量或表达式;

v_num  NUMBER:=5; 

2.通过SELECT..INTO赋值

-- SELECT  字段 INTO  变量名

-- 例如:
declare
    v_age number;
begin
    SELECT  AGE INTO v_age  FROM STUDENT WHERE SNO=‘05880101';
    dbms_output.put_line(‘v_age='||v_age);
end;

【例4-4】查找标量类型变量或常量在声明过程中可能出现的错误,并进行改正。

运行给定的PL/SQL程序代码,进行调试改错,指出错误总数,并最终看到正确的运行结果。程序代码如下:

DECLARE
    123_sno CHAR(8);----------------------------------①
    sum NUMBER; ----------------------------------- --②
    v_date DATE; ----------------------------------------③
v_num  NUMBER NOT NULL; -----------------④
    c_pi CONSTANT NUMBER(8,7); ----------------⑤
    v_cname, v_tname  VARCHAR2(10); -----------⑥
BEGIN
    DBMS_OUTPUT.PUT_LINE(‘我把所有错误都改正了,真棒!’);
END;

【例4-6】编写带有SELECT…INTO赋值语句的PL/SQL程序编写一个PL/SQL程序,输出学号为20180006的学生姓名和出生年份。程序运行效果如下图所示。

DECLARE
    v_sname student.sname%TYPE;
    v_birth NUMBER;
BEGIN
    SELECT sname,2018-age INTO v_sname,v_birth FROM student WHERE sno=’20180006’;
    DBMS_OUTPUT.PUT_LINE('学号为20180006的学生姓名为:'||v_sname||’ 出生年份:’||v_birth);
END;

4.3 PL/SQL中的运算符和函数

4.3.1 PL/SQL运算符

PL/SQL有一组运算符,可以分为3类:算术运算符、关系运算符和逻辑运算符。

1、算术运算符

+(加)、-(减)、*(乘)、/(除)、**(指数)和 ||(连接)。

其中 +(加)和 -(减)运算符也可用于对DATE(日期)数据类型的值进行运算。

2、关系运算符

关系运算符(又称为比较运算符),其运算结果为逻辑值TRUE和FALSE。
关系运算符有以下几种:

(1)=(等于)、<>或!=(不等于)、<(小于)、 >(大于)、>=(大于等于)、<=(小于等于);
(2)BETWEEN…AND…(检索两值之间的内容);
(3)IN(检索匹配列表中的值);
(4)LIKE(检索匹配字符样式的数据);
(5)IS NULL(检索空数据)。

3、逻辑运算符

逻辑运算符用于对某个条件进行测试,运算结果为TRUE和FALSE。

逻辑运算符有:

(1)AND(两个表达式为真则结果为真);
(2)OR(只要有一个表达式为真则结果为真);
(3)NOT(取相反的逻辑值)。

4.3.2 PL/SQL中的函数

在PL/SQL中支持所有SQL中的单行数字型的函数、单行字符型的函数、数据类型的转换函数、日期型的函数和其他各种函数,但不支持聚组函数(如:AVG、COUNT、MIN、MAX、SUM等)。在PL/SQL块内只能在SQL语句中使用聚组函数。

举例说明PL/SQL块中可以引用的各种函数

v_date:=SYSDATE;
v_name:=UPPER(‘Tom’)||LOWER(‘Jerry’);
v_sal:=SUM(salary); /*错误,不能在有赋值运算符的语句中使用聚组函数*/
SELECT SUM(salary) INTO v_sal FROM employees;/*正确,这是SQL语句*/ 

4.4 PL/SQL 条件结构

4.4.1 条件语句结构

根据另一条语句或表达式的结果执行一个操作或一条语句。分为IF条件语句与CASE条件语句。

1、IF条件语句

语法结构:

IF 条件1 THEN 
        语句体1;
	  [ELSIF 条件2 THEN
		   语句体2;]
		   …
	  [ELSE
		   语句体n;]
	  END IF;
-- 其中:条件是一个布尔型的变量或表达式。 

说明:如果条件1成立,就执行语句体1中的内容,否则判断条件2是否成立,如果条件2成立,执行语句体2的内容,依次类推。如果所有条件都不满足,执行ELSE中语句体n的内容。

注意:每个IF语句以相应的END IF语句结束,IF语句后必需有THEN语句,IF…THEN后不跟语句结束符“;”,一个IF语句最多只能有一个ELSE语句。条件是一个布尔型的变量或表达式。IF条件语句最多只能执行一个条件分支,执行之后跳出整个语句块。

2、CASE条件语句

含SELECTOR(选择符)的CASE语句

语法如下:

CASE SELECTOR
    WHEN  表达式1  THEN  语句序列1;
    [WHEN  表达式2  THEN  语句序列2;]
    ...
    [WHEN  表达式N  THEN  语句序列N;]
    [ELSE  语句序列N+1;]
END CASE ;

搜索CASE语句

语法如下:

CASE
    WHEN  搜索条件1  THEN  语句序列1;
    WHEN  搜索条件2  THEN  语句序列2;
    ...
    WHEN  搜索条件N  THEN  语句序列N;
    [ELSE  语句序列N+1;]
END CASE;

【例4-8】编写含有IF条件语句的PL/SQL程序,根据某一个学生的平均成绩,判断学生获得的奖学金等级,并输出结果。学号由键盘随机输入,输出等级说明:如果平均成绩>85分,则输出此同学的平均成绩,一等奖学金;如果平均成绩在75分至85分之间,则输出此同学的平均成绩,二等奖学金;否则,输出此同学的平均成绩,无奖学金。

程序运行效果如下图所示。

declare
    v_sno student1.sno%TYPE := &sno;
    v_grade sc1.grade%TYPE;
begin
  select avg(grade) into v_grade from sc1 where sno = v_sno;
  if v_grade > 85 then
    dbms_output.put_line('此同学平均成绩为:' || v_grade || ',一等奖学金。');
  elsif v_grade >= 75 then
    dbms_output.put_line('此同学平均成绩为:' || v_grade || ',二等奖学金。');
  else
    dbms_output.put_line('此同学平均成绩为:' || v_grade || ',无奖学金。');
  end if;
end;

【例4-9】编写含有IF条件嵌套语句的PL/SQL程序,完善一个PL/SQL程序,判断随机输入的3个数中的最大值。程序运行效果如下图所示。

declare
  -- 定义三个变量储存输入的值
  v_a number := &a;
  v_b number := &b;
  v_c number := &c;
  -- 定义一个最大值变量来保存最大值
  v_x number ;
begin
  -- 判断a是否都比两个数值大,如果都比两个数值大则a就是最大值,如果不是则最大值在剩下的两个变量中
  if(v_a > v_b) and (v_a > v_c) then
    v_x := v_a;
  else
    -- 在判断剩下两个
    if v_b > v_c then
      v_x := v_b;
    else
      v_x := v_c;
    end if;
  end if;
  dbms_output.put_line('最大值为:' || v_x);
end;

【例4-10】编写含有SELECTOR(选择符)CASE语句的PL/SQL程序,根据给定的PL/SQL程序代码,用CASE语句判断v_grade变量的值是否等于A、B、C、D,并分别处理。如果程序能正常运行,请说明运行结果;如果程序不能正常运行,请说明原因。程序代码如下:

DECLARE
  v_grade VARCHAR2(10) := 'B';
BEGIN
	CASE v_grade
	  WHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent');
    WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good');
    WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('Good');
  	WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE('Fair');
  	WHEN 'D' THEN DBMS_OUTPUT.PUT_LINE('Poor');
	ELSE  
    DBMS_OUTPUT.PUT_LINE('No such grade');
END CASE;
END;

4.5 PL/SQL循环结构

4.5.1 简单循环

简单循环的特点,循环体至少执行一次。

其语法如下:

LOOP 
    循环体;
    [EXIT;]  -- 满足退出条件,退出循环
END LOOP

退出循环的语法如下:

IF  条件表达式  THEN
    EXIT;
END IF;

EXIT  WHEN  条件表达式;

4.5.2 WHILE循环

语法如下:

WHILE  条件表达式  
    LOOP 
      循环体;
    END  LOOP;

说明:当条件表达式为TRUE时,执行循环体中的内容,如果结果为FALSE,则结束循环。

4.5.3 数字式FOR循环

语法如下:

FOR counter IN [REVERSE]   start_range..end_range      
LOOP
		循环体;
END LOOP;

说明:

  1. 简单LOOP循环和WHILE循环的循环次数都是不确定的,FOR循环的循环次数是固定的;

  2. counter是一个隐式声明的变量,不需要在DECLARE部分定义。

  3. start_range和end_range指明了循环的次数。

  4. REVERSE表示循环变量从最大值向最小值叠代。

【例4-12】利用简单循环LOOP语句实现输出1-10之间的平方数。

declare
  i number := 1;
begin
  loop
    dbms_output.put_line(i || '的平方数为' || i*i);
    i := i+1;
    if i>10 then
      exit;
    end if;
  end loop;
end;
declare
  i number := 1;
begin
  loop
    dbms_output.put_line(i || '的平方数为' || i*i);
    i := i+1;
    exit when i>10;
  end loop;
end;
declare
  i number := 1;
begin
  while i<=10
    loop
      dbms_output.put_line(i || '的平方数为' || i*i);
      i := i+1;
    end loop;
end;
declare
  i number := 1;
begin
  for i in 1..10
    loop
      dbms_output.put_line(i || '的平方数为' || i*i);
    end loop;
end;

【例4-13】利用WHILE循环结构求10的阶乘。

declare
  n number := 1;
  i number := 1;
begin
  while i<=10
    loop
      n := n * i;
      i := i + 1;
    end loop;
  dbms_output.put_line('10的阶乘为:' || n);
end;
declare
  n number := 1;
begin
  for i in 2..10
    loop
      n := n * i;
    end loop;
  dbms_output.put_line('10的阶乘为:' || n);
end;

用反向FOR循环结构求10的阶乘。

declare
  n number := 1;
begin
  for i in  reverse 2..10
    loop
      n := n * i;
    end loop;
  dbms_output.put_line('10的阶乘为:' || n);
end;

说明:IN表示循环变量i从小到大依次取值。IN REVERSE表示循环变量i从大到小依次取值。

posted @ 2024-03-05 22:26  WNAG_zw  阅读(25)  评论(0编辑  收藏  举报