Oracle Database-PL/SQL

Posted zella1996 (部分文章代码格式错误见谅)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle Database-PL/SQL相关的知识,希望对你有一定的参考价值。

Oracle Database-PL/SQL

 

“Oracle Database logo”的图片搜索结果

PL/SQL基础

PL/SQL(Procedure Language/SQL)
    PL/SQL是Oracle对SQL语言的过程化扩展,指在SQL命令语言中增加了过程处理语(如分支、循环等),使SQL语言具有过程处理能力。把SQL语言的数据操纵能力与过程语言的数据处理能力结合起来,使得 PLSQL面向过程但比过程语言简单、高效、灵活和实用。
因为是过程化扩展,所以
  • PL/SQL程序内支持编写SQL语言
    • SQL语言的数据操纵能力与过程语言的数据处理能力结合
  • PL/SQL是一门面向过程的语言

PL/SQL的官方文档

如下,可以查到DBMS_OUTPUT程序包的内容
也可以在命令行中使用desc关键字直接查看程序包的结构
如下,这种方法相当于简化的文档

PL/SQL的语法

*Java的语法

PL/SQL可以完成以下Java代码(伪)完成的功能
下面是PL/SQL实现这个功能
而PL/SQL相对JDBC实现这个更能更有效率
开发模式:SpringMVC+存储过程(PL/SQL)
Hello World
这个dbms_output是一个程序包(类似API)

declare-变量和常量的说明

注意:若没有需要说明的变量或常量,可以没有declare关键字
说明基本数据类型变量时要说明三个部分
  • 变量名
  • 数据类型
  • 数据大小
 
基本类型变量
与表的列的数据类型对应(意味着我们可以把表中的数据存到这些类型的变量中)
  • char
  • varchar2
  • date
  • number
  • boolean
  • long
*PL/SQL中的":="相当于Java中的"=","="相当于Java中的"=="
引用型变量
引用表中列的数据类型作为该变量的数据类型
示例:
赋值有两种方法,":="和"into"关键字
记录型变量
记录型变量引用表中的一行作为变量(s)的数据类型,换句话说,记录型变量可以存放表中的一行数据,可以理解成一个数组(或者集合),数组中的每一个元素代表这一行的记录每一列
示例:

IF语句

语法
注意elsif这个写法与Java else if不一样
示例:
-- 判断用户从键盘输入的数字并打印相应语句
-- 接收键盘输入
-- accept addr prompt \'xxx\'
-- addr 是一个地址值,在该地址上保持了输入的值
accept num prompt \'请输入一个数字\';
declare 
  pnum number := #
  -- 注意:取出存在在这个地址值的值要使用\'&\'符号相当于c的指针
