PL/SQL
PL/SQL 是一种高级数据库程序设计语言,该语言专门用于在各种环境下对 ORACLE 数据库进行访问 。由于该语言 集成于数据库服务器中 所以 PL/SQL 代码可以对数据进行快速高效的处理。
PL/SQL是Procedure Language & Structured Query Langu age 的缩写,是一种过程处理语言。 PL/SQL是对 SQL 语言存储过程语言的扩展语言的过程化扩展,指在 SQL 命令语言中增加了过程处理语句(如分支、循环等),使 SQL 语言具有过程处理能力。把 SQL 语言的数据操纵能力与过程语言的数据处理能力结合起来,使得 PLSQL 面向过程但比过程语言简单、高效、灵活和实用。 从 ORACLE6 以后, ORACLE 的 RDBMS 附带了 PL/SQL 。
PL/SQL块:
PL/SQL程序由三个块组成,即声明部分、执行部分、异常处理部分
PL/SQL块的结构如下:
DECLARE
声明部分 : 在此声明 PL/SQL 用到的变量 类型及游标,以及局部的存储过程和函数
BEGIN
执行部分 : 过程及 SQL 语句 , 即程序的主要部分
EXCEPTION
执行异常部分 : 错误处理
END;
其中执行部分是必须的。
变量
声明变量的语法:
变量名 类型(长度);
比如number(5,2)
3表示这个数据的有效位数(精度),2表示两个小数位(刻度)
例如:123.45
变量赋值的语法:
- 变量名 := 变量值;
- select 列名 into 变量名
一般要求被赋值的变量与SELECT 中的列名要一一对应 。
--变量声明与赋值
declare
v_price number(10,2);--单价
v_usenum number;--水费字数
v_usenum2 number(10,2);--吨数
v_money number(10,2);--金额
begin
v_price:=2.45;--单价赋值
v_usenum:=8953;--水费字数
v_usenum2:=round(v_usenum/1000,2);--吨数
v_money:=v_price*v_usenum2;--金额
DBMS_OUTPUT.put_line('金额:'||v_money);
end;
--select 列名 into 变量名
declare
v_price number(10,2);--单价
v_usenum number;--水费字数
v_usenum2 number(10,2);--吨数
v_money number(10,2);--金额
v_num0 number;--上月水表数
v_num1 number;--本月水表数
begin
v_price:=2.45;--单价赋值
--v_usenum:=8953;--水费字数
--从数据库中查询
select usenum,v_num0,v_num1 into v_usenum,v_num0,v_num1 from t_account
where year = '2012' and month='01' and owneruuid=1;
v_usenum2:=round(v_usenum/1000,2);--吨数
v_money:=v_price*v_usenum2;--金额
DBMS_OUTPUT.put_line('水费字数:'||v_usenum||' 金额:'||v_money);
end;
属性类型
属性类型:
- %TYPE引用型
作用:引用某表某列的字段类型
使用%TYPE 特性的优点在于:
所引用的数据库列的数据类型可以不必知道;
所引用的数据库列的数据类型可以实时改变。- %ROWTYPE 记录型
作用: 标识某个表的行记录类型
使用%ROWTYPE 特性的优点在于:
所引用的数据库中列的个数和数据类型可以不必知道;
所引用的数据库中列的个数和数据类型可以实时改变。
--属性类型(引用型 表名.列名%type)
declare
v_price number(10,2);--单价
v_usenum t_account.usenum%type;--水费字数
v_usenum2 number(10,2);--吨数
v_money number(10,2);--金额
v_num0 t_account.num0%type;--上月水表数
v_num1 t_account.num1%type;--本月水表数
begin
v_price:=2.45;--单价赋值
--从数据库中查询
select usenum,v_num0,v_num1 into v_usenum,v_num0,v_num1 from t_account
where year = '2012' and month='01' and owneruuid=1;
v_usenum2:=round(v_usenum/1000,2);--吨数
v_money:=v_price*v_usenum2;--金额
DBMS_OUTPUT.put_line('水费字数:'||v_usenum||' 金额:'||v_money);
end;
--属性类型 (记录型 表名%rowtype)
declare
v_price number(10,2);--单价
v_usenum2 number(10,2);--吨数
v_money number(10,2);--金额
v_account t_account%rowtype;--台账行记录类型
begin
v_price:=2.45;--单价赋值
--从数据库中查询
select * into v_account from t_account
where year = '2012' and month='01' and owneruuid=1;
v_usenum2:=round(v_account.usenum/1000,2);--吨数
v_money:=v_price*v_usenum2;--金额
DBMS_OUTPUT.put_line('水费字数:'||v_account.usenum||' 金额:'||v_money);
end;
记录类型
记录类型
记录类型是把逻辑相关 的数据作为一个单元存储起来 称作 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 customer_type is record(
v_cust_name varchar2(20),
v_cust_id number(10));
--声明自定义记录类型的变量
v_customer_type customer_type;
begin
v_customer_type.v_cust_name := '刘德华';
v_customer_type.v_cust_id := 1001;
dbms_output.put_line(v_customer_type.v_cust_name||','||v_customer_type.v_cust_id);
end;
异常
概念:异常情况处理(EXCEPTION)是用来处理正常执行过程中未预料的事件 程序块的异常处理预定义的错误和 自定义错误 由于 PL/SQL 程序块一旦产 生异常而没有指出如何处理时 程序就会自动终止整个程序运行。
分类:
1. 预定义 (Predefined)错误
ORACLE预定义的异常情况大约有 24 个。对这种异常情况的处理,无需在程序中定义, 由 ORACLE 自动将其引发 。
2. 非预定义 (Predefined)错误
即其他标准的ORACLE 错误。对这种异常情况的处理,需要用户在程序中定义,然后由 ORACLE 自动将其引发。
3. 用户定义 (User_define) 错误
程序执行过程中,出现编程人员认为的非正常情况。对这种异常情况的处理,需要 用户在程序中定义,然后显式地在程序中将其引发 。
异常处理部分一般放在PL/SQL 程序体的后半部结构为:
EXCEPTION
WHEN first_exception THEN code to handle first exception
WHEN second_exception THEN code to handle second exception
WHEN OTHERS THEN code to handle others exception
END
异常处理可以按任意次序排列但 OTHERS 必须放在最后
--异常处理1
declare
v_price number(10,2);--单价
v_usenum2 number(10,2);--吨数
v_money number(10,2);--金额
v_account t_account%rowtype;--台账行记录类型
begin
v_price:=2.45;--单价赋值
--从数据库中查询
select * into v_account from t_account
where year = '2012' and month='01' and owneruuid=200;
v_usenum2:=round(v_account.usenum/1000,2);--吨数
v_money:=v_price*v_usenum2;--金额
DBMS_OUTPUT.put_line('水费字数:'||v_account.usenum||' 金额:'||v_money);
exception
when no_data_found then
DBMS_OUTPUT.put_line('没有找到账务数据');
end;
--异常处理2
declare
v_price number(10,2);--单价
v_usenum2 number(10,2);--吨数
v_money number(10,2);--金额
v_account t_account%rowtype;--台账行记录类型
begin
v_price:=2.45;--单价赋值
--从数据库中查询
select * into v_account from t_account
where year = '2012' and month='01';
v_usenum2:=round(v_account.usenum/1000,2);--吨数
v_money:=v_price*v_usenum2;--金额
DBMS_OUTPUT.put_line('水费字数:'||v_account.usenum||' 金额:'||v_money);
exception
when no_data_found then
DBMS_OUTPUT.put_line('没有找到账务数据');
when too_many_rows then
dbms_output.put_line('返回了多行数据');
end;
条件判断
三种语法形式:
- 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
--条件判断
declare
v_price1 number(10,2);--单价
v_price2 number(10,2);--单价
v_price3 number(10,2);--单价
v_usenum2 number(10,2);--吨数
v_money number(10,2);--金额
v_account t_account%rowtype;--台账行记录类型
begin
v_price1:=2.45;--单价赋值(5吨以下)
v_price2:=3.45;--单价赋值(5-10吨)
v_price3:=4.45;--单价赋值(超多10吨)
--从数据库中查询
select * into v_account from t_account
where year = '2012' and month='01' and owneruuid=1;
v_usenum2:=round(v_account.usenum/1000,2);--吨数
--v_money:=v_price*v_usenum2;--金额
--阶梯水费计算
if v_usenum2<=5 then
v_money:=v_price1*v_usenum2;
elsif v_usenum2>5 and v_usenum2<=10 then
v_money:=v_price1*5+v_price2*(v_usenum2-5);
else
v_money:=v_price1*5 + v_price2*5 + v_price3*(v_usenum2-10);
end if;
DBMS_OUTPUT.put_line('水费字数:'||v_account.usenum||' 金额:'||v_money);
exception
when no_data_found then
DBMS_OUTPUT.put_line('没有找到账务数据');
when too_many_rows then
dbms_output.put_line('返回了多行数据');
end;
循环
- 简单循环
LOOP
要执行的语句;
EXIT WHEN 条件语句 > 条件满足,退出循环语句
END LOOP;- WHILE 循环 (相较 1 ,推荐使用 2)
WHILE <布尔表达式 > LOOP
要执行的语句
END LOOP;- 数字式循环
FOR 循环计数器 IN [REVERSE ] 下限 … 上限 LOOP
要执行的语句;
END LOOP;
每循环一次,循环变量自动加1;使用关键字 REVERSE ,循环变量自动减 1 。跟在 IN REVERSE 后面的数字必须是从小到大的顺序, 而且必须是整数,不能是变量或表达式 。可以使用 EXIT 退出循环。
标号和 GOTO
PL/SQL中 GOTO 语句是 无条件跳转到指定的标号去 的意思。语法如下:
GOTO label;
. . . . .
<< label >>标号是用 括起来的标识符
--无条件循环:输出1-100的数
declare
v_num number;
begin
v_num:=1;
loop
dbms_output.put_line(v_num);
v_num:=v_num+1;
/*方式一:
if v_num>100 then
exit;
end if;
*/
--方式二:
exit when v_num>100;
end loop;
end;
--有条件循环:输出1-100
declare
v_num number;
begin
v_num:=1;
while v_num <=100
loop
dbms_output.put_line(v_num);
v_num:=v_num+1;
end loop;
end;
--for循环
--declare
--自动声明变量
begin
for v_num in 1..100
loop
dbms_output.put_line(v_num);
end loop;
end;
--GOTO的示例
DECLARE
V_counter NUMBER := 1;
BEGIN
LOOP
DBMS_OUTPUT.PUT_LINE('V_counter 的当前值为 :'||V_
V_counter := v_counter + 1;
IF v_cou nter > 10 THEN
GOTO l_ENDofLOOP;
END IF;
END LOOP;
<<l_
DBMS_OUTPUT.PUT_LINE('V_counter 的当前值为 :'||V_
END ;
游标
含义:是系统为用户开设的一个数据缓冲区存放 SQL 语句的执行结果。 我们可以把游标理解为 PL/SQL 中的结果集。程序中,对于 处理多行记录 的事务经常使用游标来实现。
在声明区声明游标,语法如下:
CURSOR 游标名称 IS SQL 语句
使用游标语法:
OPEN 游标名称
LOOP
FETCH 游标名称 INTO 变量
EXIT WNEN 游标名称 %NOTFOUND
END LOOP;
CLOSE 游标名称
游标属性:
%FOUND 布尔型属性,当最近一次读记录时成功 返回 则值为 TRUE
%NOTFOUND 布尔型属性,与 %FOUND 相反;
%ISOPEN 布尔型属性,当游标已打开时返回 TRUE
%ROWCOUNT 数字型属性,返回已从游标中读取的记录数。
游标的 FOR 循环:
PL/SQL 语言提供了 游标 FOR 循环语句,自动执行游标的 OPEN 、 FETCH 、 CLOSE 语句和 循环 语句的功能;
当进入循环时,游标 FOR 循环语句 自动 打开游标,并提取第一行游标数据,当程序处理完 当前所提取的数据而进入下一次循环时,游标 FOR 循环语句自动提取下一行数据供程序处理,当提取完结果集合中的所有数据行后结束循环,并自动关闭游标 。
格式:
FOR index_variable IN cursor_name[value[, value]…]
LOOP
游标数据处理代码
END LOOP;
index_variable 为游标 FOR 循环语句隐含声明的索引变量,该变量为记录变量 ,其结构与游标查询语句返回的结构集合的结构相同。
--游标
--需求:打印业主类型为1的价格表
select * from t_pricetable ;
select * from t_pricetable where ownertypeid=1;
--游标 输出结果集
DECLARE
--声明游标
cursor cur_pricetable is select * from t_pricetable where ownertypeid=1;
v_pricetable t_pricetable%rowtype;
BEGIN
open cur_pricetable;--打开游标
loop
fetch cur_pricetable into v_pricetable;--提取游标
exit when cur_pricetable%notfound;--退出循环游标
dbms_output.put_line('价格:'||v_pricetable.price||
' 吨位:'||v_pricetable.minnum||'-'||v_pricetable.maxnum);
end loop;
close cur_pricetable;--关闭游标
END;
--带参数的游标
DECLARE
--声明带参游标
cursor cur_pricetable(v_ownertype number) is select * from t_pricetable where ownertypeid=v_ownertype;
v_pricetable t_pricetable%rowtype;
BEGIN
--打开游标时指定参数值
--方式一:
--open cur_pricetable(v_ownertype=>3);
--方式二:
open cur_pricetable(3);
--错误的
--open cur_pricetable(v_ownertype=3);
loop
fetch cur_pricetable into v_pricetable;--提取游标
exit when cur_pricetable%notfound;--退出循环游标
dbms_output.put_line('价格:'||v_pricetable.price||
' 吨位:'||v_pricetable.minnum||'-'||v_pricetable.maxnum);
end loop;
close cur_pricetable;--关闭游标
END;
--for循环 带参数的游标
DECLARE
--声明带参游标
cursor cur_pricetable(v_ownertype number) is select * from t_pricetable where ownertypeid=v_ownertype;
--变量的声明也可以省略
--v_pricetable t_pricetable%rowtype;
BEGIN
for v_pricetable in cur_pricetable(2)
loop
dbms_output.put_line('价格:'||v_pricetable.price||
' 吨位:'||v_pricetable.minnum||'-'||v_pricetable.maxnum);
end loop;
END;
存储函数
ORACLE 提供可以把 PL/SQL 程序存储在数据库中,并可以在任何地方来运行它。这样就叫存储过程或函数 。过程和函数统称为 PL/SQL 子程序,他们是被命名的 PL/SQL 块,均存 储在数据库中,并通过输入、输出参数或输入 输出参数与其调用者交换信息。 过程和函数的唯一区别是函数总向调用者返回数据,而过程则不返回数据 。
创建函数
语法如下:
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}
<类型 变量的说明>
BEGIN
FUNCTION_body
EXCEPTION
其它语句
END
说明
- OR REPLACE 为可选 . 有了它 , 可以或者创建一个新函数或者替换相同名字的函数 , 而不会出现冲突
- 函数名后面是一个可选的参数列表 , 其中包含 IN, OUT 或 IN OUT 标记 . 参数之间用逗号隔开。
IN模式:只读。在模块里面,实参的数值只能被引用,而这个参数不能被改变。
OUT模式:只写。 不能接收传入的实参值。标记表示一个值在函数中进行计算并通过该参数传递给调用语句 ;
IN OUT模式:可读写。 标记表示传递给函数的值可以变化并传递给调用语句。
若省略标记 , 则参数隐含为 IN 。 - 因为函数需要返回一个值 , 所以 RETURN 包含返回结果的数据类型
在创建存储过程 时 , 可使用 AUTHID CURRENT_USER 或 AUTHID DEFINER 选项 以表明在执行该过程时Oracle 使用的权限。
- 如果使用 AUTHID CURRENT_USER 选项创建一个过程 , 则 Oracle 用调用该过程的用户权限执行该过程 。为了成功执行该过程调用者 必须具有访问该存储过程体中引用的所有数据库对象所必须的权限
- 如果用默认的 AUTHID DEFINER 选项创建过程选项创建过程 , 则 Oracle 使用过程所有者的特权执行该过程.为了成功执行该过程为了成功执行该过程, 过程的所有者必须具有访问该存储过程体中引用的所有数据库对象所必须的权限. 想要简化应用程序用户的特权管理, 在创建存储过程时, 一般选择 AUTHID DEFINER选项 ,这样就不必授权给需要调用的此过程的所有用户了。
--存储函数 创建
create or replace function fn_getaddress(v_id number)
return varchar2
is
--声明变量
v_name varchar2(30);
begin
--查询地址表
select name into v_name from t_address where id=v_id;
return v_name;
end;
--自定义存储函数测试
select fn_getaddress(3) from dual;
--存储函数的应用
select id,name,fn_getaddress(addressid) from t_owners;
--out型参数的示例
/*
要求: 定义一个函数: 获取给定部门的工资总和 和 该部门的员工总数(定义为 OUT 类型的参数).
要求: 部门号定义为参数, 工资总额定义为返回值.
*/
create or replace function sum_sal(dept_id number, total_count out number)
return number
is
cursor sal_cursor is select salary from employees
where department_id = dept_id;
v_sum_sal number(8) := 0;
begin
total_count := 0;
for c in sal_cursor
loop
v_sum_sal := v_sum_sal + c.salary;
total_count := total_count + 1;
end loop;
--dbms_output.put_line('sum salary: ' || v_sum_sal);
return v_sum_sal;
end;
--执行函数:
delare
v_total number(3) := 0;
begin
dbms_output.put_line(sum_sal(80, v_total));
--返回的是函数执行后v_total的值
dbms_output.put_line(v_total);
end;
存储过程
建立存储过程
在 ORACLE SERVER 上建立存储过程可以被多个应用程序调用可以向存储过程传递参数 也可以向存储过程传回参数
创建过程语法
CREATE [OR REPLACE ] PROCEDURE Procedure_name
[ (argment [ {IN | IN OU T }] Type,
argment [ { IN | OUT | IN OUT } ] Type]
[ AUTHID DEFINER | CURRENT_USER]
{IS | AS}
<类型 变量的说明>
BEGIN
<执行部分>
EXCEPTION
<可选的异常错误处理程序>
END
1 、 存储函数中有返回值,且必须返回而存储过程没有返回值 ,可以通过传出参数返回多个值 。
2 、 存储函数可以在 select 语句 中直接使用,而存储过程不能。过程多数是被应用程序所调用。
3 、 存储函数一般都是封装一个查询结果,而 存储过程一般都封装一段事务代码。
--业主序列
create sequence seq_owners
start with 11;
--存储过程
--不带传出参数的存储过程
create or replace procedure pro_owners_add
(--默认in传入参数
v_name varchar2,--名称
v_addressid number,--地址编号
v_housenumber varchar2,--门牌号
v_watermeter varchar2,--水表号
v_ownertypeid number--业主类型
)
is
begin
insert into t_owners
values(seq_owners.nextval ,v_name,v_addressid,v_housenumber,v_watermeter,sysdate,v_ownertypeid);
commit;
end;
--调用不带参数的存储过程
call pro_owners_add('雷震子',2,'2333','234',1);
begin
pro_owners_add('黄天化',2,'2233','1234',1);
end;
--创建带传出参数的存储过程
create or replace procedure pro_owners_add
(
v_name varchar2,--名称
v_addressid number,--地址编号
v_housenumber varchar2,--门牌号
v_watermeter varchar2,--水表号
v_ownertypeid number,--业主类型
v_id out number
)
is
begin
--对传出参数赋值
select seq_owners.nextval into v_id from dual;
--新增业主
insert into t_owners
values(v_id ,v_name,v_addressid,v_housenumber,v_watermeter,sysdate,v_ownertypeid);
commit;
end;
--调用传出参数的存储过程
declare
v_id number;
begin
pro_owners_add('杨戬',2,'2332','1234321',1,v_id);
dbms_output.put_line(v_id);
end;
触发器
触发器在数据库里以独立的对象存储 ,它与存储过程不同的是,存储过程通过其它程序来启动运行或直接启动运行,而 触发器是由一个事件来启动运行 。 即触发器是当某个事件发生时自动地隐式运行 。并且,触发器不能接收参数 。所以 运行触发器就叫触发或点火(firing)。 ORACLE 事件指的是对数据库的表进行的INSERT、UPDATE 及 DELETE 操作或对视图进行类似的操作。ORACLE将触发器的功能扩展到了 触发 ORACLE如数据库的启动与关闭等。
创建触发器
创建触发器的一般语法是
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;
触发器触发次序
- 执行 BEFORE 语句级 触发器
- 对与受语句影响的每一行:
执行 BEFORE 行级 触发器
执行 DML 语句
执行 AFTER 行级 触发器 - 执行 AFTER 语句级 触发器
触发器的限制
- CREATE TRIGGER 语句文本的字符长度不能超过 32KB
- 触发器体内的 SELECT 语句只能为 SELECT … INTO … 结构,或者为定义游标所使用的 SELECT 语句 。
- 触发器中不能使用数据库事务控制语句 COMMIT; ROLLBACK, SVAEPOINT 语句;
- 由触发器所调用的过程或函数也不能使用数据库事务控制语句;
:NEW 修饰符访问操作完成后列的值
:OLD 修饰符访问操作完成前列的值
触发语句 | :old | :new |
---|---|---|
INSERT | 所有字段都是空(null) | 将要插入的数据 |
UPDATE | 更新以前该行的值 | 更新后的值 |
DELETE | 删除以前该行的值 | 所有字段都是空(null) |
注:语句级触发器无:old 和 :new
--触发器
--前置触发器
create or replace trigger tri_account_num1
before
update of num1
on t_account
for each row
declare
begin
--通过伪记录变量修改usenum字段的值
:new.usenum:= :new.num1-:new.num0;
end;
--测试前置触发器
--查询之前的usenum为:13808
select usenum from t_account where id=3;
update t_account
set num1=8888
where id=3;
--修改后查询usenum为:-51415 事务提交之前就执行了
select usenum from t_account where id=3;
commit;
--10180
select usenum from t_account where id=2;
update t_account
set num1=8888
where id=2;
---41235
select usenum from t_account where id=2;
rollback;
--10180
select usenum from t_account where id=2;
--改后置触发器不能创建
--因为不能改:new的值,但是能看
create or replace trigger tri_account_num1
after
update of num1
on t_account
for each row
declare
begin
--通过伪记录变量修改usenum字段的值
:new.usenum:= :new.num1-:new.num0;
end;
--后置触发器
--创建日志表,记录业主名称修改前和修改后的名称
create table t_owners_log(
updatetime date,
ownerid number,
oldname varchar2(30),
newname varchar2(30)
);
create or replace trigger tri_owners_log
after
update of name
on t_owners
for each row
declare
begin
--向日志表插入记录
insert into t_owners_log values(sysdate,:new.id,:old.name,:new.name);
--触发器不用commit;存储过程得commit;
end;
--测试后置触发器
update t_owners set name='Naruto' where id=4;
commit;