PL/SQL

pl/sqlprocedural language/sql)是Oracle在标准的sql语言上的扩展。pl/sql不仅允许嵌入式sql语言,还可以定义变量和常量,允许使用条件语句和循环语句,允许使用例外处理各种错误。这样使得他的功能变的更强大。缺点是移植性不好。

编写一个存储过程,向表中添加数据。

  1. create table mytest (name varchar2(30),passwd varchar2(30));
  2. create or replace procedure xxc_pro1 is

begin

insert into mytest values ('小红','m123');

end;

  1. 调用过程  exec  过程名(参数1,参数2…)或call 过程名参数1,参数2…
    • exec xxc_pro1;   或者是
    • call xxc_pro1;

pl/sql可以做什么?

块:包括过程、函数、触发器、包。

编写规范:

  1. 注释  --:单行注释

egselect * from emp where empno=7788;--取得员工信息

    /*……*/多行注释

  1. 表示符号(变量)的命名规范:
    • 当定义变量时,建议用v_作为前缀:v_ename
    • 当定义常量时,建议用c_作为前缀:c_rate
    • 当定义游标时,建议用_cursor作为后缀:emp_cursor
    • 当定义例外时,建议用e_作为前缀:e_error

块(block)是pl/sql的今本程序单元,编写pl/sql程序实际上就是在编写pl/sql块;pl/sql块由三部分组成:定义部分,执行部分,例外处理部分。

declare  --可选部分

/*定义部分:定义常量,变量,游标,例外,复杂数据类型*/

begin   --必选部分

/*执行部分:要执行的pl/sql语句和sql语句*/

exception  --可选部分

/*例外处理部分:处理运行的各种错误*/

实例1:只包含执行部分的pl/sql

SQL> set serveroutput on  --打开输出

SQL> begin

  2  dbms_output.put_line('hello');

  3  end;

  4  /

说明:dbms_outputoracle提供的包,该包包含一些过程,put_line就是其中之一。

实例2:包含定义部分和执行部分

SQL> declare

  2  v_ename varchar2(5);

  3  begin

  4  select ename into v_ename from emp where empno = &no;

  5  dbms_output.put_line('雇员名'||v_ename);

  6  end;

  7  /

说明:&:从控制台输入变量,会弹出一个对话框。

实例3.同时输出雇员名和工资

SQL> declare

  2  v_ename varchar2(20);

  3  v_sal number(10,2);

  4  begin

  5  select ename,sal into v_ename,v_sal from emp where empno=&no;

  6  dbms_output.put_line('雇员名:'||v_ename||' 工资:'||v_sal);

  7  end;

  8  /

包含定义,执行,和例外处理的pl/sql块。

实例4.当输入的员工号不存在时

SQL> declare

  2  v_ename varchar2(20);

  3  v_sal number(10,2);

  4  begin

  5  select ename,sal into v_ename,v_sal from emp where empno =&no;

  6  dbms_output.put_line('雇员名:'||v_ename||' 工资:'||v_sal);

  7  exception   --异常处理部分

  8  when no_data_found then

  9  dbms_output.put_line('请输入正确的员工号!');

 10  end;

 11  /

以上为块的基础,下面来介绍块的各个组成:过程,函数,触发器,包。

过程

过程用于执行特定的操作,当执行过程的时候,可以指定输入参数(in),也可以指定输出参数(out)。通过在过程中使用输入参数,可以讲数据输入到执行部分,通过使用输出参数,可以将执行部分的数据输出到应用环境,在pl/sql中可以使用create procedure命令来创建过程。

编写一个存储过程,可以输入雇员名和新工资来改变员工工资。

--案例

create or replace procedure xxc_pro3(newname in varchar2,newsal in number) is 

begin

update emp set sal=newsal where ename=newname;

end;

调用   exec xxc_pro3(‘SCOTT’,2900);

--in表示是输入参数,可以不写,默认是in,但out必须写。

java程序中调用存储过程来修改工资;

//演示java程序调用oracle中的存储过程

package TestOraPro;//根据不同的包,包名不同

import java.sql.*;

public class TestOraPro {

    /**

     * @param args

     */

    public static void main(String[] args) {

       // TODO Auto-generated method stub

       try{

       //加载驱动

       Class.forName("oracle.jdbc.driver.OracleDriver");

       //得到连接   1521为端口号

       Connection ct = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:CUIXIAO2","scott","xxc");

       //创建callablestatement

       CallableStatement cs=ct.prepareCall("{call xxc_pro3(?,?)}");

       //给?赋值

       cs.setString(1, "SMITH");

       cs.setInt(2,2600);

       //执行

       cs.execute();

      

       }catch(Exception e){

            e.printStackTrace();

       } finally{

           //关闭资源

           cs.close();

           ct.close();

}

    }

}

以上为过程基础,后会详细讲。

函数

函数用于返回特定的数据,当建立函数时,在函数头部必须包含return子句,而在函数体内必须包含return语句来返回数据,可以使用create function来建立函数。

案例1.返回工人年工资。

--函数案例

create or replace function xxc_fun1(newname varchar2)

