PLSQL

PL/SQL

一、PL/SQL 程序设计简介

1、概述

  • PL/SQL 是 Procedure Language & Structured Query Language 的缩写。
  • PL/SQL是对 SQL 语言存储过程语言的扩展。
  • 它现在已经成为一种过程处理语言,简称 PL/SQL。

2、PL/SQL 可用的 SQL 语句

  • PL/SQL 是 ORACLE 系统的核心语言,现在 ORACLE 的许多部件都是由 PL/SQL 写成。
  • 在 PL/SQL 中可以使用的 SQL 语句有:
    • INSERT,UPDATE,DELETE,SELECT … INTO,COMMIT,ROLLBACK,SAVEPOINT。
  • 提示:在 PL/SQL 中只能用 SQL 语句中的 DML 部分,不能用 DDL 部分,如果要在 PL/SQL 中使用 DDL(如CREATE table 等)的话,只能以动态的方式来使用。

补充:大纲

1、pl/sql基本的语法格式

2、记录类型

3、流程控制

3.1条件判断(两种)

方式一:if … then … elseif then … else … end if;

方式二:case … when … then … end;

3.2循环结构(三种)

方式一:loop … exit when … end loop;

方式二:while … loop … end loop;

方式三:for i in … loop … end loop;

3.3 goto、exit

4、游标的使用(类似于java中的iterator)

5、异常的处理(三种方式)

6、存储函数(有返回值)、存储过程(没有返回值)

7、触发器

二、PL/SQL 块结构和组成元素

SQL> set serveroutput on

1、PL/SQL 块

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

(1)PL/SQL 块的结构如下:

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

其中 执行部分是必须的。

  • 输出helloworld
declare----声明的变量、类型、游标
begin----程序的执行部分(类似于java里的main()方法)dbms_output.put_line('helloworld');
exception----针对begin块中出现的异常,提供处理的机制----when ... then ...----when ... then ...
end;
SQL> set serveroutput onbegindbms_output.put_line('helloworld');
end;helloworldPL/SQL procedure successfully completed
  • 查询100号员工的工资
declare--声明变量v_sal number(20);  
begin--sql语句的操作:select ... into ... from ... where ...select salary into v_sal from employees where employee_id = 100;--打印dbms_output.put_line(v_sal);
end;
  • 查询100号员工的工资,邮箱,入职日期
declarev_sal number(20);v_email varchar2(20);v_hire_date date;  
beginselect salary,email, hire_date into v_sal,v_email,v_hire_date from employees where employee_id = 100;dbms_output.put_line(v_sal||','||v_email||','||v_hire_date);
end;24000,SKING,17-JUN-87PL/SQL procedure successfully completed

注意:变量之间要对应。

  • 声明变量也可以动态声明
declarev_sal employees.salary%type;v_email employees.email%type;v_hire_date employees.hire_date%type;  
beginselect salary,email, hire_date into v_sal,v_email,v_hire_date from employees where employee_id = 100;dbms_output.put_line(v_sal||','||v_email||','||v_hire_date);
end;

(2)PL/SQL 块可以分为三类:

  1. 无名块:动态构造,只能执行一次。
  2. 子程序:存储在数据库中的存储过程、函数及包等。当在数据库上建立好后可以在其它程序中调用它们。
  3. 触发器:当数据库发生操作时,会触发一些事件,从而自动执行相应的程序。

2、标识符

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

  • 标识符名不能超过 30 字符;
  • 第一个字符必须为字母;
  • 不分大小写;
  • 不能用’-‘(减号);
  • 不能是 SQL 保留字。

一般不要把变量名声明与表中字段名完全一样,如果这样可能得到不正确的结果。

例如:下面的例子将会删除所有的纪录,而不是 KING 的记录;

DECLAREEname varchar2(20) := ’KING’;
BEGINDELETE FROM emp WHERE ename=ename;
END;

变量命名在 PL/SQL 中有特别的讲究,建议在系统的设计阶段就要求所有编程人员共同遵守一定的要求,使得整个

系统的文档在规范上达到要求。下面是建议的命名方法:

标识符命名规则例子
程序变量V_nameV_name
程序常量C_NameC_company_name
游标变量Name_cursorEmp_cursor
异常标识E_nameE_too_many
表类型Name_table_typeEmp_record_type
Name_tableEmp
记录类型Name_recordEmp_record
SQL*Plus替代变量P_nameP_sal
绑定变量G_nameG_year_sal

3、变量类型

(1)变量类型

在这里插入图片描述
在这里插入图片描述

补充:number类型

NUMBER( precision, scale)

  • precision表示数字中的有效位;如果没有指定precision的话,Oracle将使用38作为精度。

  • 如果scale大于零,表示数字精确到小数点右边的位数;scale默认设置为0;如果scale小于零,Oracle将把该数字取舍到小数点左边的指定位数。

  • Precision的取值范围为【1—38】;Scale的取值范围为【-84—127】。

  • NUMBER整数部分允许的长度为(precision- scale),无论scale是正数还是负数。

  • 如果precision小于scale,表示存储的是没有整数的小数。

  • Precision表示有效位数,有效数位:从左边第一个不为0的数算起,小数点和负号不计入有效位数;scale表示精确到多少位,指精确到小数点左边或右边多少位(±决定)。

Number值类型举例:

实际值数据类型存储值
1234567.89Number1234567.89
1234567.89Number(8)1234567
1234567.89Number(6)出错
1234567.89Number(9,1)1234567.9
1234567.89Number(9,3)出错
1234567.89Number(7,2)出错
1234567.89Number(5,-2)1234600
1234511.89Number(5,-2)1234500
1234567.89Number(5,-4)1230000
1234567.89Number(*,1)1234567.9
0.012Number(2,3)0.012
0.23Number(2,3)出错
  • 关于precision, scale也可以作如下表述,定点数的精度§和刻度(s)遵循以下规则:
  1. 当一个数的整数部分的长度 > p-s 时,Oracle就会报错

  2. 当一个数的小数部分的长度 > s 时,Oracle就会舍入。

  3. 当s(scale)为负数时,Oracle就对小数点左边的s个数字进行舍入。

  4. 当s > p 时, p表示小数点后第s位向左最多可以有多少位数字,如果大于p则Oracle报错,小数点后s位向右的数字被舍入

  • 与int的区别

1)oracle本来就没有int类型,为了与别的数据库兼容,新增了int类型作为number类型的子集。

2)int类型只能存储整数,int相当于number(22),存储总长度为22的整数。

3)number可以存储浮点数,也可以存储整数;

number(8,1)存储小数位为1位,总长度为8的浮点数,如果小数位数不足,则用0补全;

number(8)存储总长度为8的整数;

  • number类型的子类

a) oracle本来就没有int类型,为了与别的数据库兼容,新增了int类型作为number类型的子集。

b) 在oracle数据库建表的时候,decimal,numeric不带精度,oracle会自动把它处理成INTEGER;带精度,oracle

会自动把它处理成number。

c) Oracle只用NUMBER(m,n)就可以表示任何复杂的数字数据。

d) decimal,numeric,int等都为SQL、DB2等数据库的数据类型,ORACLE为了兼容才将其引入;但实际上在

ORACLE内部还是以NUMBER的形式将其存入。

declarev_sal number(8,2) :=0;v_emp_id number(10);v_email varchar2(20);v_hiredate date;
beginselect salary,employee_id,email,hire_date into v_sal,v_emp_id,v_email,v_hiredatefrom employeeswhere employee_id = 123;dbms_output.put_line('employee_id:'||v_emp_id||' salary:'||v_sal||' email:'||v_email||' hire_date:'||v_hiredate);end;employee_id:123 salary:6500 email:SVOLLMAN hire_date:10-OCT-97PL/SQL procedure successfully completed
(2)复合类型 ------记录和表
  • 记录类型

