PL/SQL 编程(一)

一、PL/SQL概念

  1. PL/SQL:Procedural language/sql,是oracle在标准sql语句上的扩展。PL/SQL不仅允许嵌入sql语言,还可以定义变量和常量,允许使用条件语句和循环语句,允许使用例外处理各种错误。

    155135_7ap7_1757476.png

    存储过程、函数、触发器都是存放在oracle数据库当中,比较复杂的任务一般可以用存储过程来实现,而这些都是用PL/SQL来编写的。存储过程、函数可以用Java程序来调用。

    PL/SQL的优点:

    1. 提高应用程序的运行性能:

      传统操作数据库方法是:SQL语句是写在Java程序中,然后Java程序得到一个connection,数据库拿到SQL语句                                        后需要编译(时间),将语句翻译成可识别的语言。然后返回结果。

      存储过程:减少Java程序中的SQL语言,而且存储过程已经在数据库中编译好。

    2. 模块化的设计思想:直接传参数调用过程

    3. 减少网络传输量:与传整个sql语句相比,调用存储过程只需要穿过程名,参数即可。

    4. 提高安全性:存储过程避免了重要信息的暴露,因为直接在数据库中。

PL/SQL缺点:

  1. 移植性不好:如果数据库变换,则这些存储过程全都要推翻掉。

PL/SQL的编写工具:Sql plus 或 PL/SQL developer。后者是用于开发pl/sql块的集成开发环境(IDE),这个一个独立的产品,而不是oracle的附带品。

一个简单案例:编写一个存储过程,向某表添加记录。

--创建一个表
create table mytest(
name varchar2(20),
password varchar2(20)
);

--创建存储过程
create or replace procedure pro_insertMytest is
begin
  --执行部分
  insert into mytest values ('SMITH', 'm123');
end;

--最后输入/表示执行存储过程
--如果有错误,SQL> show error; 显示错误信息

--如何调用该存储过程
--方式一: exec procedureName(param1, param2...);
--方式二: call procedureName(param1, param2...);

