PL/SQL

1 PL/SQL入门
2 PL/SQL-常量和变量
3 PL/SQL-控制语句
4 PL/SQL-游标
5 PL/SQL-异常
6 PL/SQL-存储过程
7 PL/SQL-存储函数
8 PL/SQL-触发器

1 PL/SQL-入门

1.1 PL/SQL是什么

PL/SQL:(Procedure Language/SQL)Oracle数据库基于SQL语言的一门编程语言。

1.2 PL/SQL的作用

PL/SQL是一门编程语言,作用是在Oracle数据库中实现编程逻辑。

1.3 数据库编程的使用场景

使用场景:引用场景

Java使用JDBC操作数据库数据的缺点:

  1. SQL语句如果要经过网络传输到数据库,可能会被拦截(安全性不高)。
  2. SQL语句每次都要先解释再执行,效率相比在数据库中编程要低。
  3. 逻辑写在Java中,其他语言不能重用Java的代码。

数据库编程的缺点:

  1. 维护性差。
  2. 调试难。

1.4 PL/SQL的基础语法

[declare]
   --声明部分
   --注意:变量常量的声明必须要放在声明部分
begin
   --逻辑处理部分
end;

1.5 示例代码

-- 需求:编写一段PL/SQL,打印HelloWorld
-- 注意:Oracle数据库默认是没有打开dbms_output输出的,需要使用set打开
set serveroutput on;
begin
   -- 打印结果到控制台
   dbms_output.put_line('HelloWorld');
end;

2 PL/SQL-变量和常量

2.1 常量的声明

-- 需求:编写一段PL/SQL,先声明一个常量存储HelloWorld,再打印HelloWorld
declare
   -- 声明常量
   -- 声明的语法:   
   -- <常量名> constant <数据类型>[(大小)]:=初始值;
   -- 常量的注意事项:常量是必须要有初始值的。
   -- := 是PL/SQL的赋值符号
   v_say constant varchar2(10):='HelloWorld';
begin
   -- 打印结果到控制台
   dbms_output.put_line(v_say);
end;

2.2 变量的声明

-- 需求:编写一个PL/SQL,输入一个员工的编号,打印该员工的名字。
declare
  -- 声明一个员工的编号,先定义为常量,写死
  -- v_empno constant number(4):=7788;
  -- 声明员工编号为变量,输入值。&后面是随便写的。注意:&在注释中也是起作用的,所有不要在注释中出现&
  v_empno number(4):=&empno;
  -- 声明一个变量
  -- <变量名> <数据类型>[(大小)][:=初始值];
  v_ename varchar2(10);
begin
  -- 通过一个员工的编号查询员工的名字
  -- 注意:查询语句的返回字段,只在当前SQL有效
  -- 如果其他的语句需要使用SQL的返回字段,必须通过INTO赋予给一个变量
  select ename into v_ename from emp where emp.empno = v_empno;
  -- 输出
  dbms_output.put_line(v_ename);
end;
-- 变量和常量以v开头仅仅是为了和数据库的字段名区分

2.3 %type

-- 需求:编写一个PL/SQL,输入一个员工的编号,打印该员工的名字。
-- 想法:既然v_empno和v_ename的变量的数据类型是和表的字段是一样的。那么能直接引用表字段的声明吗?
-- 答:可以的。
-- 引用表字段定义的数据类型的格式:
-- 表名.字段名%type
declare
  v_empno emp.empno%type := &empno;
  v_ename emp.ename%type;
begin
  select ename into v_ename  from emp where emp.empno = v_empno;
  -- 输出
  dbms_output.put_line(v_ename);
end;

2.4 %rowtype

表的行类型(%rowtype)作用是:使用一个表的结构变量来接收表的所有字段。

-- 需求:编写一个PL/SQL,输入一个员工的编号,打印该员工的名字、工资。
declare
  v_empno emp.empno%type := &empno;
  v_ename emp.ename%type;
  v_sal emp.sal%type;
begin
  select ename, sal into v_ename, v_sal from emp where emp.empno = v_empno;
  dbms_output.put_line('员工姓名:'||v_ename||',员工工资:'||v_sal);
end;