记录类型是把逻辑相关的数据作为一个单元存储起来,称作 PL/SQL RECORD 的域(FIELD),其作用是存放互不相

同但逻辑相关的信息。

定义记录类型语法如下:

TYPE record_type IS RECORD(Field1 type1 [NOT NULL] [:= exp1 ],Field2 type2 [NOT NULL] [:= exp2 ],. . . . . .Fieldn typen [NOT NULL] [:= expn ] ) ;
declare
----声明一个记录类型type emp_record is record(v_sal employees.salary%type,v_email employees.email%type,v_hire_date employees.hire_date%type);----定义一个记录类型的成员变量v_emp_record emp_record;beginselect salary,email, hire_date into v_emp_record from employees where employee_id = 100;dbms_output.put_line(v_emp_record.v_sal||','||v_emp_record.v_email||','||v_emp_record.v_hire_date);
end;SQL> /24000,SKING,17-JUN-87PL/SQL procedure successfully completed

注:相当于是一个类里面有三个成员变量,java里面不能通过类去调用成员变量,需要通过对象来调用。

declare
----声明一个记录类型type emp_record is record(v_sal number(8,2) :=0,v_emp_id number(10),v_email varchar2(20),v_hiredate date);----声明一个记录类型的变量v_emp_record emp_record;
beginselect salary,employee_id,email,hire_date into v_emp_recordfrom employeeswhere employee_id = 123;dbms_output.put_line('employee_id:'||v_emp_record.v_emp_id||' salary:'||v_emp_record.v_sal||' email:'||v_emp_record.v_email||' hire_date:'||v_emp_record.v_hiredate);end;employee_id:123 salary:6500 email:SVOLLMAN hire_date:10-OCT-97PL/SQL procedure successfully completed

注:声明记录类型时一定要注意里面的标点

declaretype salary_record is record(v_name varchar2(20),v_salary number(10,2));v_sal_record salary_record;beginv_sal_record.v_name :='刘德华';v_sal_record.v_salary :=12000;dbms_output.put_line('name:'||v_sal_record.v_name||'salary:'||v_sal_record.v_salary);end;  name:刘德华salary:12000PL/SQL procedure successfully completed
  • 使用%TYPE

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

优点:

所引用的数据库列的数据类型可以不必知道;

所引用的数据库列的数据类型可以实时改变。

(3)使用%ROWTYPE

PL/SQL 提供%ROWTYPE 操作符, 返回一个记录类型, 其数据类型和数据库表的数据结构相一致。

优点:

所引用的数据库中列的个数和数据类型可以不必知道;

所引用的数据库中列的个数和数据类型可以实时改变。

declarev_emp_record employees%rowtype;beginselect * into v_emp_recordfrom employeeswhere employee_id = 123;dbms_output.put_line('employee_id:'||v_emp_record.employee_id||' salary:'||v_emp_record.salary||' email:'||v_emp_record.email||' hire_date:'||v_emp_record.hire_date);end;  
declarev_emp_record employees%rowtype;beginselect * into v_emp_recordfrom employeeswhere employee_id = 123;dbms_output.put_line('employee_id:'||v_emp_record.employee_id||' salary:'||v_emp_record.salary||' email:'||v_emp_record.email||' hire_date:'||v_emp_record.hire_date);end;  
(4)PL/SQL 表(嵌套表)

(emmmm,暂不掌握)

PL/SQL 程序可使用嵌套表类型创建具有一个或多个列和无限行的变量, 这很像数据库中的表。

声明嵌套表类型的一般语法如下:

TYPE type_name IS TABLE OF
{datatype | {variable | table.column} % type | table%rowtype};

4、运算符和表达式(数据定义)

(1)关系运算符

在这里插入图片描述

(2)一般运算符

在这里插入图片描述
在这里插入图片描述

(3)逻辑运算符

在这里插入图片描述

5、变量赋值

在 PL/SQL 编程中,变量赋值是一个值得注意的地方,它的语法如下:

variable := expression;

variable 是一个 PL/SQL 变量, expression 是一个 PL/SQL 表达式。

(1)字符及数字运算特点

空值加数字仍是空值:NULL + < 数字> = NULL

空值加(连接)字符,结果为字符:NULL || <字符串> = < 字符串>

(2)BOOLEAN 赋值

布尔值只有 TRUE, FALSE 及 NULL 三个值。

(3)数据库赋值

数据库赋值是通过SELECT语句来完成的,每次执行SELECT语句就赋值一次,一般要求被赋值的变量与SELECT中的列名要一一对应。如:

DECLARE
emp_id emp.empno%TYPE :=7788;
emp_name emp.ename%TYPE;
wages emp.sal%TYPE;BEGIN
SELECT ename, NVL(sal,0) + NVL(comm,0) INTO emp_name, wages 
FROM emp WHERE empno = emp_id;
DBMS_OUTPUT.PUT_LINE(emp_name||’----‘||to_char(wages));
END;

提示:不能将SELECT语句中的列赋值给布尔变量。

(4)可转换的类型赋值
  • CHAR 转换为 NUMBER

使用 TO_NUMBER 函数来完成字符到数字的转换,如:

v_total := TO_NUMBER(‘100.0’) + sal;

  • NUMBER 转换为 CHAR

使用 TO_CHAR 函数可以实现数字到字符的转换,如:

v_comm := TO_CHAR(‘123.45’) || ’元’ ;

  • 字符转换为日期

使用 TO_DATE 函数可以实现字符到日期的转换,如:

v_date := TO_DATE(‘2001.07.03’,‘yyyy.mm.dd’);

  • 日期转换为字符

使用 TO_CHAR 函数可以实现日期到字符的转换,如:

v_to_day := TO_CHAR(SYSDATE, ‘yyyy.mm.dd hh24:mi:ss’) ;

6、变量作用范围及可见性

在 PL/SQL 编程中,如果在变量的定义上没有做到统一的话,可能会隐藏一些危险的错误,这样的原因主要是变量的作用范围所致。与其它高级语言类似,PL/SQL 的变量作用范围特点是:

  • 变量的作用范围是在你所引用的程序单元(块、子程序、包)内。即从声明变量开始到该块的结束。

  • 一个变量(标识)只能在你所引用的块内是可见的。

  • 当一个变量超出了作用范围,PL/SQL 引擎就释放用来存放该变量的空间(因为它可能不用了)。

  • 在子块中重新定义该变量后,它的作用仅在该块内。

7、注释

1)使用双 ‘-‘ ( 减号) 加注释

PL/SQL允许用 – 来写注释,它的作用范围是只能在一行有效。如:

V_Sal NUMBER(12,2); – 工资变量。

2)使用 /************/ 来加一行或多行注释,如:

/* 文件名: department_salary.sql */

/***********************************************/

提示:被解释存放在数据库中的 PL/SQL 程序,一般系统自动将程序头部的注释去掉。

只有在 PROCEDURE 之后的注释才被保留;另外程序中的空行也自动被去掉。

8、简单例子

通过变量实现delete,update,insert

(1)数据插入
DECLARE
v_ename VARCHAR2(20) := ‘Bill’;
v_sal NUMBER(7,2) :=1234.56;
v_deptno NUMBER(2) := 10;
v_empno NUMBER(4) := 8888;
BEGIN
INSERT INTO emp ( empno, ename, JOB, sal, deptno , hiredate ) 
VALUES ( v_empno, v_ename, ‘Manager’, v_sal, v_deptno, 
TO_DATE(’1954.06.09’,’yyyy.mm.dd’) );
COMMIT;
END;
(2)数据更新
declarev_emp_id number(10);begin v_emp_id :=123;update employeesset salary = salary + 100where employee_id = v_emp_id;dbms_output.put_line('执行成功');
end;执行成功PL/SQL procedure successfully completed
(3)数据删除
DECLARE
v_empno number(4) := 8888;
BEGIN
DELETE FROM emp WHERE empno=v_empno;
COMMIT;
END;