begin
  if pnum = 0 then dbms_output.put_line(\'you input 0\');
     else if pnum = 1 then dbms_output.put_line(\'you input 1\');
     else then dbms_output.put_line(\'you input other number\');
     end if;
end;
注意,这段正确的代码无法在PL/SQL Developer和命令行中运行,但可以在Oracle SQL Developer(自行下载)中运行
 

循环语句

语法
示例:输入数字1到10

Cursor-光标(游标)

在Java语言中有集合的概念,在PL/SQL语言中也会用到多条记录作为整体,此时我们需要用到游标,游标可以存储查询返回的多条数据
简言之,光标/游标类似Java中的ResultSet

基本语法

说明光标
CURSOR 光标名 [(参数名数据类型[,参数名数据类型]...)] IS SELECT 语句;
如:
使用步骤
fetch关键字的作用是将光标的一个值赋到变量中并将指针往后移动一位(游标的指针默认在第一个值上)
示例:

光标的属性

  • %isopen:光标是否已打开
  • %rowcount:影响的行数(即已取出的行数)
  • %found:当前指针的位置是否仍然有记录
  • %notfound:当前指针位置是否没有记录

带参数的光标

语法
示例:

例外

例外例外是程序设计语言提供的一种功能,用来增强程序的健壮性和容错性
相当于Java中的异常

例外的分类

  • 系统定义的例外
    • No_data_found(没有找到数据)
    • Too_many_rows(select …into语句匹配多个行)
    • Zero_Divide(被零除)
    • Value_error(算术或转换错误)
    • Timeout_on_resource(在等待资源时发生超时)(分布式数据库相关)
  • 用户定义的例外

定义及处理例外

  • 在declare节中定义例外
    • out_of exception;
  • 在可行语句中引起例外
    • raise out_of;(类似Java中的throw)
  • 在exception节处理例外
    • when out_of then …;(类似Java中的try-catch)
示例:

*PL/SQL的应用

实例

declare
  --部门
  cursor cdept is select deptno from dept;
  pdeptno dept.deptno%type;
  
  --部门中员工的薪水
  cursor cemp(dno number) is select sal from emp where deptno=dno;
  psal emp.sal%type;
  
  --每个段的人数
  count1 number; count2 number; count3 number;
  --部门的工资总额
  salTotal number := 0;
begin
  --部门
  open cdept;
  loop
       --取一个部门
       fetch cdept into pdeptno;
       exit when cdept%notfound;
       
       --初始化
       count1:=0; count2:=0; count3:=0;
       --得到部门的工资总额
       select sum(sal) into salTotal  from emp where deptno=pdeptno;
       
       --取部门的中员工薪水
       open cemp(pdeptno);
       loop
            --取一个员工的薪水
            fetch cemp into psal;
            exit when cemp%notfound;
            
            --判断
            if psal < 3000 then count1:=count1+1;
               elsif psal >=3000 and psal<6000 then count2:=count2+1;
               else count3:=count3+1;
            end if;
       end loop;
       close cemp;

       --保存结果
       insert into msg values(pdeptno,count1,count2,count3,nvl(saltotal,0));

  end loop;
  close cdept;
  
  commit;
  dbms_output.put_line(\'完成\');
  
end;

 

编写PL/SQL程序的一般步骤
  • 需求分析
    • SQL语句:即涉及操作数据库的逻辑
      • 获取数据库中的数据
        • 使用什么类型的变量来存放获取到的数据
        • 使用什么方式来从变量中提取数据(如循环,判断等)
      • 修改数据库中的数据
    • 变量
      • 变量的初始值
      • 变量的获取方式
        • 满足什么条件就改变变量的值

PL/SQL进阶内容

存储过程和存储函数

存储过程

    存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能SQL语句集编译后存储在数据库中,用户通过指定存储过程的名字给出参数(如果该存储过程带有参数)来执行它。
    存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应用程序都应该用到存储过程。

存储函数

    函数(Function)为一命名的存储程序,可带参数,并返回一计算值。函数和过程的结构类似,但必须有一个RETURN子句,用于返回函数值。函数说明要指定函数名、结果值的类型,以及参数类型等。

*

    简言之,存储在数据库中供所有用户程序调用的由PL/SQL编写的子程序被称为存储过程或存储函数。
    在Java程序中,不能直接调用PL/SQL编写好的程序,但是可以调用存储过程或存储函数。
    当然,在存储过程或存储函数中也可以调用存储过程或存储函数。

存储过程和存储函数的区别

    一般来讲,过程和函数的区别在于函数可以有一个返回值:而过程没有返回值。但过程和函数都可以通过out指定一个或多个输出参数。我们可以利用out参数,在过程和函数中实现返回多个值。
    通常除去return(返回)值的不同,可以认为两者相同。

使用存储过程的语法

创建存储过程
调用存储过程
示例:
 
示例:

使用存储函数的语法

创建存储函数
调用存储函数
示例

存储过程和存储函数中的in(输入参数)out(输出参数)

    一般来讲,过程和函数的区别在于函数可以有一个返回值,而过程没有返回值。但过程和函数都可以通过out指定一个或多个输出参数,我们可以利用out参数,在过程和函数中实现返回多个值。
使用out参数来返回值
在out参数中返回游标

使用存储过程/函数的注意事项

如果只有一个返回值,用存储函数,否则,就用存储过程
尽量不在存储过程或存储函数中操作事务,因为子程序是交由其他人调用的,不应该操作事务

*Java调用存储过程/存储函数

创建测试项目,导包
包的位置位于安装Oracle的机器下的这个目录
导入项目如下
编写工具类
package demo;

import java.sql.*;

public class JDBCUtils {
    private static String driver = "oracle.jdbc.OracleDriver";
    private static String url = "jdbc:oracle:thin:@169.254.35.157:1521/orcl";
    private static String user = "scott";
    private static String password = "tiger";

    static{
        // 注册驱动
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public static Connection getConnection() {
        try {
            return DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void release(Connection conn, Statement st, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            finally{
                rs = null;
                // 置空作为垃圾回收
            }
        }
        if (st != null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            finally{
                st = null;
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            finally{
                conn = null;
            }
        }
    }
}
Statement接口的子接口CallableStatement就是专门用于执行SQL存储过程的接口
其中红框内是调用存储过程的标准方式
编写测试类分别测试存储过程和存储函数

存储过程

存储过程如下
测试代码如下
 /*
    procedure queryEmpInformation(eno in number,
                                  pename out varchar2,
                                  psal   out number,
                                  pjob   out varchar2)
                                  */
    @Test
    public void testProcedure() {
        String sql = "{call queryEmpInformation(?,?,?,?)}";
        Connection conn = null;
        CallableStatement call = null;
        try {
            conn = JDBCUtils.getConnection();
            call = conn.prepareCall(sql);

            // 对于in参数,需要对其赋值
            call.setInt(1, 7839);

            // 对于out参数,需要对其声明
            call.registerOutParameter(2, OracleTypes.VARCHAR);
            call.registerOutParameter(3, OracleTypes.NUMBER);
            call.registerOutParameter(4, OracleTypes.VARCHAR);

            // 执行调用存储过程
            call.execute();

            // 从CallableStatement对象中取出输出out参数
            String name = call.getString(2);
            Double sal = call.getDouble(3);
            String job = call.getString(4);

            System.out.println("name:" + name + " sal:" + sal + " job:" + job);

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.release(conn, call, null);
        }

    }
输出如下

存储函数

存储过程如下
测试代码如下
    /*
    function queryEmpIncome(eno in number)
    return number
    */
    @Test
    public void testFunction() {
        String sql = "{?= call queryEmpIncome(?)}";
        Connection conn = null;
        CallableStatement call = null;
        try {
            conn = JDBCUtils.getConnection();
            call = conn.prepareCall(sql);

            // 存储函数的输出参数即返回值,是第一个问号,对其注册
            call.registerOutParameter(1, OracleTypes.NUMBER);

            // 赋值输入参数
            call.setInt(2, 7839);

            call.execute();

            double annIncome = call.getDouble(1);

            System.out.println("annual income:" + annIncome);

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.release(conn, call, null);
        }
    }

输出如下

调用使用光标作为out参数的存储过程

问题:查询某个部门所有员工的所有信息
在编写PL/SQL时申明包结构
创建包
在包中可以自定义一个数据类型(下面的empcursor),引用原有的数据类型(下面引用了cursor光标类型)作为这个类型
创建包体
在包体中需要实现包头中定义的所有存储过程和存储函数
在Java中访问
使用ResultSet来接受光标参数
具体测试代码如下
    @Test
    public void testCursor() {
        String sql = "{call mypackage.queryemplist(?,?)}";
        Connection conn = null;
        CallableStatement call = null;
        try {
            conn = JDBCUtils.getConnection();
            call = conn.prepareCall(sql);

            call.setInt(1, 10);

            call.registerOutParameter(2, OracleTypes.CURSOR);

            call.execute();

            // CallableStatement是一个通用的接口,获取时获得Oracle适用的实现类对象,可以将其强制转换成Oracle适用的接口
            ResultSet cursor = ((OracleCallableStatement) call).getCursor(2);

            while (cursor.next()) {
                System.out.println("name:"+cursor.getString("ename"));
                System.out.println("hiredate:"+cursor.getDate("hiredate"));
            }


        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.release(conn, call, cursor);
            // 此处关闭ResultSet时同时关闭了这个光标
        }
    }

触发器

    数据库触发器是一个与表相关联的存储的PL/SQL程序。每当一个特定的数据操作语句(Insert,update和delete不包括select)在指定的表上发出时,Oracle自动地执行触发器中定义的语句序列。

触发器的作用

  • 数据确认
    • 如:员工涨工资后的工资不能少于涨工资后的工资
  • 实施复杂的安全性检查
    • 如:禁止在非工作时间插入新员工
  • 审计,跟踪表上的数据操作(日志)
    • *Oracle中的审计有五种
      • 强制审计
      • 标准审计(配置)
      • 基于值的审计(触发器审计属于这一类
      • 细粒度审计
      • 管理员审计
  • 数据的备份和同步

触发器的类型

  • 语句级触发器
    • 指定的操作语句操作之前或之后执行一次,不管这条语句影响了多少行
      • 如:一次插入多条数据,触发器只执行一次
    • 针对的是表
  • 行级触发器
    • 触发语句作用的每一条记录都被触发
    • 在行级触发器中使用:old:new伪记录变量,识别值的状态
    • 针对的是行

创建触发器

语法
示例:
限制非工作时间向数据库插入数据
当非工作时间插入员工执行时会报错
示例2:
确认数据(检查emp表中sal 的修改值不低于原值)
运行效果
查询触发器、过程及函数
  • select * from user_triggers;
  • select * from user_source;

行级触发器中触发语句与伪记录变量代表的值

触发器的实际应用

S

*使用PL/SQL Developer编写PL/SQL程序

Hello World

以上是关于Oracle Database-PL/SQL的主要内容,如果未能解决你的问题,请参考以下文章

ORA-00600: internal error code, arguments: [25027], [1], [0], [], [], [], [], [], [], [], [], [](代码片

ORA-00600: internal error code, arguments: [25027], [1], [0], [], [], [], [], [], [], [], [], [](代码片

Android Adapter代码片

代码片|水波纹

代码片--练习匿名内部类

oracle 恢复数据