-- 以上代码中一个字段需要一个变量,那么100字段就需要100变量。
-- 想法:可不可以声明一个变量接收所有的字段。类似JavaBean。
-- 答:可以通过表的行类型(%rowtype)实现。
-- %rowtype的声明的格式: 表名%rowtype

declare
  v_empno emp.empno%type := &empno;
  v_emp emp%rowtype;
begin
  select ename, sal into v_emp.ename, v_emp.sal from emp where emp.empno = v_empno;
  dbms_output.put_line('员工姓名:'||v_emp.ename||',员工工资:'||v_emp.sal);
end; 

-- 行类型接收表的所有字段(*)
declare
  v_empno emp.empno%type := &empno;
  v_emp emp%rowtype;
begin
  -- 通过一个员工的编号查询员工的名字
  -- 行类型,如果不指定字段名,可以接收一个*的字段。(所有的字段)
  select * into v_emp from emp where emp.empno = v_empno;
  -- 输出
  dbms_output.put_line('员工姓名:'||v_emp.ename||',员工工资:'||v_emp.sal);
end;

3 PL/SQL-控制语句

3.1 判断

-- 需求:指定员工的编号,判断员工的工资,如果大于等于5000:A级,如果大于等于4000:B级,如果大于等于3000:C级,否则D级。

declare
  --声明一个常量
  v_empno emp.empno%type := 7788;
  --声明一个变量接收工资
  v_sal emp.sal%type;
begin
  --查询工资
  select sal into v_sal from emp where empno = v_empno;
  --输出工资
  dbms_output.put_line('工资为:'||v_sal);
  --判断输出
  if v_sal>=5000 then
     dbms_output.put_line('A级');     -- >=5000
  elsif v_sal>=4000 then
     dbms_output.put_line('B级');     -- >=4000 and <=4999
  elsif v_sal>=3000 then
     dbms_output.put_line('C级');     -- >=3000 and <=3999
  else
     dbms_output.put_line('D级');     -- <3000
  end if;
end;

3.2 循环

3.2.1 loop循环

--控制语句-循环
/*
   -- java do-while循环
   do{
     //循环体
   }while(循环条件); -- 如果为true,表示继续循环   

   -- pl/sql loop循环
   loop
      //循环体
      -- 判断退出循环的条件
      exit when <退出循环的条件>  -- 如果为true,表示退出循环
   end loop;

   pl/sql的loop循环和do-while的异同
   相同之处:
     1.它们都可以实现先循环,后判断
   不同之处:
     1.loop的条件是退出循环的条件,为true退出循环,而do-while循环是循环条件,为true继续循环。
     2.loop循环的判断条件可以在循环体的任何位置!
*/

-- 需求:计算输出1-10累加的值。
declare
   -- 声明一个变量接收总值
   -- 非字符串的类型可以不指定大小
   v_sum number := 0;
   -- 声明一个变量接收计数
   v_num number := 1;
begin
   -- 循环累加
   loop
      -- 累加
      v_sum := v_sum + v_num;
      -- 计数每次循环+1
      v_num := v_num + 1;
      -- 判断退出循环的条件
      exit when v_num=11;
   end loop;
   -- 输出
   dbms_output.put_line(v_sum); --55
end;
-- 注意,退出循环的条件可以在loop循环体内的任何位置

3.2.2 while循环

-- while循环
/*
  -- java while循环
  while(<循环条件>){  
      -- 循环体
  }  

  -- pl/sql while循环
  -- 功能等同Java的while循环
  while <循环条件>
    loop    
       -- 循环体
    end loop;
*/

-- 需求:计算输出1-10的值。
declare
   v_sum number := 0;
   v_num number := 1;
begin
   -- 循环累加
   -- 注意,while的条件是循环条件,为true,继续循环
   while v_num <= 10
      loop
         -- 累加
         v_sum := v_sum + v_num;
         -- 计数+1
         v_num := v_num + 1;
      end loop;
   dbms_output.put_line(v_sum);
end;

3.2.3 for循环

-- for循环
/*
   -- java for-each循环
   for (<变量> : <数组|集合>){
   }

   -- pl/sql
   for (<变量> in [reverse] <范围|游标>){
      loop

      end loop;
   }
*/