三、PL/SQL 流程控制语句

介绍 PL/SQL 的流程控制语句, 包括如下三类:

  • 控制语句: IF 语句
  • 循环语句: LOOP 语句, EXIT 语句
  • 顺序语句: GOTO 语句, NULL 语句

1、条件语句

(1)if表达式

(更具有通用性)

IF <布尔表达式> THEN
PL/SQL 和 SQL 语句;
END IF;IF <布尔表达式> THEN
PL/SQL 和 SQL 语句;
ELSE
其它语句;
END IF;IF <布尔表达式> THEN
PL/SQL 和 SQL 语句;
ELSIF < 其它布尔表达式> THEN
其它语句;
ELSIF < 其它布尔表达式> THEN
其它语句;
ELSE
其它语句;
END IF;

注意:ELSIF 不能写成 ELSEIF

要求:查询出 150号 员工的工资, 若其工资大于或等于 10000 则打印 ‘salary >= 10000’;

若在 5000 到 10000 之间, 则打印 ‘5000<= salary < 10000’; 否则打印 ‘salary < 5000’

法一:
declarev_sal employees.salary%type;beginselect salary into v_sal from employees where employee_id = 150;if v_sal >=1000 then dbms_output.put_line('salary >=10000');elsif v_sal >=5000 then dbms_output.put_line('5000=10000PL/SQL procedure successfully completed
法二:
declarev_sal employees.salary%type;v_temp varchar2(30);beginselect salary into v_sal from employees where employee_id = 150;if v_sal >=1000 then v_temp := 'salary >=10000';elsif v_sal >=5000 then v_temp := '5000=10000PL/SQL procedure successfully completed
(2)case表达式

限制因素很多,when和then后面不能写范围,只能写具体的值

CASE selector
WHEN expression1 THEN result1
WHEN expression2 THEN result2WHEN expressionN THEN resultN
[ ELSE resultN+1]
END;
上一题的法三:错误写法:
declarev_sal employees.salary%type;v_temp varchar2(30);beginselect salary into v_sal from employees where employee_id = 150;case v_sal when v_sal >=10000 then v_temp := 'salary >=10000'when v_sal >=5000 then v_temp := '5000<=salary<10000'else v_temp := 'salary<5000'end;dbms_output.put_line(v_sal||','||v_temp);end;注意:用case when的时候,when和then后面不可以放变量,只能写具体的值。并且,then后面不可以写dbms_output.put_line()正确写法:
declarev_sal employees.salary%type;v_temp varchar2(30);beginselect salary into v_sal from employees where employee_id = 150;v_temp :=case trunc(v_sal/5000) when 0 then  'salary<5000'when 1 then '5000<=salary<10000'else 'salary >=10000'end;dbms_output.put_line(v_sal||','||v_temp);end;
查询出 122 号员工的 JOB_ID, 若其值为 'IT_PROG', 则打印 'GRADE A'; 'AC_MGT', 打印 'GRADE B', 'AC_ACCOUNT', 打印 'GRADE C'; 否则打印 'GRADE D'declarev_job_id varchar2(10);v_temp varchar2(10);beginselect job_id into v_job_id from employees where employee_id = 122;v_temp:=case v_job_id when 'IT_PROG' then 'GRADE A'when 'AC_MGT' then 'GRADE B'when 'AC_ACCOUNT' then 'GRADE C'else 'GRADE D'end;dbms_output.put_line(v_job_id || ',' ||v_temp);end;                     ST_MAN,GRADE DPL/SQL procedure successfully completed

2、循环语句

(1)简单循环
LOOP要执行的语句;
EXIT WHEN <条件语句> ; /*条件满足,退出循环语句*/
END LOOP;
  • 使用简单循环打印1-100
--①初始化条件②循环体③循环条件④迭代条件
declare--①v_i number(5):=1;
beginloop--②dbms_output.put_line(v_i);--③    exit when v_i >=100;--④v_i :=v_i+1;end loop;end;
--①初始化条件②循环体③循环条件④迭代条件
declare--①v_i number(5):=1;
beginloop--②dbms_output.put_line(v_i);--④v_i :=v_i+1;--③    exit when v_i >=100;end loop;end;
此时打印出了1-99
(2)while循环

(相较简单循环,推荐使用while循环)

WHILE <布尔表达式> LOOP要执行的语句;
END LOOP;
  • 使用while循环打印1-100
--①初始化条件②循环体③循环条件④迭代条件
declare--①v_i number(5):=1;
begin--③while v_i <=100 loop--②dbms_output.put_line(v_i);--④v_i :=v_i+1;  end loop;end;
(3)for循环
FOR 循环计数器 IN [ REVERSE ] 下限 .. 上限 LOOP要执行的语句;
END LOOP;

每循环一次,循环变量自动加 1;使用关键字REVERSE,循环变量自动减 1。跟在 IN REVERSE 后面的数字必须是从小到大的顺序,而且必须是整数,不能是变量或表达式。可以使用 EXIT 退出循环。

  • 使用for循环打印1-100
beginfor c in 1 ..100 loopdbms_output.put_line(c);end loop;end;
  • 打印100-1
beginfor c in reverse 1 ..100 loopdbms_output.put_line(c);end loop;end;
(4)练习

输出2-100之间的质数(质数:只能被1和本身整除)

while循环:declarev_i number(3) :=2;v_j number(3) :=2;v_flag number(1) :=1;
beginwhile v_i <=100 loopwhile v_j <=sqrt(v_i) loopif v_i mod v_j = 0 then v_flag :=0;end if;v_j := v_j +1;end loop;if v_flag = 1 then dbms_output.put_line(v_i);end if;v_j :=2;v_i := v_i+1;v_flag :=1;end loop;end;
for循环:declarev_flag number(1) :=1;
beginfor v_i in 2 .. 100 loopfor v_j in 2 .. sqrt(v_i) loopif mod(v_i,v_j) = 0 then v_flag :=0;end if;end loop;if v_flag = 1 then dbms_output.put_line(v_i);end if;v_flag :=1;end loop;end;

3、标号和 GOTO

PL/SQL 中 GOTO 语句是无条件跳转到指定的标号去的意思。语法如下:

GOTO label;
. . . . . .
<
使用goto语句输出2-100之间的质数:
declarev_flag number(1) :=1;
beginfor v_i in 2 .. 100 loopfor v_j in 2 .. sqrt(v_i) loopif mod(v_i,v_j) = 0 then v_flag :=0;goto label;end if;end loop;<

打印1——100的自然数,当打印到50时,跳出循环,输出“打印结束”

beginfor i in 1 .. 100 loopif i = 50 then goto label;end if;dbms_output.put_line(i);end loop;<
beginfor i in 1 .. 100 loopif i = 50 then dbms_output.put_line('打印结束'); exit;end if;dbms_output.put_line(i);end loop;end;

四、游标的使用

在 PL/SQL 程序中,对于处理多行记录的事务经常使用游标来实现。

1、游标的概念

游标是一个指向上下文的句柄( handle)或指针。

对于不同的 SQL 语句,游标的使用情况不同:

SQL 语句游标
非查询语句隐式的
结果是单行的查询语句隐式的或显式的
结果是多行的查询语句显式的

2、显式游标

(1)显示游标处理

显式游标处理需四个 PL/SQL 步骤:

  • 定义游标:就是定义一个游标名,以及与其相对应的 SELECT 语句。

格式:

CURSOR cursor_name[(parameter[, parameter]…)] IS select_statement;

游标参数只能为输入参数,其格式为:

parameter_name [IN] datatype [{:= | DEFAULT} expression]

==在指定数据类型时,不能使用长度约束。==如NUMBER(4)、CHAR(10) 等都是错误的。

  • 打开游标:就是执行游标所对应的 SELECT 语句,将其查询结果放入工作区,并且指针指向工作区的首部,标识游标结果集合。如果游标查询语句中带有 FOR UPDATE 选项,OPEN 语句还将锁定数据库表中游标结果集合对应的数据行。

格式:

OPEN cursor_name[([parameter =>] value[, [parameter =>] value]…)];

在向游标传递参数时,可以使用与函数参数相同的传值方法,即位置表示法和名称表示 法。

PL/SQL 程序不能用 OPEN 语句重复打开一个游标。

  • 提取游标数据:就是检索结果集合中的数据行,放入指定的输出变量中。

格式:

FETCH cursor_name INTO {variable_list | record_variable };
  • 对该记录进行处理;

  • 继续处理,直到活动集合中没有记录;

  • 关闭游标:当提取和处理完游标结果集合数据后,应及时关闭游标,以释放该游标所占用的系统资源,并使该游标的工作区变成无效,不能再使用 FETCH 语句取其中数据。关闭后的游标可以使用 OPEN 语句重新打开。

格式:

CLOSE cursor_name;

注:定义的游标不能有 INTO 子句

例:打印出 80 部门的所有的员工的工资:salary: xxx

declarev_sal employees.salary%type;v_empid employees.employee_id%type;--定义游标cursor emp_sal_cursor is select salary,employee_id from employees where department_id = 80;
begin--打开游标open emp_sal_cursor;--提取游标fetch emp_sal_cursor into v_sal,v_empid;while emp_sal_cursor%found loopdbms_output.put_line('empid:'||v_empid||' salary:'||v_sal);fetch emp_sal_cursor into v_sal,v_empid;end loop;----关闭游标close emp_sal_cursor;
end;
declare--声明一个记录类型type emp_record is record(v_sal employees.salary%type,v_empid employees.employee_id%type,v_hiredate employees.hire_date%type    );--声明一个记录类型的变量v_emp_record emp_record;--定义游标cursor emp_sal_cursor is select salary,employee_id,hire_date from employees where department_id = 80;
begin--打开游标open emp_sal_cursor;--提取游标fetch emp_sal_cursor into v_emp_record;while emp_sal_cursor%found loopdbms_output.put_line('empid:'||v_emp_record.v_empid||' salary:'||v_emp_record.v_sal||' hire_date:'||v_emp_record.v_hiredate);fetch emp_sal_cursor into v_emp_record;end loop;----关闭游标close emp_sal_cursor;
end;     
(2)游标属性

%FOUND 布尔型属性,当最近一次读记录时成功返回,则值为 TRUE;

%NOTFOUND 布尔型属性,与%FOUND 相反;

%ISOPEN 布尔型属性,当游标已打开时返回 TRUE;

%ROWCOUNT 数字型属性,返回已从游标中读取的记录数。

  • 给工资低于 3000 的员工工资调为 3000。

在这里插入图片描述

(3)游标的for循环

PL/SQL 语言提供了游标 FOR 循环语句,自动执行游标的 OPENFETCHCLOSE 语句和循环语句的功能;当进入循环时,游标 FOR 循环语句自动打开游标,并提取第一行游标数据,当程序处理完当前所提取的数据而进入下一次循环时,游标 FOR 循环语句自动提取下一行数据供程序处理,当提取完结果集合中的所有数据行后结束循环,并自动关闭游标。

格式:

FOR index_variable IN cursor_name[value[, value]…] LOOP
-- 游标数据处理代码
END LOOP;

其中:
index_variable 为游标 FOR 循环语句隐含声明的索引变量,该变量为记录变量,其结构与游标查询语句返回的结构集合的结构相同。

在程序中可以通过引用该索引记录变量元素来读取所提取的游标数据,index_variable 中各元素的名称与游标查询语句选择列表中所制定的列名相同。

如果在游标查询语句的选择列表中存在计算列,则必须为这些计算列指定别名后才能通过游标 FOR 循环语句中的索引变量来访问这些列数据。

注:不要在程序中对游标进行人工操作;不要在程序中定义用于控制 FOR 循环的记录

  • 打印出 80 部门的所有的员工的工资:salary: xxx
declare--定义游标cursor emp_sal_cursor is select salary,employee_id,hire_date from employees where department_id = 80;
beginfor c in emp_sal_cursor loopdbms_output.put_line('empid:'||c.employee_id||' salary:'||c.salary||' hire_date:'||c.hire_date);end loop;end;     
  • 利用游标, 调整公司中员工的工资:
工资范围调整基数
0 - 50005%
5000 - 100003%
10000 - 150002%
15000 -1%
declarecursor emp_sal_cursor is select employee_id,salary from employees;--用于记录调整基数v_temp number(4,2);v_empid employees.employee_id%type;v_sal employees.salary%type;
beginopen emp_sal_cursor;fetch emp_sal_cursor into v_empid,v_sal;while emp_sal_cursor%found loopif v_sal < 5000 then v_temp :=0.05;elsif v_sal < 10000 then v_temp :=0.03;elsif v_sal < 15000 then v_temp :=0.02;else v_temp :=0.01;end if;update employeesset salary = salary * (1 + v_temp)where employee_id = v_empid;fetch emp_sal_cursor into v_empid,v_sal;end loop;close emp_sal_cursor     
end;
使用SQL中的 decode 函数update employees set salary = salary * (1 + (decode(trunc(salary/5000), 0, 0.05,1, 0.03,2, 0.02,0.01)))
使用游标的for循环:declarecursor emp_sal_cursor is select employee_id,salary from employees;--用于记录调整基数v_temp number(4,2);beginfor c in emp_sal_cursor loopif c.salary < 5000 then v_temp :=0.05;elsif c.salary < 10000 then v_temp :=0.03;elsif c.salary < 15000 then v_temp :=0.02;else v_temp :=0.01;end if;update employeesset salary = salary * (1 + v_temp)where employee_id = c.employee_id;end loop;     end;
1)带参数的游标
declare--定义游标cursor emp_sal_cursor(dept_id number, sal number) is select salary + 1000 sal, employee_id id from employees where department_id = dept_id and salary > sal;--定义基数变量temp number(4, 2);
begin--处理游标的循环操作for c in emp_sal_cursor(sal => 4000, dept_id => 80) loop--判断员工的工资, 执行 update 操作--dbms_output.put_line(c.id || ': ' || c.sal);if c.sal <= 5000 thentemp := 0.05;elsif c.sal <= 10000 thentemp := 0.03;   elsif c.sal <= 15000 thentemp := 0.02;elsetemp := 0.01;end if;dbms_output.put_line(c.sal || ': ' || c.id || ', ' || temp);--update employees set salary = salary * (1 + temp) where employee_id = c.id;end loop;
end;