二、PL/SQL 基础知识

  1. PL/SQL可以编写什么:

    1. 块(block)是pl/sql的基本程序单元,编写pl/sql程序实际上就是编写pl/sql块。要实现相对复杂的功能,可能需要在一个pl/sql块中嵌套其他的pl/sql块。

    2. 简单分类:  

      164136_VVsC_1757476.png

    3. 编写规范:

      1. 注释:

        单行注释:--     多行注释: /* ...  */

      2. 标识符号的命名规范

        1) 定义变量时,建议使用 v_  作为前缀  v_name

        2) 定义常量时,建议使用 c_  作为前缀  c_rate

        3) 定义游标时,建议使用 _cursor  作为后缀  emp_cursor

        4) 定义例外时,建议使用 e_  作为前缀  e_error

  2. PL/SQL 块的结构:

    由三个部分构成:定义部分、执行部分、例外处理部分,如下:

    declare

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

    begin

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

    exception

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

    说明:定义部分是从declare开始的,可选;执行部分从begin开始,必须;处理部分是从exception开始,可选。

  3. PL/SQL块的实例:

  4. /*
      实例1-最简单的块
      说明:dbms_output 是oracle提供的包(package),类似于java的开发包,该包包含一些过程,put_line是该包的一个过程。
    */
    set serveroutput on;  --打开输出选项    set serveroutput off; 关闭
    begin
      dbms_output.put_line('Hello World');
    end;
    
    -------------------------------华丽的分割线-------------------------------------------
    
    /*
      实例2-包含定义部分和执行部分的pl/sql块
      说明: & 表示要接收从控制台输入的变量
    */
    declare
      v_ename varchar2(10);  -- 定义字符串变量
      v_sal number(7,2);
    begin
      --执行部分
      select ename, sal into v_ename, v_sal from emp where empno=&EmployeeNo;
      --在控制台显示用户名
      dbms_output.put_line('Employee Name:' || v_ename || '   Salary:' || v_sal);
    end;
    
    -------------------------------华丽的分割线-------------------------------------------
    
    /*
      实例3-包含定义部分、执行部分和例外处理的部分的pl/sql块
      说明: 上面的实例2,如果没有找到对应的empno,则需要进行异常处理
    */
    declare
      v_ename varchar2(10);  -- 定义字符串变量
      v_sal number(7,2);
    begin
      --执行部分
      select ename, sal into v_ename, v_sal from emp where empno=&EmployeeNo;
      --在控制台显示用户名
      dbms_output.put_line('Employee Name:' || v_ename || '   Salary:' || v_sal);
    exception
      when no_data_found then dbms_output.put_line('Employee number not exists!');
    end;
  5. 存储过程:用于执行特定的操作。建立存储过程时,可以指定输入参数(in)和指定输出参数(out)。使用输入参数,可以将数据传递到执行部分,而使用输出参数可以将执行部分的数据传递到应用环境。

    实例:编写一个存储过程,输入员工名、新工资去修改该员工的工资。以及用java调用存储过程。

  6. /*
      实例4-编写一个存储过程,输入员工名、新工资去修改该员工的工资
      说明: 执行带参数的存储过程:  exec pro_updateEmp('SMITH', 4678);
    */
    create or replace procedure pro_updateEmp(empName varchar2, newSal number) is
    --此处可以定义变量
    begin
      --执行部分
      update emp set sal = newSal where ename = empName;
    end;                                                                                                                                                                                           -------------------------------华丽的分割线-------------------------------------------          //Java 程序调用存储过程                                                                              public class TestOracleProcedure {
    	    public static void main(String[] args) {
    		    try {
    			//加载驱动
    			Class.forName("oracle.jdbc.driver.OracleDriver");
    			
    			//获取连接
    			Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:ORCL", "dog", "dog");
    			
    			//创建CallableStatement
    			//注:procedure 名大小写要保持和数据库一致
    			CallableStatement cs = conn.prepareCall("{call PRO_UPDATEEMP(?, ?)}");
    			cs.setString(1, "SMITH");
    			cs.setInt(2, 100);
    						
    			//执行
    			cs.execute();
    
    			//关闭资源
    			cs.close();
    			conn.close();
    			
    		    } catch (Exception e) {
    			e.printStackTrace();
    		    }
    	    }
        }
  7. 函数:用于返回特定的数据,当建立函数时,在函数头部必须包含return子句,而在函数体内必须包含return语句返回的数据。

  8. --函数案例
    --输入员工名,返回该员工的年薪
    create or replace function fun_YearSal(EmpName varchar2) return number is YearSal number(7,2);
    begin
      --执行部分
      select (sal+nvl(comm, 0))*12 into YearSal from emp where ename = EmpName;
      return YearSal;
    end;
    
    --如何调用function
    --定义变量abc接收返回结果
    var abc number;
    call fun_YearSal('SMITH') into:abc;
  9. 包:用于在逻辑上组合存储过程和函数,它由包规范和包体两部分组成。包的规范只包含了存储过程和函数的说明,但是没有存储过程和函数的实现代码。包体用于实现包规范中的存储过程和函数。

  10. --包 案例
    --创建包 pak_test,并声明该包有 存储过程 PRO_UPDATEEMP 和函数 FUN_YEARSAL
    create or replace package pak_test is
      procedure PRO_UPDATEEMP(empName varchar2, newSal number);
      function FUN_YEARSAL(EmpName varchar2) return number;
    end;
    
    --接下来要实现包体
    create or replace package body pak_test is
      --实现包中的存储过程
      procedure pro_updateEmp(empName varchar2, newSal number) is
      begin
        update emp set sal = newSal where ename = empName;
      end;
      --实现包中的函数
      function fun_YearSal(EmpName varchar2) return number is YearSal number(7,2);
      begin
        select (sal+nvl(comm, 0))*12 into YearSal from emp where ename = EmpName;
        return YearSal;
      end;
    end;
    
    --如何调用包的存储过程和函数:当调用包的存储过程和函数时,在过程和函数前需要带有包名。如果要访问其他方案的包,还需要在包名前加方案名。
    --包是pl/sql种非常重要的部分
    call pak_test.updateEmp('SMITH', '2000');
  11. 触发器:指隐含的执行的存储过程,一般不会主动调用。当定义触发器时,必须要指定触发的事件和操作。常用的触发时间包括insert, update, delete 语句,而触发操作实际就是一个pl/sql块。

    触发器是非常有用的,可以维护数据库的安全和一致性。(PL/SQL第二篇中讲述)

  12. 定义并使用变量:

    编写pl/sql程序时,可以定义变量和常量;在pl/sql程序中包括有:标量类型(scalar)、复合类型(composite)、参照类型(reference)、lob (large object)

    a. 标量类型(scalar)

        ---常用类型

        identifier [constant] datatype [not null] [:=| default expr]

        identifier:名称   constant:常量。需要指定初始值,而且值不能改变。  datatype:数据类型  not null:不能为       空    :=:给变量或者常量指定初始值   default:用于指定初始值  expr:指定初始值的pl/sql表达式,可以是文本         值、其他变量、函数等。

        案例:--定义一个变长字符串  v_ename varchar2(20)

                  --定义一个小数  范围-9999.99~9999.99  v_sal number(6,2)

                  --定义一个小数并给一个初始值5.4 (:= 是pl/sql的赋值号 )   v_sal2 number(6,2):=5.4

                  --定义一个日期类型的数据  v_date date; 

                  --定义一个布尔变量, 不能为空,初始值为false     v_valid boolean not null default false;

         ---%type 类型的标量,对于上面的常用标量,如果实际的变量长度超过了定义的长度,则会报错。为了降低pl/sql             程序的维护工作量,可以使用%type属性定义变量,这样它会按照数据库列来确定定义的变量的类型和长度。

              标识符名 表名.列名%type

  13. /* 案例:输入员工号,显示员工姓名、工资、个人所得税(税率0.03), 说明变量的使用*/
    declare
       --定义标量
       c_tax_rate number(3,2):=0.03;
       v_ename varchar2(6);
       v_sal number(7,2);
       v_tax number(7,2);
    begin
      --执行
      select ename, sal into v_ename, v_sal from emp where empno=&EmployeeNum;
      --计算所得税
      v_tax:=v_sal*c_tax_rate;
      --输出
      dbms_output.put_line('Ename: '||v_ename||'  Salary: '||v_sal||'  Tax: '||v_tax);
    exception
      when no_data_found then dbms_output.put_line('Employee number not exists!'); 
    end;
    
    --上述案例使用 %type 来定义变量,使用较多
    declare
       --定义标量
       c_tax_rate number(3,2):=0.03;
       v_ename emp.ename%type;
       v_sal emp.sal%type;
       v_tax number(7,2);
    begin
      --执行
      select ename, sal into v_ename, v_sal from emp where empno=&EmployeeNum;
      --计算所得税
      v_tax:=v_sal*c_tax_rate;
      --输出
      dbms_output.put_line('Ename: '||v_ename||'  Salary: '||v_sal||'  Tax: '||v_tax);
    exception
      when no_data_found then dbms_output.put_line('Employee number not exists!'); 
    end;

    b. 复合类型(composite):存放多个值的变量,主要包括:pl/sql 记录、pl/sql 表、嵌套表、varray 四种,下面将介     绍前两种比较常用的。

  14. /*
    复合类型--pl/sql记录
    类似高级语言中的结构体,注意:当引用pl/sql记录成员时,必须要加记录变量作为前缀(记录变量名.记录成员)
    */
    declare
      --定义一个pl/sql记录类型,名为 emp_record_type,包含 ename,salary,title 三个数据
      type emp_record_type is record(
           ename emp.ename%type,
           salary emp.sal%type,
           title emp.job%type);
       --定义一个emp_record_type类型的变量,emp_record
       emp_record emp_record_type;
    begin
      select ename, sal, job into emp_record from emp where empno = 7369;
      dbms_output.put_line('Ename: '||emp_record.ename||'  Salary: '||emp_record.salary||'  Title: '||emp_record.title);
    end;
    
    /*
    复合类型--pl/sql表
    类似高级语言中的数组,不同的是,pl/sql表中的元素下标可以为负,并且下标没有限制
    */
    declare
      --定义一个pl/sql表类型,名为emp_table_type,用于存放 emp.name%type 类型的数据
      -- index by binary_integer 表示下标是整数
      type emp_table_type is table of emp.ename%type index by binary_integer;
      --定义一个emp_table_type 类型的变量 emp_table
      emp_table emp_table_type;
    begin
      select ename into emp_table(-1) from emp where empno = 7369;
      dbms_output.put_line('Ename: '|| emp_table(-1));
    end;
    
    --问题:如果返回多行数据,该怎么接收呢?

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

  15. /*
    参照变量--ref cursor游标变量
    定义游标时,不需要指定select语句,但使用游标时(open) 需要指定select 语句,这样游标就和select语句结合
    */
    --案例:输入部门号,显示该部门所有员工的姓名和工资
    declare
      --定义游标类型
      type emp_cursor_type is ref cursor;
      --定义游标变量
      emp_cursor emp_cursor_type;
      --定义两个变量去接收结果
      v_ename emp.ename%type;
      v_sal emp.sal%type;
    begin
      --执行
      --将游标emp_cursor 和 select语句结合,让游标指向结果集
      open emp_cursor for select ename, sal from emp where deptno = &DeptNum;
      --循环取出
      loop
        --取出游标
        fetch emp_cursor into v_ename, v_sal;
        --循环结束条件,emp_cursor为空
        exit when emp_cursor%notfound;
        dbms_output.put_line('Ename: '||v_ename||'  Salary: '||v_sal);
      end loop;
    end;
  16. pl/sql 控制结构

    a. 条件分支语句

  17. /*
    案例 
    实例11-条件分支语句:
          1.编写一个存储过程,输入员工名,如果该员工工资低于2000,就给该员工工资增加10%
          2.(二重条件分支) 如果补助不是0,则增加100,否则补助为200
          3.(多重条件分支) 如果该员工职位是PRESIDENT,工资加1000; 如果职位是 MANAGER,工资加500;其他情况工资加200
    */
    create or replace procedure update_sal(EmployeeName varchar2) is
    --定义
    v_sal emp.sal%type;
    v_comm emp.comm%type;
    v_job emp.job%type;
    begin
      --执行
      select sal, comm, job into v_sal, v_comm, v_job from emp where ename = EmployeeName;
      --判断 1
      if v_sal<2000 then
        update emp set sal=sal*1.1 where ename = EmployeeName;
      end if;
      
      --判断 2
      if v_comm <> 0 then
        update emp set comm=comm+100 where ename = EmployeeName;
      else
        update emp set comm=comm+200 where ename = EmployeeName;
      end if;
      
      --判断3
      if v_job = 'PRESIDENT' then
        update emp set sal=sal + 1000 where ename = EmployeeName;
      elsif v_job = 'MANAGER' then
        update emp set sal=sal + 500 where ename = EmployeeName;
      else
        update emp set sal=sal + 200 where ename = EmployeeName;
      end if;
      
      exception
        when no_data_found then dbms_output.put_line('Employee number not exists!');
    end;

    b. 循环语句:pl/sql中包含三种循环,loop、while、for

  18. /*
    案例
    实例12-循环语句: loop
           通过输入Username 向Users表中循环添加10条数据
    */
    --create table sql
    drop table users;
    
    create table users(
    userid number(10) primary key,
    username varchar2(30) not null,
    password varchar2(30));
    
    create or replace procedure loop_add_user1(Username varchar2) is
    --定义
    v_num number:=1;
    begin
      loop
        insert into users values(v_num, Username, 'loop');
        --判断是否要退出循环
        exit when v_num = 10;
        --num 自增
        v_num:=v_num + 1;
      end loop;
    end;
    
    /*
    案例 
    实例13-循环语句: while
           通过输入Username 向Users表中循环添加10条数据,从11条记录开始添加
    */
    create or replace procedure loop_add_user2(Username varchar2) is
    --定义
    v_num number:=11;
    begin
      while v_num <= 20 loop
        insert into users values(v_num, Username, 'whileLoop');
        --判断是否要退出循环
        exit when v_num = 10;
        --num 自增
        v_num:=v_num + 1;
      end loop;
    end;
    
    /*
    案例 
    实例14-循环语句: for
           通过输入Username 向Users表中循环添加10条数据,从21条记录开始添加
    */
    create or replace procedure loop_add_user3(Username varchar2) is
    begin
      --从21开始循环,直到30结束.可以看到控制变量i在隐含中不停的增加
      for i in reverse 21..30 loop
        insert into users values(i, Username, 'forLoop');
      end loop;
    end;
  19. pl/sql 顺序控制语句 goto 、 null

  20. /*
    案例
    实例15-顺序控制语句: goto 、 null
    说明:goto end_loop,这里end_loop是定义的标签  <<end_loop>>
          null语句不会执行任何操作,并且会直接将控制传递到下一条语句,一般只是为了提高程序可读性
    */
    declare
    i int := 1;
    begin
      loop
        dbms_output.put_line('Output i=' || i);
        if i=10 then
          goto end_loop;
        else
          null;
        end if;
        i := i + 1;
      end loop;
      dbms_output.put_line('Cycle Over1');
      <<end_loop>>
      dbms_output.put_line('Cycle Over2');
    end;

转载于:https://my.oschina.net/u/1757476/blog/495398

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值