return number is yearSal number(10,2);

begin

select sal*12+nvl(comm,0)*12 into yearSal from emp where ename=newname;

return yearSal;

end;

sqlplus中调用函数

sql>var income number;

sql>call xxc_fun1(‘SCOTT’) into: income;

sql>print income;

java中调用函数

select xxc_fun1(‘SCOTT’) from dual;

这样可以通过rs.setInt(1)得到返回结果。

包用于在逻辑上组合过程和函数,它由包规范和包体两部分组成。

1.我们可以用create(or replace) package命令来建包;

    实例:create package xxc_package is

           Procedure update_sal (name varchar2,newsal number);

           Function annual_sal(name varchar2) return number;

           End;

包规只包含过程和函数的说明,但是没有函数和过程的实现代码。

包体用于实现包规范中的过程和函数。

2.建立包体可以用create package body命令。

    create package body xxc_package is

    Procedure update_sal(name varchar2,newsal number) is

    begin

    update emp set sal=newsal where ename=name;

    end;

    function annual_sal(name varchar2) return number is

    annual_salary number;

    begin

    select sal*12+nvl(comm,0)*12 into annual_salary from emp    where ename=name;

    return;

    end;

    end;

3.如何调用包中的过程和函数。

    在过程和函数前加上包名。

    exec xxc_package.update_sal(‘SCOTT’,120);

触发器

触发器是指隐含的存储过程,当定义触发器是,必须指定触发事件和触发的操作,常用的触发操作有insertupdatedelete。触发器操作实际上是一个pl/sql块,可以使用create trigger命令来创建触发器。因为触发器内容很多,会在后面详细介绍,触发器是非常有用的,可以用来维护数据库的安全和一致性。

在编写pl/sql块时可以定义的变量和常量;

  1. 标量类型(scalar);
  2. 复合类型(composite);
  3. 参照类型(reference);
  4. lob(large ofject);

标量类型(scalar)—常用类型

语法格式(即前边最常见的格式)

变量名 [constant] datatype [not null] [:=/default expr]

expr:指定初始值的pl/sql表达式,可以是文本,其他变量,函数等。

定义标量案例

  • 定义一个变长字符串

V_ename varchar2(10);

  • 定义一个小数,范围在-9999.99~9999.99

v_sal number(6,2);

  • 定义一个小数并赋初值  :=pl/sql的赋值符号;

v_sal number(6,2):=12.3;

  • 定义一个日期类型数

v_birthday date;

  • 定义一个布尔类型变量,不能为空,默认是false;

v_valid boolen not null default false;

下面以输入员工号,显示员工姓名,工资,个人所得税(税率为0.03)为例,说明变量的使用。

declare

c_tax_rate number(3,2):=0.03;

v_ename varchar2(5);

v_sal number(10,2);

v_tax_sal number(10,2);

begin

select ename,sal into v_ename,v_sal from emp where empno=&no;

v_tax_sal:=v_sal*c_tax_rate;

dbms_output.put_line(‘员工名:’||v_ename||’工资:’||v_sal||’缴税:’||v_tax_sal);

end;

对于上边的pl/sql块,有一个问题。

如果员工的名字长于5个字符,就会出现错误,为了降低pl/sql的维护工作量,可以使用%type属性来定义变量,这样它会按照数据库列来确定你定义的变量的类型和长度。

变量名  表名.列名%type

对于上例的改动

v_ename emp.ename%type;

v_sal emp.sal%type;