当所声明的游标带有参数时,通过游标 FOR 循环语句为游标传递参数。

在这里插入图片描述

或者:

在这里插入图片描述

2)使用子查询

PL/SQL 还允许在游标 FOR 循环语句中使用子查询来实现游标的功能。

beginfor v_emp in (select last_name,salary from employees) loopdbms_output.put_line(v_emp.last_name||','||v_emp.salary);end loop;
end;

3、隐式游标

​ 显式游标主要是用于对查询语句的处理,尤其是在查询结果为多条记录的情况下;而对于非查询语句,如修改、删除操作,则由 ORACLE 系统自动地为这些操作设置游标并创建其工作区,这些由系统隐含创建的游标称为隐式游标,隐式游标的名字为 SQL,这是由 ORACLE 系统定义的。

​ 对于隐式游标的操作,如定义、打开、取值及关闭操作,都由 ORACLE 系统自动地完成,无需用户进行处理。用户只能通过隐式游标的相关属性,来完成相应的操作。在隐式游标的工作区中,所存放的数据是与用户自定义的显示游标无关的、最新处理的一条 SQL 语句所包含的数据。

  • 格式调用为: SQL%

  • 隐式游标属性

    • SQL%FOUND 布尔型属性,当最近一次读记录时成功返回,则值为 TRUE;
    • SQL%NOTFOUND 布尔型属性,与%FOUND 相反;
    • SQL %ROWCOUNT 数字型属性, 返回已从游标中读取得记录数;
    • SQL %ISOPEN 布尔型属性, 取值总是 FALSE。SQL 命令执行完毕立即关闭隐式游标
  • 更新指定员工 salary(涨工资 10),如果该员工没有找到,则打印”查无此人” 信息