-- 需求:输出1-10的值
begin
   -- 注意:for循环的变量可以不声明直接使用,当然也可以声明后使用
   -- '..'表示一个范围,包括上下限,步长为1
   for i in 1..10
     loop
       dbms_output.put_line(i);
     end loop;
end;

-- 需求:输出10-1的值
begin
   -- 注意:范围必须是从小到大的.如果需要从大到小,使用reverse
   for i in reverse 1..10
     loop
       dbms_output.put_line(i);
     end loop;
end;

4 PL/SQL-游标

4.1 游标是什么

游标是查询数据时的一个指向数据的指针。作用是用于读取表的数据。

数据库是没有Java里面的集合的概念。如果需要返回多条数据,必须通过游标去表里面直接读取。

Oracle中,如果需要读取多条数据时,必须使用游标。

4.2 游标的语法

-- 创建一个游标
cursor <游标名> is select语句 

-- 打开游标
open <游标名> 

-- 读取数据
fetch <游标名> into <变量列表>.. 

-- 关闭游标
close <游标名> 

-- 游标变量
%notfound   -- 如果找不到数据,返回true
%found      -- 如果读到数据了,返回true
%rowcount   -- 返回当前读取的记录数
%isopen     -- 判断游标是否是打开状态 

-- 使用格式
游标名%notfound

4.3 示例代码

-- 需求:输出emp表的所有的员工信息
-- 分析:查询出来的结果是一组数据。
declare
  -- 创建一个游标
  cursor v_cur_emp is select * from emp;
  -- 创建一个行类型接收
  v_emp emp%rowtype;
begin
  -- 打开游标
  open v_cur_emp;
  -- 循环
  loop
     -- 读取
     fetch v_cur_emp into v_emp;
     -- 结束条件
     exit when v_cur_emp%notfound;
     -- 输出
     dbms_output.put_line('当前记录数:'||v_cur_emp%rowcount||',员工编号:'||v_emp.empno||',姓名:'||v_emp.ename||',工资:'||v_emp.sal);
  end loop;
  -- 关闭
  close v_cur_emp;
end;

4.4 游标引用(重点)

/*
Java里面
String s; -- s是引用
String s = new String("aaa"); -- s是对象。
引用就是没有对象的变量。

所谓的游标引用就是声明时没有select语句的游标。 

游标引用的作用:将在声明部分的SQL语句移到逻辑处理部分。这样做可以实现SQL复杂的处理功能。

-- 游标引用的声明
 <游标名> sys_refcursor; 

 -- 打开游标引用
 open <游标名> for select 语句

 -- 读取
 fetch <游标名> into <变量>

 close 游标名>
*/

-- 需求:输入一个部门,输出emp表该部门的员工信息。通过游标引用来实现
declare
  -- 声明一个游标引用
  v_cur_emp sys_refcursor;
  -- 声明一个部门编号
  v_deptno emp.deptno%type := &deptno;
  -- 声明一个行类型
  v_emp emp%rowtype;
begin
  -- 打开游标引用
  open v_cur_emp for select empno,ename,sal,deptno from emp where deptno=v_deptno;
  -- 循环
  loop
     -- 读取
     -- 注意:读取游标返回的变量和查询语句的返回字段要一一对应
     fetch v_cur_emp into v_emp.empno,v_emp.ename,v_emp.sal,v_emp.deptno;
     exit when v_cur_emp%notfound;
     dbms_output.put_line('员工编号:'||v_emp.empno||',姓名:'||v_emp.ename||',工资:'||v_emp.sal||',部门编号:'||v_emp.deptno);
  end loop;
  -- 关闭
  close v_cur_emp;
end;

5 PL/SQL-异常(了解)

5.1 异常是什么

异常是已经预定义的错误。

5.2 常见的预定义异常