复合类型(composite-介绍

用于存放多个值的变量,主要包括这几种:

① pl/sql记录;

② pl/sql表;

嵌套表;

④ varray

其中① ② 用的多,③ ④ 用的少。

1.pl/sql的记录。

类似于高级语言的结构体,当引用pl/sql记录成员的时候,必须要加记录变量作为前缀(记录变量.记录成员)。

如:declare

type emp_record_type is record(name emp.ename%type, salary emp.sal%type,title emp.job%type);

xxc_record emp_record_type;

begin

select ename,sal,job into xxc_record from emp where empno=&no;

dbms_output.put_line(‘员工名:’||xxc_record.name||’工资:’||xxc_record.salary);

end;

2.pl/sql

相当于高级语言中的数组,需要注意的是高级语言中的数组的下标不能为负,但表的下标没有限制,可以为负。

例如:declare           -- binary_integer表示下标是整数

type xxc_table_type is table of emp.ename%type index by binary_integer;

xxc_table xxc_table_type;

begin        --下标0可以是任何整数,-1-2,23都可以,但输出的时候也要是同一个数,不然会出错。

select ename into xxc_table(0) from emp where empno=7788;

dbms_output.put_line(‘员工名:’||xxc_table(0));

end;

在这个实例中,如果把where子句去掉,就会出错,因为你只请求了一个数据,而返回了多行数据。解决办法是使用参照变量。

参照变量

参照变量是指用于存放数据指针的变量,通过使用参照变量可以使得应用程序共享相同的对象,从而降低占用的空间。在编写pl/sql程序时,可以使用游标变量(ref cursor)和对象类型变量(ref obj_type)这两种参照变量,用得多的是游标变量。

游标变量

定义游标是,不需要指定select语句,但当使用游标(open)时,需要指定select语句,这样一个游标就与一个游标就与一个select语句结合了。

实例:编写一个pl/sql块,输入部门号,显示所有员工名和工资。

       declare

       type xxc_emp_cursor is ref cursor;

test_cursor xxc_emp_cursor;

       v_ename emp.ename%type;

       v_sal emp.sal%type;

       begin

open test_cursor for select ename,sal from emp where deptno=&no;

loop

fetch test_cursor into v_ename,v_sal;

exit when test_cursor%notfound;

dbms_output.put_line(‘名字:’||v_ename||’工资:’||v_sal);

end loop;

close test_cursor;

end;

②  的基础上,如果员工的工资低于2000,加100

declare

       type xxc_emp_cursor is ref cursor;

test_cursor xxc_emp_cursor;

       v_ename emp.ename%type;

       v_sal emp.sal%type;

       begin

open test_cursor for select ename,sal from emp where deptno=&no;

loop

fetch test_cursor into v_ename,v_sal;

exit when test_cursor%notfound;

if v_sal<2000 then

update emp set sal=sal+100 where ename=v_ename;

v_sal=v_sal+100;

end if;

dbms_output.put_line(‘名字:’||v_ename||’工资:’||v_sal);

end loop;

close test_cursor;

end;

Pl/sql分支控制语句

1.条件分支语句

    if…then   if…then…else   if…then…eslif…then…else…

if语句结束时要有end if

    不等于号是<>,不是!=

    if job=’MANAGER’,是单引号,不是双引号。

2.循环语句 

--looppl/sql中最简单的循环语句,这种循环以loop开始,以end loop结束,此循环至少会被执行一次。

例:编写一个循环过程,可输入用户名,并循环添加10个用户到user表中,用户编号从1开始。

Sql>create table user(userid number(10),username varchar2(20));

Sql>create procedure xxc_insert_user1(name varchar2) is

       declare

       v_Num number:=1;  --不用指定大小吗?

       begin

       loop

       insert into user values(v_num,name);

       exit when v_num=10;

       v_num:=v_num+1;  --Oracle中没有++符号

       end loop;

       end;

--while循环

while条件为真时,执行循环体。

while…loop开头,以end loop结尾。

例:上题,用户号从11开始,添加10名用户。

create procedure xxc_insert_user2(name varchar2) is

declare

v_Num number:=11;  --不用指定大小吗?

    begin

    while v_num<=20 loop

    insert into user values(v_num,name);

    v_num:=v_num+1;

    end loop;

    end;

--for循环

循环基本结构如下:

begin

    for i in reverse 1..10 loop

    insert into users(I,’小红’);

    end loop;

    end;

顺序控制语句   --goto,null

Goto语句会增加程序的复杂性,降低可读性,一般不使用,但有时会很方便。

基本语法     goto lable lable是定义好了的标签。

例:declare

    i int:=1;

    begin

    loop

    dbms_output.put_line(‘i=’||i);

    if i=10 then

    goto end_loop;

    end if;

    end loop;

<<end_loop>>    --此为标签,注意格式

dbms_output.put_line(‘循环结束’);

end;

null语句不执行任何操作,好处是可以提高程序的可读性。

例:if sal<2000 then

       update emp set sal=sal+100 where ename=name;

       else

       null;

编写分页过程

从易到难,先写简单的过程。

写无返回值的过程,并被Java程序调用,前边已写过,不再重写。现在写一个有返回值的过程,并用Java程序调用。

例:输入员工号,输出员工姓名。

Create procedure xxc_pro4 (empid in number,empname out varchar2) is

Begin

Select ename into empname from emp where empno = empid;

End;

Java程序调用。

import java.sql.*;

public class TestOraPro1 {

    /**

     * @param args

     */

    public static void main(String[] args) {

       // TODO Auto-generated method stub

       try{

       //加载驱动

       Class.forName("oracle.jdbc.driver.OracleDriver");

       //得到连接   1521为端口号

       Connection ct = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:CUIXIAO2","scott","xxc");

       //创建callablestatement

       CallableStatement cs=ct.prepareCall("{call xxc_pro4(?,?)}");

       //给第一个?赋值

       cs.setInt(1,7788);

       //给第二个?赋值

       cs.registerOutParameter(2,oracle.jdbc.OracleTypes.VARCHAR);

       //执行

       cs.execute();

       //取出返回值,要注意?的顺序,第几个?是返回值就写几。

       String name = cs.getString(2);

       System.out.println(“7788的名字是”+name);

      

       }catch(Exception e){

            e.printStackTrace();

       }finally{

           //关闭资源

           cs.close();

           ct.close();

}

    }

}

案例扩展:输入一个员工号,输出该员工的名字,工资和岗位。

create procedure xxc_pro5(empid in number,empname out varchar2,empsal out number,empjob out varchar2) is

begin

select ename,sal,job into empname,empsal,empjob from emp where empno=empid;

end;

上一篇:【SQL调优指南--附带实例】


下一篇:【Next.js 入门教程系列】05-数据库