beginupdate employeesset salary = salary + 10where employee_id = 101;if sql%notfound then dbms_output.put_line('查无此人');end if;
end;PL/SQL procedure successfully completedbeginupdate employeesset salary = salary + 10where employee_id = 1001;if sql%notfound then dbms_output.put_line('查无此人');end if;
end;查无此人PL/SQL procedure successfully completed

4、NO_DATA_FOUND 和 %NOTFOUND 的区别

  • SELECT … INTO 语句触发 NO_DATA_FOUND;

  • 当一个显式游标的 WHERE 子句未找到时触发%NOTFOUND;

  • 当 UPDATE 或 DELETE 语句的 WHERE 子句未找到时触发 SQL%NOTFOUND;

  • 在提取循环中要用 %NOTFOUND 或%FOUND 来确定循环的退出条件,不要用 NO_DATA_FOUND。

五、异常错误处理

一个优秀的程序都应该能够正确处理各种出错情况,并尽可能从错误中恢复。

ORACLE 提供异常情况(EXCEPTION)和异常处理(EXCEPTION HANDLER)来实现错误处理。

1、异常处理概念

异常情况处理(EXCEPTION)是用来处理正常执行过程中未预料的事件,程序块的异常处理预定义的错误和自定义错误,由于 PL/SQL 程序块一旦产生异常而没有指出如何处理时,程序就会自动终止整个程序运行。

(1)有三种类型的异常错误:

  • 预定义 ( Predefined )错误

ORACLE 预定义的异常情况大约有 24 个。对这种异常情况的处理,无需在程序中定义,由 ORACLE 自动将其引发。

  • 非预定义 ( Predefined )错误

即其他标准的 ORACLE 错误。对这种异常情况的处理,需要用户在程序中定义,然后由 ORACLE 自动将其引发。

  • 用户定义(User_define) 错误

程序执行过程中,出现编程人员认为的非正常情况。对这种异常情况的处理,需要用户在程序中定义,然后显式地在程序中将其引发。

(2)异常处理部分一般放在 PL/SQL 程序体的后半部,结构为:

EXCEPTIONWHEN first_exception THEN WHEN second_exception THEN WHEN OTHERS THEN 
END;

异常处理可以按任意次序排列,但 OTHERS 必须放在最后。

2、预定义的异常处理

预定义说明的部分 ORACLE 异常错误:
在这里插入图片描述
在这里插入图片描述

对这种异常情况的处理,只需在 PL/SQL 块的异常处理部分,直接引用相应的异常情况名,并对其完成相应的异常错误处理即可。

declarev_salary employees.salary%type;beginselect salary into v_salaryfrom employeeswhere employee_id > 100;dbms_output.put_line(v_salary);end;ORA-01422: exact fetch returns more than requested number of rows实际返回的行数超出请求的行数
ORA-06512: at line 5

因为如果想要返回多行应该用游标

所以程序更改为如下:

declarev_salary employees.salary%type;beginselect salary into v_salaryfrom employeeswhere employee_id > 100;dbms_output.put_line(v_salary);exceptionwhen too_many_rows then dbms_output.put_line('输出的行数太多了');when others then dbms_output.put_line('出现其他类型的异常');  
end;输出的行数太多了PL/SQL procedure successfully completed
  • 更新指定员工工资,如工资小于 300,则加 100;对 NO_DATA_FOUND 异常, TOO_MANY_ROWS 进行处理。

在这里插入图片描述

3、非预定义的异常处理

对于这类异常情况的处理,首先必须对非定义的 ORACLE 错误进行定义。步骤如下:

  • 在 PL/SQL 块的定义部分定义异常情况:

    <异常情况> EXCEPTION;

  • 将其定义好的异常情况,与标准的 ORACLE 错误联系起来,使用 PRAGMA EXCEPTION_INIT 语句:

    PRAGMA EXCEPTION_INIT(<异常情况>, <错误代码>);

  • 在 PL/SQL 块的异常情况处理部分对异常情况做出相应的处理。

例子:

SQL> delete from employees2  where employee_id = 100;
delete from employees
where employee_id = 100ORA-02292: integrity constraint (SCOTT.DEPT_MGR_FK) violated - child record found
违反完整约束条件(SCOTT.DEPT_MGR_FK) - 已找到子记录

因为100号员工还是别的员工的manager,所以产生异常

02292不是预定义的异常,所以此时需要自己定义异常

declaree_deleteid_exception exception;pragma exception_init(e_deleteid_exception,-2292);
begindelete from employees where employee_id = 100;
exceptionwhen e_deleteid_exception then dbms_output.put_line('违反完整性约束条件,故不可删除此用户');
end;  	 

4、用户自定义的异常处理

当与一个异常错误相关的错误出现时,就会隐含触发该异常错误。

用户定义的异常错误是通过显式使用 RAISE 语句来触发。

当引发一个异常错误时,控制就转向到 EXCEPTION 块异常错误部分,执行错误处理代码。

对于这类异常情况的处理,步骤如下:

  • 在 PL/SQL 块的定义部分定义异常情况:
    <异常情况> EXCEPTION;

  • RAISE <异常情况>;

  • 在 PL/SQL 块的异常情况处理部分对异常情况做出相应的处理。

练习1:

declaree_too_high_sal exception;v_sal employees.salary%type;beginselect salary into v_sal from employees where employee_id = 100;if v_sal > 10000 thenraise e_too_high_sal;end if;exceptionwhen e_too_high_sal then dbms_output.put_line('工资太高了');  end;

练习2:更新指定员工工资,增加 100;若该员工不存在则抛出用户自定义异常: no_result。

在这里插入图片描述

declare--自定义异常                                   no_result exception;   
beginupdate employees set salary = salary + 100 where employee_id = 1001;--使用隐式游标, 抛出自定义异常if sql%notfound thenraise no_result;end if;  exception--处理程序抛出的异常when no_result thendbms_output.put_line('更新失败');
end;

练习3:

declaree_too_high_sal exception;v_sal employees.salary%type;e_deleteid_exception exception;pragma exception_init(e_deleteid_exception,-2292); begindelete from employees where employee_id = 100;select salary into v_sal from employees where employee_id = 100;if v_sal > 10000 thenraise e_too_high_sal;end if;exceptionwhen e_too_high_sal then dbms_output.put_line('工资太高了');  when e_deleteid_exception then dbms_output.put_line('违反完整性约束条件,故不可删除此用户');when others then dbms_output.put_line('发生其他类型的异常');
end;先执行delete操作,所以结果如下:违反完整性约束条件,故不可删除此用户PL/SQL procedure successfully completed

5、练习:

(1)通过 select … into … 查询某人的工资, 若没有查询到, 则输出 “未找到数据”。

declarev_sal employees.salary%type;beginselect salary into v_sal from employees where employee_id = 1001;dbms_output.put_line(v_sal);end;ORA-01403: no data found
ORA-06512: at line 5

在预定义的异常中找到了1403,所以直接对异常进行处理即可

declarev_sal employees.salary%type;beginselect salary into v_sal from employees where employee_id = 1001;dbms_output.put_line(v_sal);
exceptionwhen no_data_found then dbms_output.put_line('查无此人');
end;