预定义异常说明
CURSOR_ALREADY_OPEN试图”OPEN”一个已经打开的游标
DUP_VAL_ON_INDEX试图向有”UNIQUE”中插入重复的值
INVALID_CURSOR试图对以关闭的游标进行操作
INVALID_NUMBER在SQL语句中将字符转换成数字失败
LOGIN_DENIED使用无效用户登陆
NO_DATA_FOUND没有找到数据时
NOT_LOGIN_ON没有登陆Oracle就发出命令时
PROGRAM_ERROR PL/SQL存在诸如某个函数没有”RETURN”语句等内部问题
STORAGE_ERROR PL/SQL耗尽内存或内存严重不足
TIMEOUT_ON_RESOURCE等待资源期间发生超时
TOO_MANY_ROWS“SELECT INTO”返回多行时
VALUE_ERROR当出现赋值错误
ZERO_DIVIDE除数为零
others捕捉所有的异常

5.3 异常的语法

-- 声明语法

  [declare]
    -- 声明部分
  begin
    -- 逻辑处理部分
    -- 必须在逻辑处理部分的最后
    [exception
        when <异常名> then
           -- 处理
        when others then  --others表示捕捉所有的异常
           -- 处理
    ]
  end;  

-- 异常有两个异常变量(只能用于异常处理部分)
1.sqlcode : 错误码
2.sqlerrm : 错误信息

5.4 示例代码

-- 除数不能为零
declare
  v_num number := 0;
begin
  -- 除数为,0报错
  v_num := 10/0;
  -- 异常处理
  exception
     -- when ZERO_DIVIDE then
     when others then
     -- dbms_output.put_line('除数不可以为0,错误码:'||sqlcode ||',错误信息:' ||sqlerrm);
     -- 异常可以自定义信息抛出。
     raise_application_error(-20004, '除数为0了!不可以哟');
end;

6 PL/SQL-存储过程

6.1 存储过程是什么

存储过程是一个命名的PL/SQL语句块。它使用一个数据库对象封装了PL/SQL语句,实现功能的重用。类似Java里面的方法。

6.2 存储过程的作用

实现PL/SQL语句的重用。

6.3 存储过程的语法

-- 创建的语法
create [or replace] procedure <存储过程名>[(参数列表)]
as
   -- 声明部分
begin
   -- 逻辑处理部分
   [exception]
   -- 异常处理部分
end;

-- 参数列表
输入参数 : <参数名> [in] <数据类型>
输出参数 : <参数名> out <数据类型>

6.4 存储过程入门-无参数

-- 编写一个存储过程,输出HelloWorld
create or replace procedure pro_say_hello
as
  -- 声明一个常量
  v_hello constant varchar2(10) := 'HelloWorld';
begin
   -- 输出
   dbms_output.put_line(v_hello);
end; 

-- 查看
select * from SYS.user_procedures;
-- 调用
-- 方式1:使用命令调用
exec pro_say_hello;
-- 方式2;使用pl/sql调用
begin
   pro_say_hello;
end; 

-- 移除存储过程
drop procedure pro_say_hello;

6.5 存储过程-有参数

-- 需求:传入一个员工的编号,输出该员工的姓名
create or replace procedure pro_find_by_empno(p_empno in emp.empno%type,p_ename out emp.ename%type)
as
begin
   -- 查询
   select ename into p_ename from emp where emp.empno=p_empno;
end;

-- pl/sql调用
declare
   -- 声明一个变量
   v_ename emp.ename%type;
begin
   -- 注意:输出参数必须需要一个变量接收输出的值
   pro_find_by_empno(7788,v_ename);
   -- 输出变量
   dbms_output.put_line(v_ename);
end;

6.6 存储过程-有游标

-- 需求:传入一个员工的部门编号,输出该员工的信息
-- 输出一组数据,必须使用游标
create or replace procedure pro_find_by_deptno(p_dpetno in emp.deptno%type,p_cur_emp out sys_refcursor)
as
begin
   -- 将查询结果返回给游标
   open p_cur_emp for select * from emp where deptno=p_dpetno;
end;

-- 使用pl/sql调用
declare
   -- 声明一个游标引用接收输出的值
   v_cur_emp sys_refcursor;
   -- 使用一个行类型接收
   v_emp emp%rowtype;
