PL/SQL(Procedure Language/SQL)是 Oracle 对 sql 语言的过程化扩展,指在 SQL 命令语言中增加了过程处理语句(如分支、循环等),使 SQL 语言具有过程处理能力。把 SQL 语言的数据操纵能力与过程语言的数据处理能力结合起来,使得 PLSQL 面向过程但比过程语言简单、高效、灵活和实用。
基本的语法结构:
--语法:变量声明和异常处理可以没有,当时begin..end块必须存在
[declare
声明变量;
]
begin
逻辑代码;
[exception
异常处理逻辑;
]
end;
变量声明:
--语法:在declare中声明变量
--1.只声明变量,不赋值
变量名称 变量类型(长度); --如:v_name varchar2(30);
--2.声明时就给初始值
变量名称 变量类型(长度):=值; --如:v_name varchar2(30):='somnus';
变量的赋值:
--变量赋值方式:
--1.声明时就给初始值
变量名称 变量类型(长度):=值; --如:v_name varchar2(30):='somnus';
--2.先声明,在begin..end块中进行赋值
变量名:=值; --如:v_name:='somnus';
--3.先声明,在begin..end块中通过select 字段名 into 变量名 from 表名 条件;方式进行赋值
select 字段名 into 变量名 from 表名 条件;
案例:
--1.变量声明和赋值
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:=9527;
v_usenum2:=round(v_usenum/1000);
v_money:=v_price*v_usenum2;
--输出值
dbms_output.put_line('用水量/吨:'||v_usenum2);
dbms_output.put_line('金额/元:'||v_money);
end; --要有分号
--2.从表中查询数据到变量中 select 列名 into 变量名from 表名 [条件]
declare
v_price number(10,2); -- 水费单价
v_usenum number;--水表数字
v_usenum2 number(10,2);--水的吨数
v_money number(10,2); --水费金额
begin
--赋值
v_price:=2.45;
--从水费台帐表中查询数据到变量中
select usenum into v_usenum from t_account where year='2016' and month='01' and id = 1;
v_usenum2:=round(v_usenum/1000);
v_money:=v_price*v_usenum2;
--输出值
dbms_output.put_line('用水量/吨:'||v_usenum2);
dbms_output.put_line('金额/元:'||v_money);
end; --要有分号
如果定义的变量的类型依赖于具体表中的某列(引用型属性)或查询表结果集的中行(记录类型),则可以通过定义的方式的使得变量依赖于具体的列类型或行类型。
引用类型:变量名称 表名.列名%type;
--案例:
declare
v_price number(10,2); -- 水费单价
v_usenum t_account.usenum%type;--水表数字
v_usenum2 number(10,2);--水的吨数
v_money number(10,2); --水费金额
begin
--赋值
v_price:=2.45;
--从表中查询数据到变量中
select usenum into v_usenum from t_account where year='2016' and month='01' and id = 1;
v_usenum2:=round(v_usenum/1000);
v_money:=v_price*v_usenum2;
--输出值
dbms_output.put_line('用水量/吨:'||v_usenum2);
dbms_output.put_line('金额/元:'||v_money);
end; --要有分号
记录类型:变量名称 表名%rowtype;
将结果集中一行记录封装到变量中
declare
v_price number(10,2); -- 水费单价
v_account t_account%rowtype;--将查询的一行记录封装到一个变量中
v_usenum2 number(10,2);--水的吨数
v_money number(10,2); --水费金额
begin
--为变量赋值
v_price:=2.45;
--从表中查询一行数据封装到一个变量中
select * into v_account from t_account where year='2016' and month='01' and id = 1;
--通过记录类型变量.列名获取一行记录中某列的值
v_usenum2:=round(v_account.usenum/1000); -- 通过变量.列名调用某列的值
v_money:=v_price*v_usenum2;
--输出值
dbms_output.put_line('用水量/吨:'||v_usenum2);
dbms_output.put_line('金额/元:'||v_money);
end; --要有分号
在运行程序时出现的错误叫做异常/例外,发生异常后,语句将停止执行,控制权转移到 PL/SQL 块的异常处理部分
异常有两种类型:
1. 预定义异常:当 PL/SQL 程序违反 Oracle 规则或超越系统限制时隐式引发,一共有21种异常;
2. 用户定义异常:用户可以在 PL/SQL 块的声明部分定义异常,自定义的异常通过 RAISE 语句显式引发。
异常处理的语法:
--语法:在begin..end块中定义
begin
处理逻辑;
exception
when 异常类型 then
异常处理逻辑
end;
案例:
--一般使用系统预定义异常:
--可能多个异常时,需要有多个when..then语句
--异常/例外 预定义异常21种和其他用户自定义异常
declare
v_num number;
begin
v_num:=1/0; --除0异常
exception
when ZERO_DIVIDE --除数不能为0的异常关键字
then
dbms_output.put_line('除数不能为0');
when NO_DATA_FOUND --使用 select into 未返回行
then
dbms_output.put_line('没有返回数据');
end;
判断语句if的语法:
--在begin..end块中使用判断语句:注意elsif中不是else if
declare --当没有变量的定义时,declare可以省略
begin
--if的格式1:if语句
if 1<2 then
dbms_output.put_line('1小于2');
end if;
--if的格式2:if..else语句
if 1>2 then
dbms_output.put_line('1大于2');
else
dbms_output.put_line('1小于2');
end if;
--if的格式3:if..elsif..elsif..else语句
if 1>2 then
dbms_output.put_line('1大于2');
elsif 1=2 then
dbms_output.put_line('1等于2');
else
dbms_output.put_line('1小于2');
end if;
end;
无条件循环:
--语法:循环语句定义在begin..end块中
loop
循环体;
end loop;
--案例:
declare
v_num number:=1;
begin
loop
dbms_output.put_line(v_num);
v_num:=v_num+1;
--退出循环方式1
if v_num >= 100 then
exit;
end if;
--退出循环方式2
exit when v_num >= 100;
end loop;
end;
有条件循环:
--语法:循环语句定义在begin..end块中
while 条件;--条件是指循环能够执行的条件
loop
循环体;
end loop;
--案例:
declare
v_num number:=1;
begin
while v_num < 100 --循环的条件
loop
dbms_output.put_line(v_num);--输出变量的值
v_num:=v_num+1; -- 变量加1
end loop;
end;
for循环:
--语法:循环语句定义在begin..end块中
for 变量 in 起始值..终止值
loop
循环体;
end loop;
--案例:
--使用for循环
declare
begin
--此处的x变量相当于是局部变量,不用再定义,并且x只能在loop循环体内使用
for x in 1..100
loop
dbms_output.put_line(x);
end loop;
end;
--for循环,可以使用变量控制循环的数据范围
declare
v_num number:=50;
begin
--此处的x变量相当于是局部变量,不用再定义,并且x只能在loop循环体内使用
for x in 1..v_num
loop
dbms_output.put_line(x);
end loop;
end;
游标是系统为用户开设的一个数据缓冲区,存放 SQL 语句的执行结果。我们可以把游标理解为 PL/SQL 中的结果集。查询的数据存放在内存中缓冲区中,降低了服务器性能。游标打开时默认指向的是结果集中第一行的上面,当通过fetch抓取数据时才使得游标指向第一条数据。
定义无参数游标语法:
--语法:
cursor 游标名称 is SQL 语句;
无参数游标使用语法:
--语法:
open 游标名称;
loop
fetch 游标名称 into 变量;
exit when 游标名称%notfound;
end loop;
close 游标名称;
--案例:
--无参数的游标
declare
cursor cur_pricetable is
select * from t_pricetable where ownertypeid=1;
--获取游标中存储的值
v_price t_pricetable%rowtype;
begin
open cur_pricetable;
loop
fetch cur_pricetable into v_price; --获取游标中的值到行变量中
exit when cur_pricetable%notfound; --游标的值取完后退出循环
dbms_output.put_line(v_price.price||'--'||v_price.minnum||'--'||v_price.maxnum);
--退出语句放在输出语句后面,会将最后一个值输出两次
--原因是:只有fetch语句才会抓取下一个值,当是最后一个值,输出语句进行输出,
--判断条件判断的是当前的结果是最后一个值,下次循环,fetch抓取的是空值,但
--输出语句还是会继续输出最后一个值,到退出语句才会结束循环
--exit when cur_pricetable%notfound; --游标的值取完后退出循环
end loop;
close cur_pricetable;
end;
有参数游标的定义与使用:
--定义带参数的游标
declare
cursor cur_pricetable2(v_ownertypeid number) is --定义游标的参数
select * from t_pricetable where ownertypeid=v_ownertypeid;
--获取游标中存储的值
v_price t_pricetable%rowtype;
begin
open cur_pricetable2(3); --调用带参数的游标需要传入参数
loop
fetch cur_pricetable2 into v_price; --获取游标中的值到行变量中
exit when cur_pricetable2%notfound; --游标的值取完后退出循环
dbms_output.put_line(v_price.price||'--'||v_price.minnum||'--'||v_price.maxnum);
end loop;
close cur_pricetable2;
end;
通过for循环使用游标:
--for循环提取游标值:隐式的打开游标和关闭游标
declare
cursor cur_pricetable2(v_ownertypeid number) is --定义游标的参数
select * from t_pricetable where ownertypeid=v_ownertypeid;
--获取游标中存储的值
v_price t_pricetable%rowtype;
begin
for v_price in cur_pricetable2(1)
loop
dbms_output.put_line(v_price.price||'--'||v_price.minnum||'--'||v_price.maxnum);
end loop;
end;
存储函数又称为自定义函数。可以接收一个或多个参数,返回一个结果。在函数中我们可以使用 P/SQL 进行逻辑的处理。
创建存储过程语法:
--语法:
CREATE [ OR REPLACE ] FUNCTION 函数名称
(参数名称 参数类型, 参数名称 参数类型, ...)
RETURN 结果变量数据类型
IS
变量声明部分;
BEGIN
逻辑部分;
RETURN 结果变量;
[EXCEPTION
异常处理部分]
END;
--注意事项:
函数的参数声明时不能加参数长度;
函数的返回值不能添加类型的长度;
案例:
--存储函数可以嵌入SQL语句中,返回的是一个值或对象
create or replace function fn_getAddress
(v_id number)
return varchar2
is
--变量声明
v_name varchar2(20);
begin
select name into v_name from t_address where id = v_id;
return v_name;
end;
--调用存储函数
select fn_getAddress(1) from dual; --简单的调用存储函数
--调用存储函数作为select语句字段
select id,name,fn_getaddress(addressid) from t_owners where id=1;
存储过程是被命名的 PL/SQL 块,存储于数据库中,是数据库对象的一种。应用程序可以调用存储过程,执行相应的逻辑。存储过程的缺点:数据库中表之间耦合度较高,不利于功能需求的更改。
存储过程:能够将开发中的业务逻辑进行封装,其思想和hibernate框架思想相反。
存储过程与存储函数都可以封装一定的业务逻辑并返回结果,存在区别如下:
存储过程语法结构:
--语法:
CREATE [ OR REPLACE ] PROCEDURE 存储过程名称
(参数名 [in|out|in out] 类型, 参数名 [in|out|in out] 类型, 参数名 [in|out|in out] 类型...)
IS|AS --两者都可以,但必须有一个
变量声明部分;
BEGIN
逻辑部分;
[EXCEPTION
异常处理部分]
END;
--注意事项:
参数声明时不能指定类型的长度;
参数的in|out说明:in代表该参数是传入参数,out代表该参数是传出参数
声明参数为in:声明该参数是传入参数
声明参数为out:声明该参数是传出参数
声明参数为in out:声明该参数是传入传出参数
无传出参数的存储过程调用:
call方式调用;
begin...end方式调用;
JDBC中调用;
有传出参数的存储过程调用:不能使用call调用,无法获取传出参数
begin...end方式调用;
JDBC中调用;
案例1:无参数的存储过程创建
--不带传出参数的存储过程
create sequence seq_owners start with 18;--创建自动增长序列,用于设置表的id列主键
--创建存储过程
create or replace procedure pro_owners_add
(
v_name varchar2, --传入参数默认省略in关键字
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);
end;
案例1:无参数的存储过程调用–call和begin..end方式调用
--调用不带传出参数的存储过程
--调用方式1:
call pro_owners_add('sunmos',1,'2-1','0729-0809',1);commit;
--调用方式2:
begin
pro_owners_add('sunmos',1,'2-1','0729-0809',1);
commit;
end;
案例1:无参数的存储过程调用–jdbc方式调用
/**
* 注意事项:调用方式:{call pro_owners_add(?,?,?,?,?)},必须要加{},传入参数
* 调用数据库的无传出参数的存储过程
*/
public void pro_add(Owners owners) {
// 声明数据库连接对象
Connection conn = null;
// 声明执行存储过程的对象
CallableStatement stmt = null; // 存储过程使用的CallableStatement对象
try {
// 获取连接
conn = BaseDao.getConnection();
// 预编译存储过程
stmt = conn.prepareCall("{call pro_owners_add(?,?,?,?,?)}");
// 设置参数
stmt.setString(1, owners.getName());
stmt.setLong(2, owners.getAddressid());
stmt.setString(3, owners.getHousenumber());
stmt.setString(4, owners.getWatermeter());
stmt.setLong(5, owners.getOwnertypeid());
// 执行sql语句
stmt.execute();
} catch (SQLException e) {
e.printStackTrace();
} finally{
// 释放资源
BaseDao.closeAll(conn, stmt, null);
}
}
案例2:有传出参数存储过程的创建
--创建带传出参数的存储过程
create or replace procedure pro_owners_add2
(
v_name varchar2,
v_addressid number,
v_housenumber varchar2,
v_watermeter varchar2,
v_ownertypeid number,
v_id out number --传出参数需要定义out关键字
)
is -- is和as均可以
begin
--将添加的业主的id值存储到传出变量中
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; --dml操作需要使用commit提交事务
end;
案例2:有传出参数存储过程的调用–begin..end方式调用
--调用带传出参数的存储过程
--只能通过begin...end语句进行调用,不能通过call调用
declare
v_id number; --声明接收传出参数的变量
begin
--将传出参数作为存储过程的对应的参数
pro_owners_add2('sunmos',1,'2-1','0729-0809',1,v_id);
dbms_output.put_line(v_id);--输出传出参数值
end;
案例2:有传出参数存储过程的调用–JDBC方式调用
/**
* 注意事项:
* 调用方式:{call pro_owners_add(?,?,?,?,?)},必须要加{},传入参数;
* 获取传出参数必须在执行SQL语句后才能获取传出参数值;
* 调用数据库的有传出参数的存储过程
*/
public Long pro_add2(Owners owners) {
// 定义传出的数据的值
Long v_id = 0L;
// 声明数据库连接对象
Connection conn = null;
// 声明执行存储过程的对象
CallableStatement stmt = null; // 存储过程使用的CallableStatement对象
try {
// 获取连接
conn = BaseDao.getConnection();
// 预编译存储过程
stmt = conn.prepareCall("{call pro_owners_add2(?,?,?,?,?,?)}");
// 设置参数
stmt.setString(1, owners.getName());
stmt.setLong(2, owners.getAddressid());
stmt.setString(3, owners.getHousenumber());
stmt.setString(4, owners.getWatermeter());
stmt.setLong(5, owners.getOwnertypeid());
// 设置传出参数的类型,最后一个参数是传出参数,类型时number类型
stmt.registerOutParameter(6, OracleTypes.NUMBER);//OracleTypes是Oracle驱动包中的类
// 执行SQL语句
stmt.execute();
// 在存储过程执行完毕后获取传出参数值
v_id = stmt.getLong(6);
} catch (SQLException e) {
e.printStackTrace();
} finally{
BaseDao.closeAll(conn, stmt, null);
}
return v_id;
}
数据库触发器是一个与表相关联的、存储的PL/SQL程序。每当一个特定的数据操作语句(Insert,update,delete)在指定的表上发出时,Oracle 自动地执行触发器中定义的语句序列。触发器是作用表上的监听DML操作并执行预定义的逻辑代码。触发器分为前置触发器和后置触发器。当表被删除时,作用在表上的触发器会被自动删除。
触发器的作用: 数据确认;实施复杂的安全性检查;做审计,跟踪表上所做的数据操作等; 数据的备份和同步。
触发器的定义:
--语法:
CREATE [or REPLACE] TRIGGER 触发器名
BEFORE | AFTER
[DELETE ][[or] INSERT] [[or]UPDATE [OF 列名]]
ON 表名
[FOR EACH ROW ] [WHEN(条件) ]
declare
变量声明;
begin
逻辑代码块;
End;
--注意事项:
before:代表是前置触发器;执行语句会先被挂起,如果触发器执行过程抛出异常,挂起的sql语句不能执行;
after:代表是后置触发器;执行语句执行后,再执行触发器;
[DELETE ][[or] INSERT] [[or]UPDATE [OF 列名]]:表示该表发生那种DML操作时触发该触发器,如果需要监听多个DML操作,则使用or关键字连接;如果是监听update操作,还可以指定要监听该表的具体列,监听多列时,列名用逗号隔开;
[FOR EACH ROW ]:表示该触发器的类别是行级触发器,缺省时代表是语句级触发器;
[WHEN(条件) ]:表示该触发器执行的条件,默认是true,执行;
触发器中的dml语句会自动commit提交,不用手动提交.
行级触发器和语句触发器的区别:
行级触发器:for each row,一次语句执行,执行一次触发器中的逻辑;
语句级触发器:没有伪记录变量 多次的语句执行,才执行一次触发器中的逻辑;
触发器中的伪记录变量:每一个触发器都有默认的old和new变量,old代表变化之前的行记录,new代表变化后行记录
触发语句 | :old变量 | :new变量 |
---|---|---|
insert语句 | 所有的字段均为null | 将要插入的行数据 |
update语句 | 更新该行记录之前的行记录值 | 更该行后的行记录值 |
delete语句 | 删除该行之前的行记录值 | 所有的字段均为null |
案例:
--定义前置触发器
--自动计算每月用水量
create trigger tri_account_usenum
before --创建前置触发器
update of num1 --触发器作用在某列的值改变
on t_account
for each row
declare
--声明变量
begin
:new.usenum:=:new.num1-:new.num0;
end;
--定义后置触发器
--当业主名称修改时,将修改之前的信息保存到信息记录表中
--创建存储用户信息修改的日志表
create table t_owners_log(
v_updatetime date,
v_oldname varchar2(30),
v_newname varchar2(30),
v_ownersid number);
--创建后置触发器
create trigger tri_owners_update
after
update of Name --在业主表的name列上创建触发器
on t_owners
for each row
declare
--声明变量
begin
--触发器中的dml语句会自动提交事务,不用手动提交事务
insert into t_owners_log values (sysdate,:old.name,:new.name,:old.id);
end;