(2)更新指定员工工资,如工资小于300,则加100;对 NO_DATA_FOUND 异常, TOO_MANY_ROWS 进行处理.

declarev_sal employees.salary%type;beginselect salary into v_sal from employees where employee_id = 1001;if v_sal < 300 then update employees set salary = salary + 100 ;end if;
exceptionwhen no_data_found then dbms_output.put_line('查无此人');  when too_many_rows then dbms_output.put_line('输出的行数太多');  
end;

6、补充:在 PL/SQL 中使用 SQLCODE, SQLERRM

SQLCODE :返回错误代码数字

SQLERRM :返回错误信息.

如:

SQLCODE=-100 —> SQLERRM=’no_data_found ‘
SQLCODE=0 —> SQLERRM=’normal, successfual completion’

例: 将 ORACLE 错误代码及其信息存入错误代码表

CREATE TABLE errors (errnum NUMBER(4), errmsg VARCHAR2(100));
DECLAREerr_msg VARCHAR2(100);
BEGIN/* 得到所有 ORACLE 错误信息 */FOR err_num IN -100 .. 0 LOOPerr_msg := SQLERRM(err_num);INSERT INTO errors VALUES(err_num, err_msg);END LOOP;
END;
DROP TABLE errors;

例 :查询 ORACLE 错误代码

BEGININSERT INTO emp(empno, ename, hiredate, deptno)VALUES(2222, ‘Jerry’, SYSDATE, 20);DBMS_OUTPUT.PUT_LINE('插入数据记录成功!');INSERT INTO emp(empno, ename, hiredate, deptno)VALUES(2222, ‘Jerry’, SYSDATE, 20);DBMS_OUTPUT.PUT_LINE('插入数据记录成功!');
EXCEPTIONWHEN OTHERS THENDBMS_OUTPUT.PUT_LINE(SQLCODE||’---‘||SQLERRM);
END

六、存储函数和存储过程

  • ORACLE 提供可以把 PL/SQL 程序存储在数据库中,并可以在任何地方来运行它。这样就叫存储过程或函数。

  • 过程和函数统称为 PL/SQL 子程序,他们是被命名的 PL/SQL 块,均存储在数据库中,并通过输入、输出参数

    或输入/输出参数与其调用者交换信息。

  • 过程和函数的唯一区别是函数总向调用者返回数据,而过程则不返回数据。

  • [存储函数:有返回值,创建完成后,通过select function() from dual;执行]
    [存储过程:由于没有返回值,创建完成后,不能使用select语句,只能使用pl/sql块执行]

1、存储函数

(1)函数建立
CREATE [OR REPLACE] FUNCTION function_name[ (argment [ { IN | IN OUT }] Type,argment [ { IN | OUT | IN OUT } ] Type ][ AUTHID DEFINER | CURRENT_USER ]RETURN return_type { IS | AS }<类型.变量的说明>BEGINFUNCTION_bodyEXCEPTION其它语句END;

说明:

  • OR REPLACE 为可选. 有了它, 可以或者创建一个新函数或者替换相同名字的函数, 而不会出现冲突。

  • 函数名后面是一个可选的参数列表, 其中包含 IN, OUT 或 IN OUT 标记. 参数之间用逗号隔开。

  • IN 参数标记表示传递给函数的值在该函数执行中不改变;

    OUT 标记表示一个值在函数中进行计算并通过该参数传递给调用语句;

    IN OUT 标记表示传递给函数的值可以变化并传递给调用语句;

    若省略标记, 则参数隐含为 IN。

  • 因为函数需要返回一个值, 所以 RETURN 包含返回结果的数据类型.

--存储函数create or replace function func_name(dept_id number,salary number)
return number
is--函数使用过程中,需要声明的变量、记录类型、cursor
begin--函数的执行体
exception       --处理函数执行过程中的异常
end;       

例:不带参数的函数

create or replace function test_funreturn dateisv_date date;
beginselect sysdate into v_datefrom dual;dbms_output.put_line('我是函数哈');return v_date;end;declarev_date date;
beginv_date :=test_fun();dbms_output.put_line(v_date);
end;我是函数哈
17-DEC-20PL/SQL procedure successfully completed

例:获取某部门的工资总和:

create or replace function get_salary(dep_id employees.department_id%type,emp_count out number)return numberisv_sum number;beginselect sum(salary),count(*) into v_sum,emp_countfrom employeeswhere department_id = dep_id;return v_sum;exceptionwhen no_data_found thendbms_output.put_line('您需要的数据不存在'); when others then dbms_output.put_line(sqlcode || ',' || sqlerrm);end;         
(2)函数的调用

函数声明时所定义的参数称为形式参数,应用程序调用时为函数传递的参数称为实际参数

应用程序在调用函数时,可以使用以下三种方法向函数传递参数:

1)第一种参数传递格式称为位置表示法,格式为:

argument_value1[,argument_value2 …]

在这里插入图片描述

2)第二种参数传递格式称为名称表示法,格式为:

argument => parameter [,…]

其中:argument 为形式参数,它必须与函数定义时所声明的形式参数名称相同。Parameter 为实际参数。

在这种格式中,形势参数与实际参数成对出现,相互间关系唯一确定,所以参数的顺序可以任意排列。

在这里插入图片描述

3)第三种参数传递格式称为混合表示法
即在调用一个函数时,同时使用位置表示法和名称表示法为函数传递参数。

采用这种参数传递方法时,使用位置表示法所传递的参数必须放在名称表示法所传递的参数前面。也就是说,无论

函数具有多少个参数,只要其中有一个参数使用名称表示法,其后所有的参数都必须使用名称表示法。

在这里插入图片描述

无论采用哪一种参数传递方法,实际参数和形式参数之间的数据传递只有两种方法:传址法传值法

所谓传址法是指在调用函数时,将实际参数的地址指针传递给形式参数,使形式参数和实际参数指向内存中的同一区域,从而实现参数数据的传递。这种方法又称作参照法,即形式参数参照实际参数数据。输入参数均采用传址法传递数据。

传值法是指将实际参数的数据拷贝到形式参数,而不是传递实际参数的地址。默认时,输出参数和输入/输出参数均采用传值法。在函数调用时,ORACLE 将实际参数数据拷贝到输入/输出参数,而当函数正常运行退出时,又将输出形式参数和输入/输出形式参数数据拷贝到实际参数变量中。

(3)参数默认值

在CREATE OR REPLACE FUNCTION 语句中声明函数参数时可以使用DEFAULT关键字为输入参数指定默认值。

create or replace function get_salary(dep_id employees.department_id%type default 50,emp_count out number)return numberisv_sum number;beginselect sum(salary),count(*) into v_sum,emp_countfrom employeeswhere department_id = dep_id;return v_sum;exceptionwhen no_data_found thendbms_output.put_line('您需要的数据不存在'); when others then dbms_output.put_line(sqlcode || ',' || sqlerrm);end;         

具有默认值的函数创建后,在函数调用时,如果没有为具有默认值的参数提供实际参数值,函数将使用该参数的默认值。但当调用者为默认参数提供实际参数时,函数将使用实际参数值。在创建函数时,只能为输入参数设置默认值,而不能为输入/输出参数设置默认值。

declarev_num number;v_sum number;
beginv_sum :=get_salary(emp_count => v_num);dbms_output.put_line('50号部门是工资总和'||v_sum||',人数'||v_num);
end;50号部门是工资总和156400,人数45PL/SQL procedure successfully completed 
(4)练习

练习1:函数的 helloworld: 返回一个 “helloworld” 的字符串


create or replace function hello_world
return varchar2
is 
beginreturn 'helloworld';
end; 函数调用:
begindbms_output.put_line(hello_world);
end;helloworldPL/SQL procedure successfully completed或者用select语句调用:
SQL> select hello_world from dual;HELLO_WORLD
--------------------------------------------------------------------------------
helloworld

