您的位置:首页 > 数据库 > Oracle

关于oracle中PL/SQL语句,即存储过程的写法和调用。

2015-01-20 15:57 561 查看
1.什么是PL/SQL,有什么作用

  --Procedure Language/SQL 过程语言+SQL

  --PL/SQL是Oracle数据库内部存储和运行

    一段程序

  --PL/SQL是Oracle在SQL语句基础上扩展了

   一些判断,循环等编程元素.可以实现一些

   复杂的运算和逻辑功能.

  --PL/SQL适合大量数据运算.

2.PL/SQL基本结构

   DECLARE

     ....//变量声明区

   BEGIN

     ....//主要处理逻辑;循环,判断+SQL

   EXCEPTION  

     ....//异常处理逻辑

   END;//结束

  set serveroutput on;//打开oracle控制台输出

  //最简单的pl/sql

  begin

     dbms_output.put_line('Hello PL/SQL');

  end;

  注意:pl/sql语句块是以"/"作为结束运行标识.

上面结构的PL/SQL程序段,只编译运行一次,

不会在Oracle存储,如果需要存储可以写成

存储过程,触发器等元素.

3.PL/SQL基本语法

   1)注释

      -- 单行注释内容

      /*

         多行注释内容

      */

   2)变量定义和赋值操作

      变量名 类型;

      a number(5);//a为null

      b number(3) := 100;//b为100

    注意:变量定义,如果没有指定值,默认为null.

赋值语句为:=格式. =号用于判断比较是否相等.

    declare

        a number(5) := 100; --定义变量a赋值100

        b number(5) :=200; --定义变量a赋值300

        c number(5);

    begin

        c := a+b; --将a+b结果给c赋值

        dbms_output.put_line('结果是:' || c);

    end;

   3)if判断

     if 条件 then

        ...

     elsif 条件 then

        ...

     else

        ...

     end if;

    ------------------

    declare

       sex varchar2(2) := 'F';

    begin

       if sex='M' then

          dbms_output.put_line('Male');

       elsif sex='F' then

          dbms_output.put_line('Female');

       else

           dbms_output.put_line('Yao!!!');

       end if;

    end;

    //有a,b,c三个number(3)变量,如果a>b,c=1;

如果a=b,c=0;如果a<b,c=-1;最后将c打印输出

   declare

      a number(3) :=300;

      b number(3) :=200;

      c number(3);

   begin

       if a>b then

          c:=1;

      elsif a=b then

          c:=0;

      else

          c:=-1;

      end if;

      dbms_output.put_line('a,b比较结果:' || c);

   end;

  4)循环

    提供了3种循环语法;

   a.loop循环

    declare

       i number(2) :=1;

    begin

       loop   --循环开始标记

          dbms_output.put_line(i);

          i:=i+1;

          exit when i>10; --指定结束循环条件

       end loop; --循环结束标记

    end;

   ---------------------

      declare

          i number(3) :=1;

          s number(11) := 0;

              --合计变量,指定初值0,否则为null,

              --null和其他数累加结果还是null

      begin

          loop

             s := s+i;

             i:=i+1;

             exit when i>100;

          end loop;

          dbms_output.put_line(s);

      end;

   

    b.while循环

      declare

         i number(3):=1;

      begin

         while i<=10 loop --满足条件进入循环;不满足退出循环

            dbms_output.put_line(i);

            i:=i+1;

         end loop;--循环结束标记

      end;

   

    c.for循环

     begin

        for i in 1..10 loop --循环1到10输出

           dbms_output.put_line(i);

        end loop;--循环结束标记

     end;

   FOR循环使用注意事项:

-- 循环变量i不需要定义,会根据集合元素自动定义

-- in 后面的集合只适用于数值范围

  5)PL/SQL使用DML和TCL事务语句

   DML和TCL语句可以直接在PL/SQL程序中

使用,没有任何特殊要求.

   begin

      --向dept表插入一条记录

      insert into dept(deptno,dname,loc) 
values (50,'testing','beijing');

      --删除编号为60的部门 

      delete from dept where deptno=60;

      --提交事务

      commit;

   end;

   6)PL/SQL使用DDL语句

     先前学习的create tabe...,drop table,alter table

语句必须采用下面格式:

     execute immediate 'DDL语句'; --立即执行‘DDL语句’。

------------------

    begin

        execute immediate 'create table emp_bak (EMPNO number(11),ENAME varchar2(20),SAL number(9,2))';

    end;

