oracle--plsql块、存储过程、存储函数

Source

一、pl/sql块


在sql语句中增加过程处理语句,使sql语句具有过程处理能力
语法:
[declare
--变量声明
]
begin
  --代码逻辑
  [exception
  --异常处理
  ]
  end;
  
--变量
--声明变量
变量名 类型(长度)
--变量赋值语法
变量名:= 变量值

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:=9231;
  v_usenum2:=round(v_usenum/1000,2);
  v_money:=v_price*v_usenum2;
  --打印
  DBMS_OUTPUT.put_line('金额:'||v_money);
end;

--从数据库中查询然后赋值
select 列名 into 变量名 from 表名 where 条件

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:=9231; 水表字数
  select usenum into v_usenum 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_money);
end;

--属性类型(引用型 表名.列名%type)
处理数据库表数据时的首选写法
完全不用改代码,自动适配新类型
select * from t_account
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 into v_usenum 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_money);
end;

--数据类型(记录型 表名%rowtype)
它是存储一整行数据的记录型类型,是 %TYPE 的升级版
1. 查询表中一整行数据时(最常用)
不用单独声明 g_id、g_name、g_price 三个变量,一个变量搞定所有列。
2. 表结构修改时(自动适配)
如果给表加一列、修改价格精度,代码完全不用改,%ROWTYPE 自动同步新结构。
3. 插入 / 更新整行数据时
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_money);
end;

二、异常


发生异常后,语句停止执行,控制权转移到pl/sql 异常处理部分,有两种异常
预定义异常:当pl/sql程序违法oracle规制或超过系统现在模式引发
自定义异常:在pl/sql块的声明部分定义异常,自定义的异常通过raise语句显示引发
exception
  when 异常类型 then
    异常处理逻辑
    
--条件判断
if 条件 then
  业务逻辑
 end if;
 
 if条件 then
 业务逻辑
 else
   业务逻辑
   end if;

if 条件 then
  业务逻辑
  elsif 条件 then 业务逻辑
    else
      业务逻辑
      end if;
      
设置三给等级的水费5吨以下2.45元,5-10吨3.45 超过10吨4.45
declare
v_price 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_price:=2.45;
  v_price2:=3.45;
  v_price3:=4.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);
  --水费计算
  if v_usenum2<=5 then
    v_money:=v_price*v_usenum2;
    elsif v_usenum2>5 and v_usenum2<=10 then 
      v_money := v_price2*(v_usenum2 - 5)+ v_price*5;
    else 
      v_money := v_price*5+v_price2*5+v_price3*(v_usenum2-10);
    end if;
     

  --打印
  DBMS_OUTPUT.put_line('金额:'||v_money);
end;

三、循环


1.无条件循环
loop
  --循环语句
end loop;
输出从1到100个数
declare
v_num number:=1;
begin
  loop
    dbms_output.put_line(v_num);
    v_num:=v_num+1;
    if v_num>100 then
      exit;
      end if;
    end loop;
  end;
  
2.有条件循环
while 条件
loop
end loop;
输出从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;

3.for循环
declare
begin
  for v_num in 1..100
    loop
      dbms_output.put_line(v_num);
      end loop;
  end;
  


四、游标


游标是系统给用户开设的一个数据缓冲区,存放sql语句执行的结果,可以把游标理解成pl/sql的一个结果集
语法:
--声明
cursor 游标名称 is sql语句
--使用游标语法
open 游标名称
loop
  fetch 游标名称 into 变量
  exit when 游标名称%notfound --代表游标走到底了
  end loop;
close 游标名称

打印业主类型为1的价格表
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);
    end loop;
close cur_pricetable; 
 end;
 
 --带参数的游标
 declare
 cursor cur_pricetable(v_ownertype) is select * from t_pricetable where ownertypeid = v_ownertype; --声明变量
 v_pricetable t_pricetable%rowtype;
begin
open cur_pricetable(1);--打开游标,数字是ownertypeid对应类型,代表传进去的参数
  loop
    fetch cur_pricetable into v_pricetable;--提前数据
    exit when cur_pricetable%notfound;--退出循环
    DBMS_OUTPUT.PUT_LINE('价格'||v_pricetable.price);
    end loop;
close cur_pricetable; 
 end;
 
 --for循环带参数游标
  declare
 cursor cur_pricetable(v_ownertype) is select * from t_pricetable where ownertypeid = v_ownertype; --声明变量
 v_pricetable t_pricetable%rowtype;
begin
  for v_pricetable in cur_pricetable(1) --数字是ownertypeid对应类型,代表传进去的参数
  loop
    DBMS_OUTPUT.PUT_LINE('价格'||v_pricetable.price);
    end loop;
 end;