练习2:返回一个"helloworld MM"的字符串,其中MM由执行函数时输入。

create or replace function hello_world(v_logo varchar2)
return varchar2
is 
begindbms_output.put_line('人家是函数的啦,么么');return 'helloworld '||v_logo;
end;       Function created函数调用法一:
SQL> select hello_world('MM') from dual;HELLO_WORLD('MM')
--------------------------------------------------------------------------------
helloworld MM人家是函数的啦,么么函数调用法二:
begindbms_output.put_line(hello_world('MM'));
end;人家是函数的啦,么么
helloworld MMPL/SQL procedure successfully completed

练习3:创建一个存储函数,返回当前的系统时间

create or replace function get_sysdatereturn dateisv_date date;
begin v_date := sysdate;return v_date;
end;函数调用法1:
SQL> select get_sysdate from dual;GET_SYSDATE
-----------
2020/12/17函数调用法2:
declarev_date date;
beginv_date := get_sysdate();dbms_output.put_line(v_date);
end;17-DEC-20PL/SQL procedure successfully completed

练习4:

定义带参数的函数:两个数相加

create or replace function add_param(v_num1 number,v_num2 number)return numberisv_sum number(10);
beginv_sum := v_num1+v_num2;return v_sum;
end;       函数调用法1:SQL> select add_param(2,4) from dual;ADD_PARAM(2,4)
--------------6SQL> select add_param(2.1,2.2) from dual;ADD_PARAM(2.1,2.2)
------------------4   函数调用法2:
begin dbms_output.put_line(add_param(3,4));
end;7PL/SQL procedure successfully completed

练习5:

定义一个函数: 获取给定部门的工资总和, 要求:部门号定义为参数, 工资总额定义为返回值

函数创建法1:
create or replace  function get_sal(dept_id number)return numberisv_sumsal number(10) :=0;cursor salary_cursor is select salary from employees where department_id = dept_id;beginfor c in salary_cursor loopv_sumsal :=v_sumsal + c.salary;end loop;return v_sumsal;end;函数创建法2:
create or replace  function get_sal(dept_id number)return numberisv_sumsal number(10) :=0;v_sal number;cursor salary_cursor is select salary from employees where department_id = dept_id;beginopen salary_cursor;loopfetch  salary_cursor into v_sal;exit when salary_cursor%notfound;v_sumsal :=v_sumsal + v_sal;end loop;close salary_cursor;return v_sumsal;end;函数调用法1:
SQL> select get_sal(80) from dual;GET_SAL(80)
-----------304500函数调用法2:
begindbms_output.put_line(get_sal(80));
end;函数调用法3:
declarev_deptid number(4) := 80;begin dbms_output.put_line(get_sal(v_deptid));end;

练习6:

要求1: 定义一个函数: 获取给定部门的工资总和 和 该部门的员工总数(定义为 OUT 类型的参数)。

要求2: 部门号定义为参数,工资总额定义为返回值。

关于 OUT 型的参数: 因为函数只能有一个返回值, PL/SQL 程序可以通过 OUT 型的参数实现有多个返回值

create or replace  function get_sal1(dept_id number,total_count out number)return numberisv_sumsal number(10) :=0;cursor salary_cursor is select salary from employees where department_id = dept_id;begintotal_count :=0;for c in salary_cursor loopv_sumsal :=v_sumsal + c.salary;total_count :=total_count + 1;end loop;return v_sumsal;end;函数调用:
declarev_num number(5) :=0;
begindbms_output.put_line(get_sal1(80,v_num));dbms_output.put_line(v_num);
end;    304500
34PL/SQL procedure successfully completed

2、存储过程

在 ORACLE SERVER 上建立存储过程,可以被多个应用程序调用。

可以向存储过程传递参数,也可以向存储过程传回参数。

(1)创建过程
CREATE [OR REPLACE] PROCEDURE Procedure_name[ (argment [ { IN | IN OUT }] Type,argment [ { IN | OUT | IN OUT } ] Type ][ AUTHID DEFINER | CURRENT_USER ]{ IS | AS }<类型.变量的说明>
BEGIN<执行部分>
EXCEPTION<可选的异常错误处理程序>
END;
(2)调用存储过程

ORACLE 使用 EXECUTE 语句来实现对存储过程的调用:

EXEC[UTE] Procedure_name( parameter1, parameter2…);
(3)删除过程和函数
1)删除过程

可以使用 DROP PROCEDURE 命令对不需要的过程进行删除,语法如下:

DROP PROCEDURE [user.]Procudure_name;
2)删除函数

可以使用 DROP FUNCTION 命令对不需要的函数进行删除,语法如下:

DROP FUNCTION [user.]Function_name;
练习:

练习1:

定义一个存储过程: 获取给定部门的工资总和(通过 out 参数), 要求:部门号和工资总额定义为参数

create or replace procedure get_sal2(dept_id number,sumsal out number)iscursor salary_cursor is select salary from employees where department_id = dept_id;beginsumsal :=0;for c in salary_cursor loopsumsal :=sumsal + c.salary;end loop;dbms_output.put_line(sumsal);end;调用存储过程:
因为不需要传递变量,所以无法用select语句调用declarev_sal number(10) :=0;
beginget_sal2(80,v_sal);
end;

练习2:

自定义一个存储过程完成以下操作:

对给定部门(作为输入参数)的员工进行加薪操作, 若其到公司的时间在 (? , 95) 期间, 为其加薪 %5

​ [95 , 98) %3

​ [98, ?) %1
得到以下返回结果: 为此次加薪公司每月需要额外付出多少成本(定义一个 OUT 型的输出参数)。

create or replace procedure add_sal(dept_id number,temp out number)
iscursor sal_cursor is select employee_id,salary,hire_date from employees where department_id = dept_id;v_i number(4,2) :=0;
begintemp :=0;for c in sal_cursor loop----纯数字的字符串有一个隐式转化,也可以比较大小if to_char(c.hire_date,'yyyy') < '1995' then  v_i := 0.05;elsif to_char(c.hire_date,'yyyy') < '1998' then v_i := 0.03;else v_i := 0.01;end if;----1、更新工资update employees set salary = salary * (1+v_i) where employee_id = c.employee_id;----2、付出的成本temp := temp + c.salary * v_i;                 end loop;dbms_output.put_line(temp);end;       注意:如果创建存储过程或者创建存储函数时报错,可以用show errors
SQL> show errors;
Errors for PROCEDURE SCOTT.ADD_SAL:
LINE/COL ERROR
-------- -------------------------------------------------------------------
6/8      PLS-00363: expression 'TEMP' cannot be used as an assignment target
6/8      PL/SQL: Statement ignored
17/12    PLS-00363: expression 'TEMP' cannot be used as an assignment target
17/12    PL/SQL: Statement ignored函数调用:
declarev_temp number(10):=0;
beginadd_sal(80,v_temp);
end;6121PL/SQL procedure successfully completed

七、触发器

触发器是许多关系数据库系统都提供的一项技术。在 ORACLE 系统里,触发器类似过程和函数,都有声明,执行和异常处理过程的 PL/SQL 块。

1、触发器概述

  • 触发器在数据库里以独立的对象存储,它与存储过程不同的是,存储过程通过其它程序来启动运行或直接启动运行,而触发器是由一个事件来启动运行。即触发器是当某个事件发生时自动地隐式运行。

  • 并且,触发器不能接收参数。所以运行触发器就叫触发或点火(firing)。

  • ORACLE 事件指的是对数据库的表进行的INSERT、UPDATE 及 DELETE 操作或对视图进行类似的操作。

    ORACLE 将触发器的功能扩展到了触发 ORACLE,如数据库的启动与关闭等。