-----------------------

   begin

       --建表

       execute immediate 

              'create table c001 (c1 number(5))';

       --插入记录

       insert into c001 (c1) values (5);

       commit;--提交事务

   end;

  上面PL/SQL编译会发生错误,因为编译insert

语句时,找不到c001表.

   execute immediate 'ddl语句';在编译时将dll语句

当作普通的字符串.执行时将字符串当作sql指令执行

-----------------------------

   begin

       --建表

       execute immediate 

              'create table c001 (c1 number(5))';

       --插入记录

       execute immediate 

             'insert into c001 (c1) values (5)';

       commit;--提交事务

   end;

     execute immediate

        'insert into c002 (c1) values ('abc')';

   使用:chr()函数,chr(39)将ascii=39字符返回.  

    execute immediate

        'insert into c002 (c1) values ('||chr(39)||'abc'||chr(39)||')';

--------------------------------

   7)PL/SQL使用DQL查询操作

     a)有且只有一条结果(使用select语句)

      使用select语句必须按照下面结构使用

       select 字段... into 变量...

       from 表 where 条件;

      上面语句可以将查询出的字段值给变量.

   declare

      --v_deptno number(2);

      --v_dname varchar2(14);

      v_deptno dept.deptno%TYPE;

      v_dname dept.dname%TYPE;

   begin

       --查询将结果给变量

       select deptno,dname into v_deptno,v_dname

       from dept where deptno=30;

       --显示变量值

       dbms_output.put_line(v_deptno||v_dname);

   end;

    使用%TYPE定义变量.格式如下:

--v_deptno类型大小与dept表的deptno字段一致

v_deptno dept.deptno%TYPE;

--v_dname类型大小与dept表的dname字段一致

v_dname dept.dname%TYPE;

   b)如果返回0或多个结果(使用CURSOR游标)

   CURSOR使用步骤:

  --在declare定义游标

    CURSOR 游标名 IS 原有select语句;

  --在begin中使用

    open 游标名;//打开游标,执行select语句.

    循环获取数据进行运算;

    fetch 游标名 into 变量;

    close 游标名;    

========================

declare

   cursor c_dept_cursor is 

      select deptno,dname from dept order by deptno;

   v_deptno dept.deptno%type;

   v_dname dept.dname%type;

begin

   open c_dept_cursor;--打开游标

   loop

       --向下移动指针,抓取记录

       fetch c_dept_cursor into v_deptno,v_dname;

       --判断是否该结束循环

       exit when c_dept_cursor%NOTFOUND;

        --打印变量值

        dbms_output.put_line(v_deptno||'  '||v_dname);

   end loop;

    close c_dept_cursor;--关闭游标

end;

=========游标变量数据过程===========

--游标初始状态,指针指向第一行记录上方

--当使用fetch抓取记录时,会向下移动游标指针,

   然后将指针指向的记录取出

--当指针指向最后一条记录时,再执行fetch,

指针会停留在最后一行,但是会将%NOTFOUND

属性值改为true;

=================================

declare

  cursor c_dept_cursor is 

    select deptno,dname from dept order by deptno;

  v_deptno dept.deptno%type;

  v_dname dept.dname%type;

begin

  open c_dept_cursor;

  --初始指针指向第一行记录上方,没有记录

  --因此需要fetch向下移动指针

  fetch c_dept_cursor into v_deptno,v_dname;

  while c_dept_cursor%FOUND loop

      dbms_output.put_line(v_deptno||' '||v_dname);

      fetch c_dept_cursor into v_deptno,v_dname;

  end loop;

  close c_dept_cursor;

end;

=========%ROWTYPE============

v_dept dept%ROWTYPE;--记录类型变量

v_dept是一个记录类型,其中有多个字段,

字段个数,名称,类型,大小与dept表一致.

它可以存储dept中的一行记录.

利用"v_dept.字段名"可以访问记录数据.

declare

   cursor c_dept_cursor is

     select * from dept order by deptno;

   --v_dept变量和dept字段数量,类型,大小一致

   v_dept dept%ROWTYPE;--记录类型

begin

   open c_dept_cursor;

   loop

      fetch c_dept_cursor into v_dept;

      exit when c_dept_cursor%NOTFOUND;

      dbms_output.put_line(

            v_dept.deptno||' '||v_dept.dname);

   end loop;

   close c_dept_cursor;
end;

=====================================================================

1.plsql基础

declare

   变量名 类型; --默认为null

   变量名 类型 := 值;

:=和=区别

/的作用

--和/* */注释

匿名块plsql

declare ... begin ... exception ... end;

