• oralce实战第七天pl/sql的进阶、分页存储过程、例外和视图


    第七天

    内容介绍

    1.pl/sql的进阶

    2.oracle的视图

    3.oracle的触发器

    期望目标

    1.掌握pl/sql的高级用法(能编写分页过程模块,下订单过程模块…)。

    2.会处理oracle常见的例外。

    3.会编写oracle各种触发器。

    4.理解视图的概念并能灵活使用视图。

    pl/sql的进阶

    控制结构

    在任何计算机语言(c,java,pascal)都有各种控制语句(条件语句,循环语句,顺序控制结构…)在pl/sql中也存在这样的控制结构。

    条件分支语句

    Plsql中提供了三种条件分支语句

    If…then

    If…then….else

    If…then…elsif…else

    简单的条件判断If…then

    编写一个过程,可以输入一个雇员名,如果该雇员的工资低于2000,就给雇员工资增加10%。

    create or replace procedure p_updateEmpSal
    (v_ename varchar2)
    is
    v_sal emp.sal%type;
    begin
      select sal into v_sal
      from emp
      where ename= v_ename;
      if v_sal < 2000 then
      update emp
      set sal = sal * 1.1
      where ename = v_ename;
      end if;
    end;

    exec p_updateEmpSal('SCOTT');

    二重条件分支If…then….else

    编写一个过程,可以输入一个雇员名,如果该雇员的补助不是0就在原来的基础上增加100;如果补助为0就把补助设为200。

    create or replace procedure p_updateEmpSalAndComm
    (v_ename varchar2)
    is
    v_comm emp.comm%type;
    begin
      select comm into v_comm
      from emp
      where ename= v_ename;
      if v_comm <> 0 then
        update emp
        set comm = comm + 100
        where ename = v_ename;
      else
        update emp
        set comm = comm + 200
        where ename = v_ename;
      end if;
    end;

    多重条件分支If…then…elsif…else

    编写一个过程,可以输入一个雇员的编号,如果该雇员的职位是president就给他的工资增加1000,如果该雇员的职位是MANAGER就给他的工资增加500,其它职位的雇员工资增加200.

    create or replace procedure p_updateSalBaseTitle
    (v_empno number)
    is
    v_job emp.job%type;
    begin
      select job into v_job
      from emp
      where empno = v_empno;
      if v_job = 'PRESIDENT' then
         update emp set sal=sal + 1000 where empno = v_empno;
      elsif v_job = 'MANAGER' then
         update emp set sal=sal + 500 where empno = v_empno;
      else
         update emp set sal=sal + 200 where empno = v_empno;
      end if;
    end;

    循环语句

    loop

    是pl/sql中最简单的循环语句,这种循环语句以loop开头,以end loop结尾,这种循环至少会被执行一次。

    案例:现有一张表users,表结构如下

    用户id    用户名

    1         zhangsan

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

    create or replace procedure p_addUsers
    (v_name varchar2)
    is
    v_num number := 1;
    begin
    loop
      insert into users values(v_num,v_name);
      exit when v_num = 10;
      v_num := v_num + 1; 
    end loop;
    end;

    while

    基本循环至少要执行循环体一次,而对于while循环来说,只有条件为true时,才会执行循环体语句,while循环以while…loop开始,以end loop结束。

    案例:现有一张表users,表结构如下

    用户id    用户名

    1         zhangsan

    请编写一个过程,可输入用户名,并循环添加10个用到users表中,用户编号从11开始增加。

    create or replace procedure p_addUsers2
    (v_name varchar2)
    is
    v_num number := 11;
    begin
    while v_num <= 20
    loop
      insert into users values(v_num,v_name);
      v_num := v_num + 1; 
    end loop;
    end;

    For

    for循环的基本结构如下:

    create or replace procedure p_addUsers3
    (v_name varchar2)
    is
    begin
    for i in reverse 1..10 loop
        insert into users values(i,v_name);
    end loop;
    end;

    我们可以看到控制变量I, 在隐含中就在不停的增加。

    顺序控制语句

    Goto语句

    Goto语句用于跳转到特定标号去执行语句。注意由于使用goto语句会增加程序的复杂性,并使得应用程序可读性变差,所以在做一般应用开发时,建议不要使用goto语句。

    基本语法如下goto lable是已经定义好的标号名。

    declare
    i int := 1;
    begin
    loop
      dbms_output.put_line('i = ' || i);
      if i=10 then
         goto end_loop;
      end if;
      i := i + 1;
    end loop;
    <<end_loop>>
    dbms_output.put_line('loop end');
    end;

    null

    null语句不会执行任何操作,并且直接将控制传递到下一条语句。使用null语句的主要好处是可以提高pl/sql的可读性。

    declare
    v_sal emp.sal%type;
    v_ename emp.ename%type;
    begin
      select ename,sal into v_ename,v_sal
      from emp
      where empno = &no;
      if v_sal < 3000 then
        update emp
        set comm = sal * 0.1
        where ename = v_ename;
      else
        null;
      end if;
    end;

    编写分页过程

    分页是任何一个网站(bbs,网上商城,blog)都会使用到的技术,因此学习pl/sql编程开发就一定要掌握该技术。

    无返回值的存储过程

    古人云:欲速则不达。为了让大家比较容易接受分页过程编写,我们还是从简单到复杂,循序渐进的给大家讲解。首先是掌握最简单的过程,无返回值的存储过程:

    案例:现有一张表,表结构如下:

    书号

    书名

    出版社

    请编写一个过程,可以向book表添加书,要求通过java程序调用该过程。

    create table book(
           bookId number,
           bookName varchar2(50),
           pulishHouse varchar2(50)
    );

    create or replace procedure p_addBook
    (
      vBookId in number,
      vBookName in varchar2,
      vPublishHouse in varchar2
    )
    is
    begin
      insert into book
      values
      (
        vBookId,
        vBookName,
        vPublishHouse
      );
    end;

    在java中调用:

    package com.anllin.jdbc.oracle;

     

    import java.sql.*;

     

    public class NoReturnValProc

    {

        public static void main(String[] args)

        {

           Connection conn = null;

           CallableStatement stmt = null;

           try

           {

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

               conn = DriverManager

                      .getConnection("jdbc:oracle:thin:@127.0.0.1:1521:myorcl",

                             "scott", "tiger");

               stmt = conn.prepareCall("{call p_addBook(?,?,?)}");

               stmt.setInt(1, 1);

               stmt.setString(2, "水是最好的药");

               stmt.setString(3, "人民出版社");

               stmt.execute();

           }

           catch (Exception e)

           {

               throw new RuntimeException(e);

           }

           finally

           {

               try

               {

                   if (null != stmt)

                      stmt.close();

                  if (null != conn)

                      conn.close();

               }

               catch (Exception ex)

               {

                  throw new RuntimeException(ex);

               }

           }

        }

    }

    有返回值的存储过程(非列表)

    再看如何处理有返回值的存储过程:

    案例:编写一个过程,可以输入雇员的编号,返回该雇员的姓名。

    create or replace procedure sp_getEnameByEmpno
    (spno in number,spName out varchar2)
    is
    begin
      select ename into spName
      from emp
      where empno = spno;
    end;

    在java中调用

    package com.anllin.jdbc.oracle;

    import java.sql.*;

    public class HaveReturnValProc

    {

        public static void main(String[] args)

        {

           Connection conn = null;

           CallableStatement stmt = null;

           try

           {

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

               conn = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:myorcl","scott","tiger");

               stmt = conn.prepareCall("{call sp_getEnameByEmpno(?,?)}");

               stmt.setInt(1,7788);

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

               stmt.execute();

               String name = stmt.getString(2);

               System.out.println("7788的名字是:"+ name);

           }

           catch (Exception e)

           {

               throw new RuntimeException(e);

           }

           finally

           {

               try

               {

                  if(null != stmt) stmt.close();

                  if(null != conn) conn.close();

               }

               catch (Exception e)

               {

                  throw new RuntimeException(e);

               }

           }

        }

    }

    案例扩张:编写一个过程,可以输入雇员的编号,返回该雇员的姓名,工资和岗位。

    create or replace procedure sp_getEmpMsgByEmpno
    (
     vNo in number,
     vName out varchar2,
     vSal out number,
     vJob out varchar2
    )
    is
    begin
      select ename,sal,job into vName,vSal,vJob
      from emp
      where empno = spno;
    end;

    在java中调用

    package com.anllin.jdbc.oracle;

     

    import java.sql.CallableStatement;

    import java.sql.Connection;

    import java.sql.DriverManager;

     

    public class HaveReturnValProc2

    {

        public static void main(String[] args)

        {

           Connection conn = null;

           CallableStatement stmt = null;

           try

           {

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

               conn = DriverManager

                      .getConnection("jdbc:oracle:thin:@127.0.0.1:1521:myorcl",

                             "scott", "tiger");

               stmt = conn.prepareCall("{call sp_getEmpMsgByEmpno(?,?,?,?)}");

               stmt.setInt(1, 7788);

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

               stmt.registerOutParameter(3, oracle.jdbc.OracleTypes.DOUBLE);

               stmt.registerOutParameter(4, oracle.jdbc.OracleTypes.VARCHAR);

               stmt.execute();

               String name = stmt.getString(2);

               double sal = stmt.getDouble(3);

               String job = stmt.getString(4);

               System.out.println("7788的名字是:" + name + ",工资是:" + sal + ",职位是:"

                      + job);

           }

           catch (Exception e)

           {

               throw new RuntimeException(e);

           }

           finally

           {

               try

               {

                  if (null != stmt)

                      stmt.close();

                  if (null != conn)

                      conn.close();

               }

               catch (Exception e)

               {

                  throw new RuntimeException(e);

               }

           }

        }

    }

    有返回值的存储过程(列表[结果集]

    案例:编写一个过程,输入部门号,返回该部门所有雇员信息。

    对该题分析如下:

    由于oracle存储过程没有返回值,它的所有返回值都是通过out参数来替代的,列表同样也不例外,但由于是集合,所以不能用一般的参数,必须要用package了,所以要分两部分:

    1)建一个包,如下:

    create or replace package testpackage as
    type test_cursor is ref cursor;
    end testpackage;

    2)建立存储过程

    create or replace procedure p_getEmpsByDeptno
    (
     vNO in number,
     p_cursor out testpackage.test_cursor
    )
    is
    begin
    open p_cursor for
    select * from emp
    where deptno = vNO;
    end;

    3)在java中调用

    package com.anllin.jdbc.oracle;

    import java.sql.*;

    public class HaveReturnValProc3

    {

        public static void main(String[] args)

        {

           Connection conn = null;

           CallableStatement stmt = null;

           try

           {

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

               conn = DriverManager

                      .getConnection("jdbc:oracle:thin:@127.0.0.1:1521:myorcl",

                             "scott", "tiger");

               stmt = conn.prepareCall("{call sp_getEmpsByDeptno(?,?)}");

               stmt.setInt(1, 20);

               stmt.registerOutParameter(2, oracle.jdbc.OracleTypes.CURSOR);

               stmt.execute();

               ResultSet rs = (ResultSet) stmt.getObject(2);

               while (rs.next())

               {

                  System.out.println(rs.getInt(1) + " " + rs.getString(2));

               }

           }

           catch (Exception e)

           {

               throw new RuntimeException(e);

           }

           finally

           {

               try

               {

                  if (null != stmt)

                      stmt.close();

                  if (null != conn)

                      conn.close();

               }

               catch (Exception e)

               {

                  throw new RuntimeException(e);

               }

           }

        }

    }

    编写存储过程

    有了上面的基础,相信大家可以完成分页存储过程了。

    要求,请编写一个存储过程,要求可以输入表名,每页显示记录数,当前页。返回总记录数,总页数和返回的结果集。

    分页的写法:

    select * from
    (     
           select t.*,rownum rn
           from (select * from emp) t
           where rownum <= 10
    )
    where rn >= 6;

    可以当成一个模板来使用。

    分页的存储过程

    create or replace package pagingPackage as
    type paging_cursor is ref cursor;
    end pagingPackage;

    create or replace procedure pagingProcedure
    (
       tableName in varchar2,
       pageSize in number,
       pageNow in number,
       rowsCount out number,
       pageCount out number,
       p_cursor out pagingPackage.paging_cursor
    )
    is
    v_sql varchar2(1000);
    v_begin number := (pageNow - 1) * pageSize + 1;
    v_end number := pageNow * pageSize;
    begin
       v_sql := 'select * from ( select t.*,rownum rn from (select * from '||tableName||') t where rownum <= '|| v_end ||') where rn >= '|| v_begin;
       open p_cursor for v_sql;
       v_sql := 'select count(*) from '||tableName;
       execute immediate v_sql into rowsCount;
       if mod(rowsCount,pageSize) = 0 then
          pageCount := rowsCount/pageSize;
       else
          pageCount := rowsCount/pageSize + 1;
       end if;
       close p_cursor;
    end;

    分页的算法请参考第三天的内容。

    在java中调用

    package com.anllin.jdbc.oracle;

     

    import java.sql.*;

    import oracle.jdbc.*;

     

    public class Paging

    {

        public static void main(String[] args)

        {

           Connection conn = null;

           CallableStatement stmt = null;

     

           try

           {

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

               conn = DriverManager

                      .getConnection("jdbc:oracle:thin:@127.0.0.1:1521:orcl",

                             "scott", "tiger");

               stmt = conn.prepareCall("{call pagingProcedure(?,?,?,?,?,?)}");

     

               stmt.setString(1, "emp");

               stmt.setInt(2, 5);

               stmt.setInt(3, 1);

     

               stmt.registerOutParameter(4, OracleTypes.INTEGER);

               stmt.registerOutParameter(5, OracleTypes.INTEGER);

               stmt.registerOutParameter(6, OracleTypes.CURSOR);

     

               stmt.execute();

     

               int rowCount = stmt.getInt(4);

               int pageCount = stmt.getInt(5);

               ResultSet rs = (ResultSet) stmt.getObject(6);

     

               System.out.println("总记录数为:" + rowCount);

               System.out.println("总页数为:" + pageCount);

     

               while (rs.next())

               {

                  System.out.println("员工编号:" + rs.getInt(1) + " 姓名:"

                         + rs.getString(2) + " 工资:" + rs.getFloat(6));

               }

           }

           catch (Exception e)

           {

               throw new RuntimeException(e);

           }

           finally

           {

               try

               {

                  if (null != stmt)

                      stmt.close();

                  if (null != conn)

                      conn.close();

               }

               catch (Exception ex)

               {

                  throw new RuntimeException(ex);

               }

           }

        }

    }

    例外处理

    例外的分类

    Oracle将例外分为预定义例外,非预定义例外和自定义例外三种。

    预定义例外用于处理常见的oracle错误。

    非预定义例外用于处理预定义例外不能处理的例外。

    自定义例外用于处理与oracle错误无关的其它情况。

    例外处理

    例外传递

    如果不处理例外我们看看会出现什么情况:

    案例:编写一个过程,可接收雇员的编号,并显示该雇员的姓名。

    问题是,如果输入的雇员的编号不存在,怎样去处理呢?

    declare
    v_ename emp.ename%type;
    begin
      select ename into v_ename from emp where empno=&no;
      dbms_output.put_line('emp name : '|| v_ename);
      exception
      when no_data_found then
      dbms_output.put_line('empno not exist.');
    end;

    处理预定义例外

             预定义例外是由pl/sql所提供的系统例外。当pl/sql应用程序违反了oracle规定的限制时,则会隐含的触发一个内部例外。Pl/sql为开发人员提供了二十多个预定义例外。我们给大家介绍常用 的例外。

    Case_not_found

    在开发pl/sql块中编写case语句时,如果when子句中没有包含必须的条件分支,就会触发case_not_found的例外:

    create or replace procedure p_proc6(vno number)
    is
    v_sal emp.sal%type;
    begin
      select sal into v_sal
      from emp
      where empno= vno;
      case
        when v_sal < 1000 then
          update emp
          set sal = sal + 100
          where empno = vno;
        when v_sal < 2000 then
          update emp
          set sal = sal + 200
          where empno = vno;
        end case;
      exception
        when case_not_found then
        dbms_output.put_line('case statement no match condition for '||v_sal);
    end;

    Cursor_already_open

    当重新打开已经打开的游标时,会隐含的触发例外cursor_already_open

    DECLARE
      CURSOR emp_cursor IS
        SELECT ename,
               sal
        FROM   emp;
        emp_record emp_cursor%ROWTYPE;
    BEGIN
      OPEN emp_cursor;
      FOR emp_record IN emp_cursor
      LOOP dbms_output.put_line(emp_record.ename);
      END LOOP;
    EXCEPTION
      WHEN cursor_already_open THEN
      dbms_output.put_line('cursor already open');
    END;

    Dup_val_on_index

    在唯一索引所对应的列上插入重复的值时,会隐含的触发例外dup_val_on_index例外

    BEGIN
      INSERT info dept
      VALUES
        (10,
         'PublicRelations',
         'beijing');
    EXCEPTION
      WHEN dup_val_on_index THEN
        dbms_output.put_line('the column of deptno can not appear repeat value');
    END;

    Invalid_cursor

    当试图在不合法的游标上执行操作时,会触发该例外

    例如:试图从没有打开的游标提取数据,或是关闭没有打开的游标。则会触发访例外

    DECLARE
      CURSOR emp_cursor IS
        SELECT ename,
               sal
        FROM   emp;
      emp_record emp_cursor%ROWTYPE;
    BEGIN
      --OPEN emp_cursor;
      FETCH emp_cursor
        INTO emp_record;
      dbms_output.put_line(emp_record.ename);
      CLOSE emp_cursor;
    EXCEPTION
      WHEN invalid_cursor THEN
        dbms_output.put_line('please check cursor is already open?');
    END;

    Invalid_number

    当输入的数据有误时,会触发该例外

    比如:数字100写成了1oo就会触发该例外。

    BEGIN
      UPDATE emp SET sal = sal + '1oo';
    EXCEPTION
      WHEN invalid_number THEN
        dbms_output.put_line('input number is invalid.');
    END;

    No_data_found

    下面是一个pl/sql块,当执行select into没有返回行,就会触发该例外。

    DECLARE
      v_sal emp.sal%TYPE;
    BEGIN
      SELECT sal INTO v_sal FROM emp WHERE ename = '&name';
    EXCEPTION
      WHEN no_data_found THEN
        dbms_output.put_line('the emp no exist.');
    END;

    Too_many_rows

    当执行select into语句时,如果返回超过了一行,就会该例外。

    DECLARE
      v_ename emp.ename%TYPE;
    BEGIN
      SELECT ename INTO v_ename FROM emp;
    EXCEPTION
      WHEN too_many_rows THEN
        dbms_output.put_line('return too many rows.');
    END;

    Zero_divide

    当执行2/0语句时,则会触发该例外。

    DECLARE
      v_num NUMBER;
    BEGIN
      v_num := 2 / 0;
    EXCEPTION
      WHEN zero_divide THEN
        dbms_output.put_line('can not divide zero');
    END;

    Value_error

    当执行赋值操作时,如果变量的长度不足以容纳实际数据,则会触发该例外value_error,

    比如:

    DECLARE
      v_ename VARCHAR2(5);
    BEGIN
      SELECT ename INTO v_ename FROM emp WHERE empno = &no;
    EXCEPTION
      WHEN value_error THEN
        dbms_output.put_line('variable is not enough size to store value');
    END;

    其它预定义例外:

    1)login_denide

    当用户非法登陆时,会触发该例外。

    1)       Not_logged_on

    如果用户没有登陆就执行dml操作,就会触发该例外。

    2)       Storage_error

    如果超出了内存空间或是内存被损坏,就会触发该例外。

    3)       Timeout_on_resource

    如果oracle在等待资源时,出现了超时就会触发该例外。

    非预定义例外

    非预定义例外用于处理与预定义例外无关的oracle错误。使用预定义例外只能处理21个oracle错误,而当使用pl/sql开发应用程序时,可能会遇到其他的一些oracle错误。比如在pl/sql块中执行dml语句时,违反了约束规定等等,在这样的情况下,也可以处理oracle的各种例外,因为非预定义例外的不多,这里就不举例了。

    处理自定义例外

    预定义例外和自定义例外都是oracle错误相关的,并且出现的oracle错误会隐含的触发相应的例外;而自定义例外与oracle错误没有任何关联,它是由开发人员为特定情况所定义的例外。

    请编写一个pl/sql块,接收一个雇员的编号,并给雇员工资增加1000元,如果该雇员不存在,请提示。

    CREATE OR REPLACE PROCEDURE ex_test(vno NUMBER) IS
      --definition exception
      myex EXCEPTION;
    BEGIN
      UPDATE emp SET sal = sal + 100 WHERE empno = vno;
      --sql%notfound  -- denotation not update
      IF SQL%NOTFOUND
      THEN
        RAISE myex;
      END IF;
    EXCEPTION
      WHEN myex THEN
        dbms_output.put_line('no emp to update');
    END;

    Oracle视图

    视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。

    假设以下情境:

    1.如果要显示各个雇员的名字他所有部门的名称,必须用两张表。

    2.假设管理员创建了一个用户,xiaohong,现在就希望xiaohong只可以查询sal<1000的哪些雇员。

    create view myview1 as select * from emp where sal < 1000;

    这时我们就可以用视图来解决。

    视图与表的区别:

    1.表需要占用磁盘空间,视图不需要。

    2.视图不能添加索引。

    3.使用视图可以简化复杂查询。比如:学生选课系统。

    4.视图利于提高安全性。

    比如:不同用户查看不同视图。

    创建视图

    create view myview as
    select emp.ename,emp.job,dept.deptno from emp,dept
    where emp.deptno = dept.deptno
    with read only;

    创建或修改视图

    create or replace view myview as
    select emp.ename,emp.job,dept.deptno from emp,dept
    where emp.deptno = dept.deptno
    with read only;

    删除视图

    drop view myview;

  • 相关阅读:
    python返回列表最大值(java返回数组最大值)
    Mysql的5种索引添加类型
    阿里云中quick bi用地图分析数据时维度需转换为地理区域类型
    根据变量查找元素,并修改数值的实践
    Linux 通过命令设置网络
    mysql 实现 上一行减去下一行
    Spark 安装与启动
    Kafka 入门之集群部署遇到问题
    rmp使用方法
    Mysql 导入数据的一种方法
  • 原文地址:https://www.cnblogs.com/zfc2201/p/2392475.html
Copyright © 2020-2023  润新知