begin
   -- 执行存储过程
   pro_find_by_deptno(10,v_cur_emp);
   -- 循环
   loop
      fetch v_cur_emp into v_emp;
      -- 读不到数据退出
      exit when v_cur_emp%notfound;
      -- 打印结果
      dbms_output.put_line('员工名:'||v_emp.ename||',部门编号:'||v_emp.deptno);
  end loop;
  -- 关闭
  close v_cur_emp;
end;

6.7 使用Java调用存储过程

/**
 * 获得连接对象的工具类
 */
public class DbUtils {  
    public static Connection getConnection() {
        // 四要素
        // 1.驱动
        String driver = "oracle.jdbc.OracleDriver";
        // 2.连接字符串
        String url = "jdbc:oracle:thin:@localhost:1521:orcl";
        // 3.用户名
        String user = "xust";
        // 4.密码
        String password = "xust";       
        try {
            // 加载驱动
            Class.forName(driver);
            // 获得连接对象
            Connection conn = DriverManager.getConnection(url, user, password);
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
        }       
        return null;
    }
    /**
     * 调用Oracle数据库中的存储过程pro_find_by_deptno
     */
    @Test
    public void findByDeptno() throws SQLException {
        // 存储过程的操作命令,格式为{call <存储过程名> (?,?)}
        String sql = "{call pro_find_by_deptno(?,?)}";
        // 1.获得连接
        Connection conn = DbUtils.getConnection();

        // 2.获得操作对象
        CallableStatement call = conn.prepareCall(sql);
        // (1)设置参数1,部门编号为10,参数的下标是从1开始
        call.setInt(1, 10);
        // (2)设置参数2,参数2是一个输出的参数,需要指定输出参数的类型
        call.registerOutParameter(2, OracleTypes.CURSOR);       

        // 3.操作
        call.execute();
        // 获得存储过程返回的数据
        // 游标对应的Java类为ResultSet
        ResultSet resultSet = (ResultSet) call.getObject(2);
        while(resultSet.next()) {
            System.out.println("员工编号:" + resultSet.getInt("empno") + "部门编号:" + resultSet.getInt("deptno") + "员工姓名:" + resultSet.getString("ename"));
        }

        // 4.关闭
        resultSet.close();
        call.close();
        conn.close();
    }

7 PL/SQL-存储函数(了解)

存储函数也是一个命名的PL/SQL语句块。它也是使用一个数据库对象封装了PL/SQL的语句,实现功能的重用。存储函数就是一个自定义函数,类似Java里面的方法。


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

  1. 存储函数是可以被SQL语句调用(DML/DQL),而存储过程是不可以。
  2. 存储函数是必须有返回值,而存储过程是没有返回值的。

由于存储函数可以被SQL语句调用,而存储过程不可以。通常习惯性地,存储函数用于处理复杂的SQL语句,而存储过程用于被第三方程序调用

7.1 存储函数的语法

-- 创建语法
create [or repalce] function <函数名>(参数名 [in]/out 类型) return <数据类型>
as
begin
   return <返回值>;
end;

7.2 示例代码

-- 需求:传入一个员工的编号,返回该员工的工资
create or replace function fn_find_sal_by_empno(p_empno in emp.empno%type) return emp.sal%type
as
   -- 声明一个变量接收工资
   v_sal emp.sal%type;
begin
   select sal into v_sal from emp where empno=p_empno;
   -- 返回工资
   return v_sal;
end;

-- 查看
-- 查看所有用户级别的数据库对象user_objects
select * from user_objects where object_type='FUNCTION';

-- 调用
-- 方式1:SQL调用
select fn_find_sal_by_empno(7788) from dual;

-- 方式2:PL/SQL调用
declare
   v_sal emp.sal%type;
begin
   v_sal := fn_find_sal_by_empno(7788);
   dbms_output.put_line(v_sal);
end;

7.3 使用Java调用存储函数