DML语句与原SQL相同

SELECT语句需要采用select ... into ...,

(select结果有且只有一条记录)

CURSOR查询多行或0行记录

declare

  CURSOR v_emp_cursor IS select .....

begin

  //打开游标Open   

  //利用LOOP循环遍历fetch

  //关闭游标Close

end;

DDL语句需要使用execute immediate 'DDL语句';

chr(39)对应'

ascii('a')对应97

使用两个''表示一个'

if...then

 ...

elsif ... then

 ...

else

 ...

end if;

loop

   exit when ...

end loop;

while ... loop

end loop;

for 变量 in 起点...终点 loop

end loop;

for 变量 in ascii('a')...ascii('z') loop

end loop;

====================

PL/SQL中匿名块不能存储在Oracle,如果需要

存储在Oracle上,需要定义成存储过程,函数,

触发器,包等对象元素.

1.存储过程

CREATE [OR REPLACE]  PROCEDURE 过程名

[(参数 IN | OUT | IN OUT 类型,参数 类型...)]

IS|AS

  --原declare声明区

BEGIN

  --主处理区

EXCEPTION

  --异常处理区

END;

  -----示例1------

 create or replace procedure helloworld

 is

  v_count number(11);

 begin

   --统计dept数量显示

   select count(*) into v_count from dept;

   dbms_output.put_line('dept toal:' || v_count);

 end;

----------调用----------

 set serveroutput on;--先打开控制台显示

 exec helloworld();或 call helloworld();或匿名块

------示例2--------

根据传入的empno显示名字和工资(工资+奖金)

--参数类型不要指定大小,名字不要与字段冲突

create or replace procedure show_emp

(no in number) 

is

 v_ename emp.ename%TYPE;

 v_sal emp.sal%TYPE;

begin

   select ename,sal+NVL(comm,0) 

   into v_ename,v_sal 

   from emp 

   where empno=no;

   dbms_output.put_line(v_ename || '  ' || v_sal);

end;

------示例3-------

传入两个整数,传出合计结果和差值结果

create or replace procedure sumsub

(v_i in number,v_j in number,

v_sum out number,v_sub out number)

is

begin

    v_sum := v_i+v_j;    --求和

    v_sub := v_i-v_j;      --求差

end;

--------测试-----------

declare

v_sum number;

v_sub number;

begin

--调用存储过程,in参数给常量,out参数给变量

sumsub(10,50,v_sum,v_sub);

dbms_output.put_line('sum:'||v_sum);

dbms_output.put_line('sub:'||v_sub);

end;

/

---------综合练习-------------

基于EMP表,根据EMP员工记录和工资信息

利用PL/SQL语句向EMP_TAX员工纳税记录表

生成数据信息.

EMP_TAX表结构如下:

EMPNO (员工编号)

ENAME (员工名)

SALARAY(工资+奖金) 

TAX  (纳税金额)

TAX_DATE(纳税日期-系统日期)

create table emp_tax(

empno number(4) ,

ename varchar2(10),

salary number(7,2),

tax number(7,2),

tax_date date);

-----------扣税规则--------------

工资+奖金=总金额按一下规则计算

2000(包含)以下不扣税,2000为免税基数

2000-3000之间的超出部分按5%扣税

3000(包含)以上超出部分按10%扣税

2500-->500*5%

3500-->1000*5%+500*10%

declare

  CURSOR c_emp_cursor IS

    select * from emp; --定义游标

  v_emp emp%ROWTYPE; --存储一行游标记录

  v_salary number(7,2) :=0.0 ;  --存储工资合计

  v_tax number(7,2) :=0.0; --存储缴税金额

begin

   open c_emp_cursor;

   loop --循环抓取员工记录

       fetch c_emp_cursor into v_emp;

         exit when c_emp_cursor%NOTFOUND;

       --计算该员工工资合计

       v_salary := v_emp.sal + NVL(v_emp.comm,0);

       --计算应缴税金额

       if v_salary <= 2000 then

          v_tax := 0.0;

       elsif v_salary <3000 then

          v_tax := (v_salary-2000)*0.05;

       else

           v_tax := (v_salary-3000)*0.1+1000*0.05;

       end if;

       --将该员工缴税信息插入emp_tax表

       insert into emp_tax 

           (empno,ename,salary,tax,tax_date)

       values (v_emp.empno,v_emp.ename,v_salary,v_tax,sysdate);

   end loop;

   commit; --提交事务

   close c_emp_cursor;

end;

---------将上述匿名块改成存储过程---------