2、触发器类型

  • DML 触发器

    ORACLE 可以在 DML 语句进行触发,可以在 DML 操作前或操作后进行触发,并且可以对每个行或语句操作上进行触发。

  • 替代触发器

    由于在 ORACLE 里,不能直接对由两个以上的表建立的视图进行操作。所以给出了替代触发器。

  • 系统触发器

    它可以在 ORACLE 数据库系统的事件中进行触发,如 ORACLE 系统的启动与关闭等。

3、触发器组成

  • 触发事件:即在何种情况下触发 TRIGGER; 例如:INSERT, UPDATE, DELETE

  • 触发时间:即该 TRIGGER 是在触发事件发生之前(BEFORE)还是之后(AFTER)触发,也就是触发事件和该 TRIGGER 的操作顺序。

  • 触发器本身:即该 TRIGGER 被触发之后的目的和意图,正是触发器本身要做的事情。 例如:PL/SQL 块。

  • 触发频率:说明触发器内定义的动作被执行的次数。即语句级(STATEMENT)触发器行级(ROW)触发器

    • 语句级(STATEMENT)触发器:是指当某触发事件发生时,该触发器只执行一次;

    • 行级(ROW)触发器:是指当某触发事件发生时,对受到该操作影响的每一行数据,触发器都单独执行一 次。

4、创建触发器

创建触发器的一般语法:

CREATE [OR REPLACE] TRIGGER trigger_name
{BEFORE | AFTER }
{INSERT | DELETE | UPDATE [OF column [, column …]]}
ON [schema.] table_name 
[FOR EACH ROW ]
[WHEN condition]
trigger_body;

其中:

1)BEFORE 和 AFTER 指出触发器的触发时序分别为前触发后触发方式。前触发是在执行触发事件之前触发当前所创建的触发器,后触发是在执行触发事件之后触发当前所创建的触发器。

2)FOR EACH ROW 选项说明触发器为行触发器。行触发器和语句触发器的区别表现在:行触发器要求当一个 DML 语句操做影响数据库中的多行数据时,对于其中的每个数据行,只要它们符合触发约束条件,均激活一次触发器;而语句触发器将整个语句操作作为触发事件,当它符合约束条件时,激活一次触发器。当省略 FOR EACH ROW 选项时,BEFORE 和 AFTER 触发器为语句触发器,而 INSTEAD OF 触发器则为行触发器。

3)WHEN 子句说明触发约束条件。Condition 为一个逻辑表达时,其中必须包含相关名称,而不能包含查询语句,也不能调用 PL/SQL 函数。WHEN 子句指定的触发约束条件只能用在 BEFORE 和 AFTER 行触发器中,不能用在 INSTEAD OF 行触发器和其它类型的触发器中。

4)当一个基表被修改( INSERT, UPDATE, DELETE)时要执行的存储过程,执行时根据其所依附的基表改动而自动触发,因此与应用程序无关,用数据库触发器可以保证数据的一致性和完整性。

  • 每张表最多可建立 12 种类型的触发器,它们是:
BEFORE INSERT
BEFORE INSERT FOR EACH ROW
AFTER INSERT
AFTER INSERT FOR EACH ROW
BEFORE UPDATE
BEFORE UPDATE FOR EACH ROW
AFTER UPDATE
AFTER UPDATE FOR EACH ROW
BEFORE DELETE
BEFORE DELETE FOR EACH ROW
AFTER DELETE
AFTER DELETE FOR EACH ROW

例1:

行级触发:每更新 employees 表中的一条记录, 都会导致触发器执行

create or replace trigger update_emp_trigger
afterupdate on employees
for each row
begindbms_output.put_line('helloworld');       
end;SQL> update employees set salary = salary + 100 where department_id = 80;
此时出现34条helloworld

语句级触发:一个 update/delete/insert 语句只使触发器执行一次

create or replace trigger update_emp_trigger1
afterupdate on employees
--for each row
begindbms_output.put_line('helloworld');       
end;SQL> update employees set salary = salary + 100 where department_id = 80;helloworld34 rows updated

例2:触发器的 helloworld: 编写一个触发器, 在向 employees1表中插入记录时, 打印 ‘helloworld’

SQL> create table employees12  as3  select4  employee_id,salary,email from employees5  where department_id = 80;Table createdcreate or replace trigger update_emp_trigger2
afterinsert on employees1
--for each row
begindbms_output.put_line('helloworld');       
end;SQL> insert into employees12  values(1001,1000,'MM@mail.com');helloworld1 row inserted
(1)创建 DML 触发器

1)触发器名可以和表或过程有相同的名字,但在一个模式中触发器名不能相同。

2)触发器的限制

  • CREATE TRIGGER 语句文本的字符长度不能超过 32KB;
  • 触发器体内的 SELECT 语句只能为 SELECT … INTO …结构,或者为定义游标所使用的 SELECT 语句。
  • 触发器中不能使用数据库事务控制语句 COMMIT; ROLLBACK, SVAEPOINT 语句;
  • 由触发器所调用的过程或函数也不能使用数据库事务控制语句;

问题:当触发器被触发时,要使用被插入、更新或删除的记录中的列值,有时要使用操作前、后列的值。

实现:

:NEW修饰符访问操作完成后列的值
:OLD修饰符访问操作完成前列的值
特性INSERTUPDATEDELETE
OLDNULL有效有效
NEW有效有效NULL

例1: 使用 :new, :old 修饰符

create or replace trigger update_emp_trigger2
afterupdate on employees1
for each row
begindbms_output.put_line('old salary:'||:old.salary||','||'new salary:'||:new.salary);       
end;SQL> update employees1 set salary = salary + 100;

例2:编写一个触发器, 在对 my_emp 记录进行删除的时候, 在 my_emp_bak 表中备份对应的记录

create table my_emp
as
select employee_id,salary from employeescreate table my_emp_back
as
select employee_id ,salary from employees
where 1=2create or replace trigger delete_emp_trigger
before
delete on my_emp
for each row
begininsert into my_emp_backvalues(:old.employee_id,:old.salary);
end;    SQL> delete from my_emp;105 rows deletedSQL> select * from my_emp_back;
此时有105条记录
(2)创建替代(INSTEAD OF)触发器
CREATE [OR REPLACE] TRIGGER trigger_name
INSTEAD OF
{INSERT | DELETE | UPDATE [OF column [, column …]]}
ON [schema.] view_name
[FOR EACH ROW ]
[WHEN condition]
trigger_body;

注意:

  • INSTEAD OF 选项使 ORACLE 激活触发器,而不执行触发事件。只能对视图和对象视图建立 INSTEAD OF触发

    器,而不能对表、模式和数据库建立 INSTEAD OF 触发器。

  • FOR EACH ROW 选项说明触发器为行触发器。当省略 FOR EACH ROW 选项时,BEFORE 和 AFTER 触发器为

    语句触发器,而 INSTEAD OF 触发器则为行触发器。

  • WHEN 子句指定的触发约束条件只能用在 BEFORE 和 AFTER 行触发器中,不能用在 INSTEAD OF 行触发器和

    其它类型的触发器中。

(3)创建系统事件触发器

ORACLE 提供的系统事件触发器可以在 DDL 或数据库系统上被触发。

DDL 指的是数据定义语言,如CREATE 、ALTER 及 DROP 等。

而数据库系统事件包括数据库服务器的启动或关闭,用户的登录与退出、数据库服务错误等。

创建系统触发器的语法如下:

CREATE OR REPLACE TRIGGER [sachema.] trigger_name{BEFORE|AFTER} 
{ddl_event_list | database_event_list}ON { DATABASE | [schema.] SCHEMA }[WHEN_clause] trigger_body;

其中: ddl_event_list:一个或多个 DDL 事件,事件间用 OR 分开;

database_event_list:一个或多个数据库事件,事件间用 OR 分开;


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章