zoukankan      html  css  js  c++  java
  • PLSQL

    plsql : procedure language sql 是Oracle特有的数据库编程语言,普通的sql可以实现对数据的增删改查,一条sql很难做到 即查询,又根据不同的条件筛选数据,修改数据,而PLSQL可以帮我们实现

    基本语法

    分成三部分:

    1. 声明部分 (声明一些临时变量,后续使用)
    2. 可执行部分 (我们的业务逻辑)
    3. 异常处理部分(可执行部分出现的异常信息,并处理)
    declare
        -- 声明部分
        自定义的变量名 变量类型; 
        -- 变量可以边声明变初始化
        v_name varchar(2) :='zhangsan';
        
    begin
        -- 执行业务逻辑
    exception
        -- 异常处理部分
    end;
    
    

    Oracle 常用的 变量类型

    1. 定义数据表时使用的变量类型(varchar2 number) 都能用
    2. 列级变量-- 表名.列名%type
    3. 行级变量-- 表名%rowtype

    PLSQL的 分支 选择 判断

    分支判断

    if 条件1 then 
    -- 处理逻辑
    elsif 条件2 then 
    -- 处理逻辑
    else
    -- 处理逻辑
    end if;
    

    while循环:

    while  条件  loop
    -- 处理逻辑
    end loop;
    

    例: 输出1-10

      declare
        i number:=1;
        begin
        while i<=10 loop
          dbms_output.put_line(i);
          i:= i+1;
        end loop;
      end;
    

    while 循环,不满足条件自动退出


    for 循环

    for 自定义的变量X in [reverse反转终止值和初始值] 初始值..终止值 loop
    -- 业务逻辑
    end loop;
    

    输出10 - 1:

    declare
    begin
      for i in reverse 1 ..10 loop   -- 起始的条件不需要声明
        dbms_output.put_line(i);
         --  i:=i+1;  循环遍历的下标   不能作为 赋值目标
      end loop;
    end;
    

    for循环中循环遍历的下标不能作为赋值的对象

    for循环中,自定义的变量X 不用提前在 declare和begin之间声明

    for循环的退出条件是 自定义的变量X 从起始值到终止值为止


    loop 循环

    loop 
    -- 业务逻辑
    exit when 退出条件
    
    end loop;
    

    输出 1-10

    declare
        i number := 1;
      begin
        loop
           i := i+1;
           dbms_output.put_line(i);
          exit when i=10;
        end loop;
      end;
    

    loop退出循环的条件是 exit when 我们手动控制


    • 例: 查询7369的工资, 并且打印出来
      -- 知道什么是 引用类型(单列的值) 表名.列名%type
      -- 如何赋值 into
       declare
          sval emp.sal%type;
       begin
          select sal into sval from EMP where EMPNO=7369;
          -- 打印
          dbms_output.put_line(sval);
       end;
    
    • 例 查询7369 的员工信息, 并且打印出来
      -- 关键是 找个变量把结果存起来 行级变量 %rowtype
    
     --sql  select * from EMP where EMPNO=7369;
       
       declare
          vrow emp%rowtype;   -- 行级变量   表名%rowtype
        begin
          select * into vrow from EMP where EMPNO=7369;
    
           --  dbms_output.put_line(vrow);   不能直接打印行!!! 而是 行.列 打印出来
    
            dbms_output.put_line('员工名称'||vrow.ENAME||'员工编号'||vrow.EMPNO);
       end;
    

    注意点: -- dbms_output.put_line(vrow); 不能直接打印行!!! 而是 行.列 打印出来

    游标

    什么是游标? 能给我们带来什么?

    我们可以把游标理解成一个指针,或者一个箭头,他可以指向我们给定的结果集(这个结果集其实就是我们的sql select的查询结果),然后我们可以用plsql的代码,控制游标遍历这个结果集,在遍历的时候我们可以取出当前行,也能取出当前行的具体一列的值,然后我们根据取出的值进行判断,下一步是该 update表数据,还是delete表数据

    最开始当游标指向这个结果集的时候,它指向的是结果集的表头部分,每当我们fetch一下,他就把下一行的数据(也就是第一行)给我们,再fetch 就给我们第二行

    游标的分类

    • 静态游标

      • 隐式游标
      • 没参数的,显示游标
    • 动态游标

      • 带参数的显示游标(ref)
      • 系统游标

    静态游标和动态游标的最大区别是啥?

    1. 静态游标在编译的时候就知道了结果集是什么, 因为它没有任何需要我们传递进去的参数
    2. 动态游标带参数,参数具体是几,只有真正运行的时候才知道,它的结果集是运行时才知道的,称它是动态游标

    语法

    cursor 游标名 in 查询结果集 -- 不带参数
    cursor 游标名[参数名, 参数类型] in 查询结果集  -- 带参数
    

    隐式游标的使用:

    begin 
    update t_student set sname='张三丰' where sid='10001';
    if sql%found then
    dbms_output.put_line('表已经更新');
    end if;
    end;
    

    每条sql默认自带一个隐式游标,游标的名字是 sql, 自动打开/关闭

    没参数的静态游标

    开发步骤;
    1. 声明游标
      -- cursor 游标名 is  查询结果集
    2. 打开游标
      -- open 游标名
    3. 取出游标中的数据( 游标相当于鼠标,每次都是从表头开始的, fetch一下,往下移动一行,正好从第一行开始)
      -- fetch 游标名 into 变量
        -- 游标名%find   找到数据
        -- 游标名%notfound  没有找到数据
    
    4. 关闭游标
      -- close 游标名`
    
    • 例 输出员工表中的员工姓名和工资(不带参数游标)
    -- select ename,SAL from emp ;
    -- 游标 -- 结果集: 所有员工
    -- 声明一个变量,用来记录一行数据  %rowtype
    
    declare
      -- 声明游标
      cursor vrows is  select * from emp ;
      -- 声明一个中间标量, 中转上面游标中获取的每一行
      vrow emp%rowtype;
      begin
      -- 开启游标
      open vrows;
         -- 遍历打印
          loop
            fetch vrows into vrow;    -- 取一条数据
            exit when vrows%notfound; -- 判断数据是否为空
    
            ---   再次强调一下,不能直接打印行
            -- dbms_output.out_line(vrow);
            ---
            dbms_output.put_line('姓名'||vrow.ename||'员工号'||vrow.EMPNO);  -- 这里输出的 内容,一定要存在于  上面的游标中
          end loop;
      -- 关闭游标
      close vrows;
    end;
    

    如果没有判断下一次获取的是否为空,当游标循环遍历到结果集的最后一行的时候,再次fetch,依然是最后一行的数据,还可能是内存溢出,所以判断条件紧跟在fetch之后,然后再是我的业务逻辑

    系统游标

    --- 系统 引用游标

      1. 声明游标
          -- 游标名  游标类型   sys_refcursor   (后面的 refcursor是个合成词)
      2. 打开游标,关联结果集
      3. 从游标中取数据
      4. 关闭游标
    

    例:

    -- 例 输出员工表中的员工姓名和工资(不带参数游标)

    declare
    -- 声明系统游标
    vrows sys_refcursor;
    -- 声明行级变量
    vrow  emp%rowtype;
    begin
      --  打开游标
      open vrows for select * from EMP ;  -- 系统游标在打开时,关联结果集
        -- 循环
        loop
            fetch vrows into vrow;  -- 获取值
            exit when vrows%notfound;
            dbms_output.put_line('姓名'||vrow.ename);
        -- 结束循环
        end loop;
    -- 关闭游标
      close vrows;
    end;
    

    带参数的动态游标

    --- 输出指定部门下的员工的姓名和工资( 带参数的游标 )

    /*
      游标: (操作的结果集) 指定部门的所有员工
      声明  一个变量记录一行数据
    */
    declare
      -- 声明带有参数的游标
      cursor vrows(dno number) is select * from EMP where DEPTNO = dno;
      -- 声明 行级 中转变量
      vrow  emp%rowtype;
    begin
       -- 打开游标  (传入参数)
          open vrows(10);
      -- 循环
          loop
             -- 取出数据
              fetch vrows into vrow ;
            -- 判断 结束循环
              exit when vrows%notfound;
            -- 输出结果
              dbms_output.put_line('姓名'||vrow.ENAME||'工资'||vrow.sal);
          end loop;
       -- 关闭游标
          close vrows;
    end;
    

    系统游标 和 不带参的静态游标

    • 相同点: 都是指向了某个结果集,可以一条一条的遍历结果集,获取出每一条数据
    • 不同点: 获取结果集的位置不同
      • 普通游标获取操作集合的位置是 declare 和 begin 之间
      • 系统游标 在begin 和 end 之间 的打开游标位置 ,使用的关键字不再是 is 而是 for
      • 声明方式不同!
      • 普通游标可带参数
      • 系统游标的声明固定写法; 名 sys_refcursor;

    for 循环遍历游标

    上面的例子都是使用loop循环遍历游标,控制退出的条件是 游标%notfound

    下面使用for循环遍历游标

    
      declare
        cursor v_rows is select * from emp;
      begin
        for  v_row in  v_rows loop
          dbms_output.put_line(v_row.JOB);
        end loop;
      end;
    

    不用提前声明 需要中转每一行的变量

    不用手动控制循环退出的条件

    小案例

    • 游标的小案例:
      -- .1 按照员工工作 给员工涨工资, 总裁涨1000 经理涨 800 其他人 400
    /*    -- 查询经理信息
      select mgr from emp where mgr is not null;
      -- 不查询不为空的员工信息
    */
    declare
      --   声明游标
      cursor vrows is select * from emp;
      -- 声明变量
      vrow emp%rowtype;
    begin
       -- 打开游标
      open vrows;
        -- 遍历游标
        loop
            -- 获取游标的当前值
          fetch vrows into vrow;
          -- 判断是否为空
          exit when vrows%notfound;
          -- 不为空,就加工资
          if vrow.job = 'MANAGER' then
            -- 注意点:  set的字段是 不能是  中专变量名.列名  而直接是类名 , 因为我们是在更新表中的字段,而不是在更新中转的那个变量
            update emp set sal  = sal+400 where empno = vrow.EMPNO;   -- plsql里面的  等于 :=  和 sql里面的  等于 = 区分开
          elsif vrow.job = 'CLERK' then
            update emp set  sal  = sal+800 where empno = vrow.EMPNO;
          end if;
        end loop;
      close vrows;
    end;
    

    注意点 : plsql和sql混在一起写的时候,注意点就是 sql中的赋值运算符是 = 但是plsql中的赋值运算符是 := 别整混了

    异常

    有啥用?

    通常异常用来处理我们在业务逻辑阶段可能出现的逻辑错误,我们捕获了相应的异常之后自己处理掉,不至于影响后续的操作

    语法

    declare 
      -- 声明异常
    begin
        -- 业务逻辑
    exception   -- 声明下面是处理异常的部分
        -- 处理异常
        when 异常1 then
        -- 处理逻辑
        when 异常2 then
        -- 处理逻辑
    end;
    

    Oracle中异常的分类

    • 系统异常
      • zero_divide : 除零异常
      • value_error : 类型转换异常
      • too_many_row: 查询查了多行的记录,但是却把它赋值给一个行级的变量
      • no_data_found: 没有找到记录
      • raise_application_error(状态码,'异常信息') : 运行时,系统异常
    • 用户自定义异常

    用户自定义异常,分三步

    1. 声明异常
    2. raise 抛出异常
    3. 在exception后面捕获异常
    4. 我们操作的结果集只能来自游标的结果集,(下面解释)

    • 看下面的代码,查询一个不存在的人的信息,看着貌似会捕获处理我们自定义的异常,但是其实并不会! 因为执行到 select的时候就会出现异常 no_data_found, 直接停止往下执行,我们的逻辑根本不会触发
    declare
      vrow emp%rowtype; -- 声明行级变量
      no_emp exception; -- 自定义异常
     begin
        select * into vrow from emp where EMPNO = 8888;  -- 如果这是 888 没有这个员工,执行到这句就会 抛出异常,而不会往下执行抛出 自定义的异常
        if vrow.SAL is not null then  -- vrow一直是有的,我们通过里面的一个字段判断他是否为空
           dbms_output.put_line('员工信息'||vrow.sal);  -- 注意这里的,依然不能使用  dbms 输出一整行
        else
          raise no_emp;  -- 抛出自定义的异常  (自己抛出,自己捕获)
        end if;
     exception
      when no_emp then
      dbms_output.put_line('没有该员工信息');
    
      when others then
      dbms_output.put_line('其他异常'||sqlerrm);   -- sqlerrm 输出其他异常
    end;
    
    • 简单的select 查询没办法完成我们的需求, 因为它碰到异常会报错,但是游标不会!!! %found %notfound
    • 解决
    • 声明 行级变量
    • 声明游标(带参数), 获取要操作的结果集
    • 遍历游标, 判断,如果为空,抛出我们自定义的异常
    declare 
          vrow emp%rowtype;   -- 声明行级变量
          --- 注意点: 游标的查询语句中去 *  再赋值给行级变量 (不然报错))
          cursor emp_Info(empno NUMBER) is select * from EMP; -- 声明游标,并获取结果集
          no_emp exception ; -- 自定义异常
         begin
          -- 打开=游标  传入参数
            open emp_Info(1);
            loop
                fetch emp_Info into vrow;  -- 获取元素
                if emp_Info%notfound then   -- 如果找不到
                     raise no_emp;  -- ,退出循环
                     exit ;         -- 退出循环
                end if;
            end loop;
          -- 关闭游标
            close emp_Info;
        -- 捕获异常
         exception
          when no_emp then
            dbms_output.put_line('没找到信息');
          when others then
            dbms_output.put_line('其他异常'||sqlerrm);
       end;
    

    用户自定义异常的例子

    declare
    
    begin
    
    end;
    

    捕获系统异常的例子:

    declare
        vi number;
      vrow emp%rowtype;  -- 行级变量
    begin
      vi:=vi/0; -- 除零异常
      vi:='abc'; -- 类型转换异常
      select * into vrow from EMP;  -- 多行数据赋给 行级变量
      select * into vrow from DEPT where DEPTNO=123;  -- 没找到异常信息异常
    
    exception
      when zero_divide then
        dbms_output.put_line('发生了除零异常');
    
      when value_error then
        dnms_output.put_line('类型转换异常');
    
      when to_many_rows then
        dbms_output.putline('多对 一  异常');
    
      when no_data_found  then
        dbms_output.put_line('没找到数据');
    
      when others then
        dbms_output.put_line('发生了其他异常');
    
    end;
    
    

    储存过程/存储函数 --procedure

    什么是存储过程:

    存储过程就是一段已经封装好了的 plsql代码片段, 这个代码片段提前已经编译好了,用户在调用的时候就不用再编译大大提高了 数据库的吞吐效率

    存储过程不编译是不能被调用的!!!

    语法:

    create or replace procedure 存储过程的名(参数1 in|out  参数类型,.......)
    is | as
        -- 声明部分
    begin
        -- 业务逻辑
    end;
    

    in 表示: 该参数是输入参数

    out 表示: 该参数是输出参数

    例子: 使用存储过程给员工涨工资,

    create or replace procedure pro_update_sal(vempno in number , vsal in number)
    is
      -- 声明部分
      -- 存储涨工资之前的 工资
      v_sal number ;
    begin
    -- 存储涨工资之前的工资
      select sal into v_sal from emp where EMPNO=vempno;
      -- 涨工资
      update emp set SAL=v_sal+vsal where EMPNO=vempno;
    -- 提交事务, 我们的存储过程里面的事务通常都是在里面直接提交的
      commit;
        exception
    when others then  rollback ;
    end;
    

    一般存储过程中加上 事务的提交,和出现异常时回滚

    如何调用

    -- 调用方式一:
    call pro_update_sal(7369,100);
    
    -- 调用方式二:
    declare
    
    begin
    pro_update_sal(7369,100);
    end;
    
    -- 方式三:
    begin
    pro_update_sal(7369,100);
    end;
    

    带返回值的存储过程

    
    create or  replace  procedure get_pro (v_no in  number , total_sal out number)
      is
      begin
        select  SAL*12+ nvl(comm,0) into total_sal from EMP where EMPNO = v_no;
        commit;
        exception
          when others then
            rollback ;
      end;
    

    不用显示的声明,声明返回值,把查询的结果赋值给 入参位置的out类型的参数就行,她自动的返回

    调用

    declare
      t_sal number;
    begin
        get_pro(7369,t_sal);
      dbms_output.put_line(t_sal);
    end;
    
    

    传递进去两个参数,不需要我们写 t_sal = get_pro(7369,t_sal)(错误的写法) 她会自动的把数据传递进t_sal ,

    什么是存储函数

    同样是一段封装在oracle 服务器中的一段plsql 代码片段, 他是已经编译好了的代码片段

    语法:

    create [or replace] function 存储函数的名称 (参数名 in|out 参数类型,参数名 in|out 参数类型.参数名 in|out 参数类型)  return 参数类型
    is | as
    
    begin
    
    end;
    

    例: 使用存储函数 查询指定员工的年薪

    create or replace function fun_get_empsal(v_empno number) return number
     is
        -- 声明变量  保存年薪
        total_sal number;
     begin
        select sal*12 + nvl(comm,0) into total_sal  from EMP where EMPNO=v_empno;   -- nvl(p1,p2)   第一个字段为空,那么就取第二个字段的值
       return total_sal;
     end;
    
    

    调用

    declare
        v_cal number;
      begin
        v_cal  :=  fun_get_empsal(number);
         dbms_output.put_line(v_cal);
    end;
    

    标准sql调用 存储函数

    select ename, fun_get_empsal(EMPNO) from EMP;
    

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

    1. 其实他们本质上是没有区别的,两者都可以实现相互的功能
    2. 语法上的区别
      3. 存储函数参数的小括号后面紧跟着 return语句
      4. 关键字 一个数 procedure 一个是 function
    3. 存储函数大多数情况下是给存储过程调用的
    4. 存储函数可以给标准的sql语句调用,但是存储过程不行

    触发器

    什么是触发器 --trigger

    Oracle 和 Mysql 中都有触发器,

    他是一段代码,用来监听用户对数据表的具体某个操作,可以在这个操作前后进行拦截,执行我们添加进去的操作

    分类

    1. 语句级触发器(表级别)

    操作无论影响多好行, 触发器执行一次

    1. 行级触发器 -- 关键字 for each row

    操作影响多少行,触发多少次

    简单的触发器 语法

    create or replace trigger  -- 创建触发器
    before | after      -- 指定触发的时机
    insert | delete | update   -- 监听的操作
    on 表名  -- 针对哪张表
    [for each row]  时候针对每一行
    declare
    
    begin
    
    end;
    

    例: 插入员工之后,输出一句话 XXX -- 每次有inset语句, 都会先执行我们的 输出XXX

    create or replace trigger trrigger_one
      after
      insert
      on EMP
      declare
        begin
        dbms_output.put_line('XXX');
      end;
    
    • 行级触发器: for each row

    行级触发器有两个内置的对象
    :new 记录了表中某个字段更新前的值
    :old 记录表中的某个字段更新后的值

    在 on 表名 和 declare 之间添加 for each row , 使触发器升级成 行级触发器

    触发器的练习--- 模拟mysql的 id 自增长

    思路: 触发器监听用户对表的insert操作,每当监听到有插入操作的时候,就会使用我们的 序列自增1,给它补全

    -- 创建表
    create table person(
        id number primary key ,
        name varchar2(10)
    );
    
    -- 创建序列--并编译执行
    create sequence id_auto_increament;
    
    -- 创建监听对person表 的insert操作的 触发器  -- 并编译执行
    create or replace trigger trigger_insert
      before
      insert
      on person
       for each row
      declare
      begin
        select id_auto_increament.nextval into :NEW.id from dual;
    end;
    
    插入数据
    insert into person values(null,'长沙'); -- 成功插入
    
    

    注意点: :NEW这个内置对象只属于行级触发器

    -- create 关键字 :
    -- 创建表
    -- 创建view
    -- 创建index
    -- 创建synonym
    -- 创建存储过程
    -- 创建用户
    -- 创建触发器
    -- 创建序列

  • 相关阅读:
    python_深浅拷贝
    Python_三级菜单
    python 字符串
    python_字典
    初识Python
    C# ADO.NET面向对象想法
    C# ADO.NET
    C# ADO.NET 面向对象
    C# 数据库连接增删改查
    C# 面向对象多态的抽象性&接口 object&is as类型转换运算符
  • 原文地址:https://www.cnblogs.com/ZhuChangwu/p/11150507.html
Copyright © 2011-2022 走看看