将declare关键字改为

create or replace procedure emp_tax_pro

is

  CURSOR c_emp_cursor IS

    select * from emp; --定义游标

  v_emp emp%ROWTYPE; --存储一行游标记录

  v_salary number(7,2) :=0.0 ;  --存储工资合计

  v_tax number(7,2) :=0.0; --存储缴税金额

begin

   open c_emp_cursor;

   loop --循环抓取员工记录

       fetch c_emp_cursor into v_emp;

         exit when c_emp_cursor%NOTFOUND;

       --计算该员工工资合计

       v_salary := v_emp.sal + NVL(v_emp.comm,0);

       --计算应缴税金额

       if v_salary <= 2000 then

          v_tax := 0.0;

       elsif v_salary <3000 then

          v_tax := (v_salary-2000)*0.05;

       else

           v_tax := (v_salary-3000)*0.1+1000*0.05;

       end if;

       --将该员工缴税信息插入emp_tax表

       insert into emp_tax1 

           (empno,ename,salary,tax,tax_date)

       values (v_emp.empno,v_emp.ename,v_salary,v_tax,sysdate);

   end loop;

   commit; --提交事务

   close c_emp_cursor;

end;

2.函数

  数据库本身提供了很多函数,例如字符串处理,

数值处理,日期函数等.当Oracle提供的不能满足

需求时,可以自定义函数.

   CREATE [OR REPLACE] FUNCTION 函数名

   [(参数 类型,参数 类型)]

   RETURN 返回类型

   IS或AS

   PL/SQL语句块

 注意:函数必须得有返回值.参数都是IN传入模式

----------示例1------------

sign(n):Oracle提供的判断n值的符号(>0,<0,=0).

自定义一个mysign,功能与sign一致.

create or replace function mysign(n number)

return number    --定义返回类型

is

v_f number := 0;

begin

   --根据n参数变量值,判断

   if n>0 then

      v_f := 1;

   elsif n=0 then

      v_f := 0;

   else

       v_f := -1;

   end if;

   return v_f;

end;

--------示例2----------

根据指定的empno值返回工资合计值

create or replace function get_sal

(v_empno emp.empno%TYPE)

return number

is

v_total_sal number(7,2) :=0.0; --存储工资合计

begin

 select sal+NVL(comm,0) into v_total_sal

 from emp where empno=v_empno;

 return v_total_sal; --返回合计结果

end;

------------删除过程和函数--------------

drop function xxx; --删除函数

drop procedure xxx; --删除存储过程

-------------示例3--------------------

自定义函数,传入工资合计值,返回应缴税额.

纳税逻辑参考存储过程部分的综合练习.

create or replace function get_tax

(v_salary number) return number

is

v_tax number(7,2) :=0.0; --存储应纳税额

begin

       if v_salary <= 2000 then

          v_tax := 0.0;

       elsif v_salary <3000 then

          v_tax := (v_salary-2000)*0.05;

       else

           v_tax := (v_salary-3000)*0.1+1000*0.05;

       end if;

       return v_tax;

end;

--------基于get_tax函数编写纳税存储过程------------

create or replace procedure emp_tax_pro

is

  CURSOR c_emp_cursor IS

    select * from emp; --定义游标

  v_emp emp%ROWTYPE; --存储一行游标记录

  v_salary number(7,2) :=0.0 ;  --存储工资合计

  v_tax number(7,2) :=0.0; --存储缴税金额

begin

   open c_emp_cursor;

   loop --循环抓取员工记录

       fetch c_emp_cursor into v_emp;

         exit when c_emp_cursor%NOTFOUND;

       --计算该员工工资合计

       v_salary := v_emp.sal + NVL(v_emp.comm,0);

       --计算应缴税金额

      v_tax := get_tax(v_salary);

       --将该员工缴税信息插入emp_tax表

       insert into emp_tax 

           (empno,ename,salary,tax,tax_date)

       values (v_emp.empno,v_emp.ename,v_salary,v_tax,sysdate);

   end loop;

   commit; --提交事务

   close c_emp_cursor;

end;

3.触发器

   1)触发器分类

 DML触发器:由insert,update,delete动作触发调用

 系统触发器:由登录,登出Oracle系统事件触发调用

 2)DML触发器的使用

    a.语句级触发器

 一个insert或update或delete语句自动触发一次.

  --------示例1----------

每次执行emp表的insert,update,delete操作,

都会自动统计出emp中员工总人数,

平均工资数,工资合计信息

create or replace trigger  emp_trigger1