    /**
     * 调用Oracle数据库中的存储函数fn_find_sal_by_empno
     */
    @Test
    public void fn_find_sal_by_empno() throws SQLException {
        // 调用fn_find_sal_by_empno
        // 命令格式:{? = call <存储函数名> {?,?}}
        String sql = "{? = call fn_find_sal_by_empno(?)}";
        // 1.获得连接对象
        Connection conn = DbUtils.getConnection();

        // 2.获得操作对象
        CallableStatement call = conn.prepareCall(sql);
        // 参数一,返回输出。需要指定输出类型返回工资
        call.registerOutParameter(1, OracleTypes.DOUBLE);
        // 参数二,输入的值。员工编号
        call.setInt(2, 7788);

        // 3.操作
        call.execute();
        // 获得返回值
        Double sal = call.getDouble(1);
        System.out.println(sal);

        // 4.关闭
        call.close();
        conn.close();
    }

8 PL/SQL-触发器

8.1 触发器是什么

触发器是在操作数据表时(增删改),会自动触发一段PL/SQL 的数据库对象。

8.2 触发器的作用

理论上,它可以实现在操作数据时,执行任何的PL/SQL。但是实际上,我们一般都是用于一些不操作其他数据的功能。如:

  1. 数据备份
  2. 隐藏ID自增长列
  3. 数据统计

8.3 触发器的语法

-- 创建触发器
create [or replace] trigger <触发器名>
   before|alter            -- 在操作之前还是之后触发,二选一
   insert|delete|update    -- 设置触发器的操作类型,三选一
   [of <字段名>]            -- 如果指定的字段发生了修改,就触发。用于update触发器
   on <表名>                --指定是哪个表的触发器
   [for each row]          --行级触发器还是表级触发器
   ------------------------pl/sql-----------------------------
   [declare]
      --声明部分
   begin
      --逻辑处理部分
      [exception]
   end;


-- 有 [for each row]:行级触发器,每影响一行记录,就触发一次。
-- 没有[for each row]:表级触发器,每次操作,触发一次。

-- 触发器变量
  :new.新记录,insertupdate操作才有新的数据,所以:new只能用户更新和插入触发器
  :old.旧记录,旧记录表示表里面已经有的数据。updatedelete操作时有旧记录,而插入时是没有旧记录的。所以:old只能用于更新和删除

8.4 示例代码

-- 需求:每次插入emp表记录之前,就触发打印HelloWorld
create or replace trigger tri_emp_insert_sayhello
    before  -- 操作之前触发
    insert  -- 触发器器类型是插入
    on emp  -- 指定表名
    for each row  -- 每插入一条就打印一个HelloWorld,使用行级触发器
    declare
       -- 声明一个常量
       v_hello constant varchar(10) := 'HelloWorld';
       begin
       -- 输出
       dbms_output.put_line(v_hello);
    end;


-- 查看
select * from user_triggers;

-- 测试
insert into emp(empno,ename) values(4001,'tri1');

8.5 触发器-隐藏ID自增长列

隐藏ID自增列:MySQL中,ID主键实现auto-increment后,插入数据可以不写该主键,MySQL会自动补全。但在Oracle中没有auto-increment,要想隐藏ID自增长列,就要应用序列+触发器。

-- 前提:必须创建一个序列。
-- 通过触发器实现将序列值,自动填入ID列。
create or replace trigger tri_emp_insert_autoincrement
   before   -- 由于主键字段是不可以为null,所以必须要在插入之前填入值
   insert
   on emp
   for each row
   begin
       -- 如何获得当前插入的记录填入值
       -- 通过触发器变量(:new)获得正在插入的新记录
       :new.empno := seq_emp.nextval;
   end;

--测试
insert into emp(ename) values('tri11');
select seq_emp.currval from dual;

8.6 触发器-数据备份

-- 创建一个触发器实现备份
create or replace trigger tri_emp_delete_backup
  before --备份数据一定在删除之前
  delete 
  on emp
  for each row
  begin
    -- 每删除一条数据就备份该数据到emp_bak
    insert into 
       emp_bak(empno,ename,job,mgr,hiredate,sal,comm,deptno)                                          values(:old.empno,:old.ename,:old.job,:old.mgr,                                                        :old.hiredate,:old.sal,:old.comm,:old.deptno);
  end;


  -- 删除一条数据
  delete from emp where empno = 1;
  -- 删除所有emp表的数据
  delete from emp;

  -- 删除触发器
  drop trigger tri_emp_insert_sayhello;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值