五、存储函数


存储函数又称自定义函数,可以接受一个或多个参数,返回一个结果,用pl/sql进行逻辑处理
语法:
create [or replace] function 函数名称
(参数名称 参数类型,参数名称 参数类型,...)
return 结果变量数据类型
IS
变量声明部分;
begin
  逻辑部分
  return 结果变量;
  [exception
  异常处理部分]
  end;


--创建存储函数,根据地址id查询地址名称
create or replace function fn_getaddress1
(v_id number)
return varchar2
IS
v_name varchar2(30);
begin
  select name into v_name from t_address where id =v_id; --将查询到name的结果存入变量v_name中
  return v_name;
end;
--存储函数测试
select fn_getaddress1(3) from dual;
--应用
查询业主id,业主名称,业主地址,用存储函数就可以实现,不用多表关联
select * from t_owners
select * from t_address
select ow.id,ow.name,ad.name from t_owners ow,t_address ad where ow.addressid = ad.id --多表的

select id,name,fn_getaddress1(addressid)from t_owners;--存储函数的

六、存储过程


业务必须全部成功或全部失败
存储过程是命名的pl/sql块,存储于数据库中,是数据库对象的一种,应用程序可以调用存储过程,执行相应的逻辑
存储过程与存储函数的区别
1.存储函数中有返回值,必须返回,存储过程没有返回值,可以通过传出参数返回多个值
2.存储过程可以在select语句中直接使用,而存储过程不能,过程多数是被应用程序所调用
3.存储过程一般是封装一个查询结果,而存储过程一般可以封装一段事务代码。
create or replace procedure 存储过程名称
(参数名 类型,参数名 类型)
is|as
变量声明部分
begin
  逻辑部分
  [exception
  异常处理部分
  ]
  end;
--过程参数三种模式
in 传入参数(默认)
out 传出参数,主要用于返回程序运行结果
in out 传入传出参数

--创建不带传出参数的存储过程:添加业主信息,可以用序列
select * from owners
create sequence seq_ownersid start with 11;
create or replace  procedure pro_insert
(v_name varchar2,v_addressid number,v_housenumber varchar2,v_waternumber varchar2,v_ownertypeid number )
is
begin
  insert into t_owners values(seq_ownersid.nextval,v_name,v_addressid,v_housenumber,v_waternumber,sysdate,v_ownertypeid);
  commit;
end;
调用存储过程
写法1:
call pro_insert ('测试','10','1-10','345',1)
写法2:
begin
  pro_insert ('测试1','10','1-10','345',1);
end;  

--创建带参数存储过程
create or replace  procedure pro_insert1
(v_name varchar2,
v_addressid number,
v_housenumber varchar2,
v_waternumber varchar2,
v_ownertypeid number,
v_id out number
 )
is
begin
  --对传出参数赋值
  select seq_account.nextval into v_id from dual;
  insert into t_owners values(v_id,v_name,v_addressid,v_housenumber,v_waternumber,sysdate,v_ownertypeid);
  commit;
end;
调用
declare
v_id number;
begin
  pro_insert1 ('测试3','10','1-10','345',1,v_id);
  dbms_output.put_line(v_id);
end;

七、触发器


一个与表关联的,存储的pl/sql程序,每当一个特定的数据操作语句在指定表上发出时,oracle自动执行触发器中定义的语句序列
可用于:
数据确认,实施复杂的安全性检查,做审计,跟踪表上所做的数据操作等,数据的备份和同步
分类
前置触发器(before)
后置触发器(after)
创建语法
create [or replace] trigger 触发器名称
before | after
[delete][[or]insert][[or]update [of 列名]]
on 表名
[for each row][where(条件)] -- for each row表明是行级触发器
declare
...
begin
  plsql块
end;

--触发器中触发语句与伪记录变量的值(前置触发器可以改这个伪列的值,后置触发器不能修改只能使用)
分为行级触发器和语句触发器,语句触发器没有:old和:new
insert
:old 所有字段都是空(null)
:new 将要插入的数据

update
:old 更新以前该行的值
:new 更新后的值

delete 
:old 删除以前该行的值
:new 所有字段都是空

--前置触发器
当用户输入本月累计表数后,自动计算出本月使用数
select * from t_account for update
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;
  
--后置触发器
当前用户修改了业主名称的时间时记录修改前与修改后的值
--创建记录表
create table t_owners_log(
updatetime date,
ownerid number,
oldnum varchar2(30),
newnum varchar2(30)
);
--创建触发器
create or replace trigger tri_after
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);
end;

select * from t_owners for update
--当前窗口能看到日志表里有数据,但是别的窗口看不到,要commit后才能看到
select * from t_owners_log
commit;