after insert or update or delete on emp

declare

   v_count number(7) := 0; --存储员工人数

   v_avg number(7,2) := 0.0; --存储平均工资

   v_sum number(11,2) :=0.0; --存储工资总额

begin

   select count(*),avg(sal),sum(sal)

   into v_count,v_avg,v_sum

   from emp;

   dbms_output.put_line('员工人数' || v_count);

   dbms_output.put_line('平均工资' || v_avg);

   dbms_output.put_line('工资总额' || v_sum);

end;

    *b.行级触发器

 insert,update,delete语句影响的每一行记录都要

自动触发一次.

可以在触发器中获取操作影响的记录信息.

在行级触发器中,可以使用下面两个关键字

:NEW 表示执行SQL之后的记录状态

:OLD 表示执行SQL之前的记录状态

insert操作只能使用:NEW,表示要插入的记录

delete操作只能使用:OLD,表示要删除之前的记录

update操作可以使用:NEW和:OLD, :NEW表示

更新之后的记录;:OLD表示更新之前的记录

----------示例1------------

将emp表删除的记录,存入emp_bak备份表.

create or replace trigger emp_trigger2

before delete on emp for each row

declare 

begin

    --将要删除的记录empno,ename,sal写入emp_bak

    --OLD表示即将要删除的EMP记录

    insert into emp_bak(empno,ename,sal)

       values (:OLD.empno,:OLD.ename,:OLD.sal);

end;

注意:触发器中不要使用事务提交操作.

4.异常处理

begin

 ....

exception

 ....//异常处理区

end;

------------------

exception

  when  异常类型 then

      .......

  when 异常类型 then

      .......

  when others then

      .......

--------------------

1)异常分类

  a.预定义异常

    在Oracle内部,提前将错误编码和特定的

异常类型名关联.由Oracle自动触发该异常.

    exception

       when no_data_found then

           dbms_output.put_line('没有记录');

  -------示例----------

  declare

      v_sal number(7,2);

   begin

       select sal into v_sal from emp 

       where empno=7654;

       dbms_output.put_line(v_sal);

   exception

       when too_many_rows then

           dbms_output.put_line('返回记录太多啦');

       when others then

           dbms_output.put_line('出错啦,原因自己找吧');

   end;

  b.非预定义异常

    需要在程序中将错误编码和定义的异常类型

关联.由Oracle自动触发该异常

    myexception exception;--定义类型名

    PRAGMA EXCEPTION_INIT
(myexception,-2292);

   -
acbf
---------------------

    exception 

        when myexception then

             dbms_output.put_line('错误')

  c.自定义异常

    没有错误编号,违反业务需求时,程序员自定义

一个异常类型,在程序中使用raise 异常类型抛出.

 由程序定义和触发.

      no_update_exception exception;

   begin

      ......

      raise no_update_exception;//抛出异常

      .....

   exception

      when no_update_exception then

         dbms_output.put_line('没有更新记录');

   end;

5.Java调用存储过程

package org.tarena;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Types;

import org.tarena.util.DbUtil;

public class TestCallPro {

/**
* @param args
*/
public static void main(String[] args) {
// testPro1();
testPro2();
}

public static void testPro1(){
Connection con = null;
CallableStatement callStat = null;
try{
con = DbUtil.openConnection();
//获取执行存储过程的Statement
//指定命令格式{call 存储过程名()}
callStat = con.prepareCall("{call emp_tax_pro()}");
callStat.execute();
}catch(Exception ex){
ex.printStackTrace();
}finally{
DbUtil.closeConnection(con,callStat);
}
}

public static void testPro2(){
Connection con = null;
CallableStatement callStat = null;
try{
con = DbUtil.openConnection();
//获取执行存储过程的Statement
//指定命令格式{call 存储过程名()}
callStat =
con.prepareCall("{call sumsub(?,?,?,?)}");
callStat.setInt(1, 10);//指定传入值
callStat.setInt(2, 60);//指定传入值
callStat.registerOutParameter(
3, Types.INTEGER);//定义传出的integer类型
callStat.registerOutParameter(
4, Types.INTEGER);//定义传出的integer类型
callStat.execute();
//获取Out参数返回值
int sum = callStat.getInt(3);//获取第3个问号值
int sub = callStat.getInt(4);//获取第4个问号值
System.out.println("合计结果"+sum);
System.out.println("差值结果"+sub);
}catch(Exception ex){
ex.printStackTrace();
}finally{
DbUtil.closeConnection(con,callStat);
}
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息