Oracle 存储过程快速入门

1.什么是oracle存储过程

    存储过程和函数也是一种PL/SQL块,是存入数据库的PL/SQL块。但存储过程和函数不同于已经介绍过的PL/SQL程序,我们通常把PL/SQL程序称为无名块,而存储过程和函数是以命名的方式存储于数据库中的。和PL/SQL程序相比,存储过程有很多优点,具体归纳:
    1. 存储过程和函数以命名的数据库对象形式存储于数据库当中。存储在数据库中的优点是很明显的,因为代码不保存在本地,用户可以在任何客户机上登录到数据库,并调用或修改代码。
    2. 存储过程和函数可由数据库提供安全保证,要想使用存储过程和函数,需要有存储过程和函数的所有者的授权,只有被授权的用户或创建者本身才能执行存储过程或调用函数。
    3. 存储过程和函数的信息是写入数据字典的,所以存储过程可以看作是一个公用模块,用户编写的PL/SQL程序或其他存储过程都可以调用它(但存储过程和函数不能调用PL/SQL程序)。一个重复使用的功能,可以设计成为存储过程。
    4. 像其他高级语言的过程和函数一样,可以传递参数给存储过程或函数,参数的传递也有多种方式。存储过程可以有返回值,也可以没有返回值,存储过程的返回值必须通过参数带回;函数有一定的数据类型,像其他的标准函数一样,我们可以通过对函数名的调用返回函数值。 
    5. 存储过程和函数需要进行编译,以排除语法错误,只有编译通过才能调用。

2.如何创建和删除存储过程

    2.1.创建存储过程的语法
    CREATE [OR REPLACE] PROCEDURE 存储过程名[(参数[IN|OUT|IN OUT] 数据类型...)] 
    {AS|IS} 
    [说明部分] 
    BEGIN 
    可执行部分 
    [EXCEPTION 
    错误处理部分] 
    END [过程名];
    其中: 
    可选关键字OR REPLACE 表示如果存储过程已经存在,则用新的存储过程覆盖,通常用于存储过程的重建。 
    参数部分用于定义多个参数(如果没有参数,就可以省略)。参数有三种形式:INOUTIN OUT。如果没有指明参数的形式,则默认为IN。 关键字AS也可以写成IS,后跟过程的说明部分,可以在此定义过程的局部变量。(注意:大小写不敏感)。
    简单的存储过程示例:
    create or replace procedure hello_world as
    say_hi varchar2(20);
    begin
      say_hi := 'Hello World!';
      dbms_output.put_line(say_hi);
    end;

    2.2 删除存储过程
    语法:
    DROP PROCEDURE 存储过程名;
    如:drop procedure hello_world;

3.存储过程的查看

    可以通过对数据字典的访问来查询存储过程或函数的有关信息,如果要查询当前用户的存储过程或函数的源代码,可以通过对USER_SOURCE数据字典视图的查询得到。

    3.1查看存储过程的脚本
    如查询存储过程hello_world的脚本
    Select text from user_source where name = ‘HELLO_WORLD’;
    (这里的过程名必须大写)

    3.2 查看存储过程的状态
    如:select status from user_objects where object_name = ‘HELLO_WORLD’;
    说明:VALID表示该存储过程有效(即通过编译),INVALID表示存储过程无效或需要重新编译。当Oracle调用一个无效的存储过程或函数时,首先试图对其进行编译,如果编译成功则将状态置成VALID并执行,否则给出错误信息。 
    当一个存储过程编译成功,状态变为VALID,会不会在某些情况下变成INVALID。结论是完全可能的。比如一个存储过程中包含对表的查询,如果表被修改或删除,存储过程就会变成无效INVALID。所以要注意存储过程和函数对其他对象的依赖关系。

4.存储过程参数说明

    三种形式的参数
    1. IN  定义一个输入参数变量,用于传递参数给存储过程   
    2. OUT 定义一个输出参数变量,用于从存储过程获取数据   
    3. IN OUT  定义一个输入、输出参数变量,兼有以上两者的功能

参数的定义形式和作用如下: 

    4.1 IN参数
    语法:参数名 IN 数据类型 DEFAULT 值; 
    定义一个输入参数变量,用于传递参数给存储过程。在调用存储过程时,主程序的实际参数可以是常量、有值变量或表达式等。DEFAULT 关键字为可选项,用来设定参数的默认值。如果在调用存储过程时不指明参数,则参数变量取默认值。在存储过程中,输入变量接收主程序传递的值,但不能对其进行赋值。 

    4.2 OUT参数
    语法:参数名 OUT 数据类型; 
    定义一个输出参数变量,用于从存储过程获取数据,即变量从存储过程中返回值给主程序。 
在调用存储过程时,主程序的实际参数只能是一个变量,而不能是常量或表达式。在存储过程中,参数变量只能被赋值而不能将其用于赋值,在存储过程中必须给输出变量至少赋值一次。 

    4.3 IN OUT参数
    语法:参数名 IN OUT 数据类型 DEFAULT 值; 
    定义一个输入、输出参数变量,兼有以上两者的功能。在调用存储过程时,主程序的实际参数只能是一个变量,而不能是常量或表达式。DEFAULT 关键字为可选项,用来设定参数的默认值。在存储过程中,变量接收主程序传递的值,同时可以参加赋值运算,也可以对其进行赋值。在存储过程中必须给变量至少赋值一次。

    示例:
    create or replace procedure say_hi(to_whom in varchar2 default '张三', who out varchar2)
    as
    who_name varchar(20);
    begin
      who_name := '李四';
      who := who_name;
      dbms_output.put_line('Say Hi to '||to_whom);
    end;

    create or replace procedure invoke_say_hi
    as
    who varchar2(20);
    whom varchar2(20);
    begin
      whom := '小明';
      say_hi(whom, who);
      say_hi(who => who);
      dbms_output.put_line(who||'say hi to '||whom);
    end;

    参数的值由调用者传递,传递的参数的个数、类型和顺序应该和定义的一致。如果顺序不一致,可以采用以下调用方法:
    过程名(参数名 => 参数的值,参数名 => 参数的值, ~~~~~);
    如上面的示例: say_hi(who => who);  =>运算符左侧是参数名,右侧是参数表达式.

    存储过程参数宽度:
    无法在存储过程的定义中指定存储参数的宽度,也就导致了我们无法在存储过程中控制传入变量的宽度。这个宽度是完全由外部传入时决定的。

5.游标

    5.1 普通游标
    游标定义: cursor  [游标名] is [sql语句]
    普通游标把整个查询已经写死,调用时不可以作任何改变
    注意:这里的is不能用as代替
    示例:
    create or replace procedure cursor_sample
    as
    user_name varchar2(20);
    user_id varchar2(20);
    cursor select_user is select t.user_name, t.user_id from t_user t;
    begin
      open select_user;
      loop
        fetch select_user into user_name, user_id;
        exit when select_user%notfound;
        dbms_output.put_line('UserId:'||user_id||' UserName: '||user_name);
      end loop;
      close select_user;
    end;

    5.2动态查询游标
        动态查询游标,查询条件的参数由变量决定。
    例:
    create or replace procedure cursor_sample2
    as
    vuser_id varchar2(20);
    vuser_name varchar2(20);
    cursor get_user_by_id is select t.user_id, t.user_name from t_user t where t.user_id = vuser_id;
    begin
      vuser_id := 'root2';
      open get_user_by_id;
      loop
        fetch get_user_by_id into vuser_id, vuser_name;
        exit when get_user_by_id%notfound;
        dbms_output.put_line('UserName:'||vuser_name||'   UserId:'||vuser_id);
      end loop;
      close get_user_by_id;
    end;

    5.3游标变量
    先定义了一个引用游标类型,然后再声明了一个游标变量. 然后再用open for 来打开一个查询。需要注意的是它可以多次使用,用来打开不同的查询。

    示例:
    create or replace procedure cursor_sample3
    as
    vuser_name varchar2(20);
    vuser_id varchar2(20);
    type cursor_type is ref cursor;
    select_user cursor_type;
    begin
      open select_user for select t.user_name, t.user_id from t_user t;
      loop
        fetch select_user into vuser_name, vuser_id;
        exit when select_user%notfound;
        dbms_output.put_line('UserName:'||vuser_name||'  ||UserId:'||vuser_id);
      end loop;
      close select_user;

      dbms_output.put_line('***********************************************');
      dbms_output.put_line('****************第二次使用游标**********************');
      open select_user for select t.user_name, t.user_id from t_user t where t.user_id = 'root2';
      loop
        fetch select_user into vuser_name, vuser_id;
        exit when select_user%notfound;
        dbms_output.put_line('UserName:'||vuser_name||'  ||UserId:'||vuser_id);
       end loop;
       close select_user; 
    end;

    5.4游标循环的方法
    (1)游标的%found和%notfound属性。
    能从游标中取出记录,得到的结果为%found,取不到记录为%notfound。在打开一个游标之后,马上检查它的%found或%notfound属性,它得到的结果即不是true也不是false.而是null.必须执行一条fetch语句后,这些属性才有值。

        三种循环方法
        5.4.1Loop循环】
        loop
            fetch select_user into vuser_name, vuser_id;
        exit when select_user%notfound;
        【Do something】;
        end loop;
        close select_user; 

        5.4.2while循环】
        fetch select_user into vuser_name, vuser_id;
          while select_user%found loop
            dbms_output.put_line('UserName:'||vuser_name||'  ||UserId:'||vuser_id);
            fetch select_user into vuser_name, vuser_id;
        end loop;
        close select_user;

        说明:我们知道了一个游标打开后,必须执行一次fetch语句,游标的属性才会起作用。所以使用while 循环时,就需要在循环之前进行一次fetch动作。 
        而且数据处理动作必须放在循环体内的fetch方法之前。循环体内的fetch方法要放在最后。否则就会多处理一次。这一点也要非常的小心。 
        使用while来循环处理游标是最复杂的方法。

        5.4.3for循环】
        create or replace procedure cursor_sample4
        as
        vuser_name varchar2(20);
        vuser_id varchar2(20);
        cursor select_user is select t.user_name, t.user_id from t_user t;
        begin
          for v_pos in select_user loop
            vuser_name := v_pos.user_name;
            vuser_id := v_pos.user_id;
            dbms_output.put_line('UserName:'||vuser_name||'UserId'||vuser_id);
          end loop;
        end;

        说明:for循环是比较简单实用的方法。 
        首先,它会自动open和close游标。解决了你忘记打开或关闭游标的烦恼。 
        其次,自动定义了一个记录类型及声明该类型的变量,并自动fetch数据到这个变量中。 我们需要注意v_pos 这个变量无需要在循环外进行声明,无需要为其指定数据类型。 它应该是一个记录类型,具体的结构是由游标决定的。这个变量的作用域仅仅是在循环体内。 把v_pos看作一个记录变量就可以了,如果要获得某一个值就像调用记录一样就可以了。 如v_pos.user_name由此可见,for循环是用来循环游标的最好方法。高效,简洁,安全。

6.异常处理

    语法:exception when [异常名] then [dosomething] …
          When others then [dosomething];
    示例:
    create or replace procedure exception_sample
    as
    vuser_name varchar2(20);
    vuser_id varchar2(20);
    vsqlcode varchar2(10);
    vsqlerrm varchar2(1000);
    begin
      select t.user_name, t.user_id into vuser_name, vuser_id from t_user t where 1 = 0;
      exception
        when others
        then 
        vsqlcode := sqlcode;
        vsqlerrm := sqlerrm;
        dbms_output.put_line('Exception, code:'||vsqlcode||' Error message:'||sqlerrm);
    end;

    另外:若在代码中需要抛出异常时,用raise+异常名
    如以下例子:
    create or replace procedure exception_sample2
    as
    vuser_name varchar2(20);
    vuser_id varchar2(20);
    yourexception exception;  --自定义异常
    cursor select_user is select t.user_name, t.user_id from t_user t;
    begin
      open select_user;
      loop
        fetch select_user into vuser_name, vuser_id;
        exit when select_user%notfound;
        if vuser_id = 'root2' then
          raise yourexception;  --抛出异常
        end if;
      end loop;
      exception
        when yourexception then
          dbms_output.put_line('人品异常');
        when others then
          dbms_output.put_line(sqlcode||sqlerrm);
    end;

    已经命名的异常
    命名的系统异常                          产生原因
    ACCESS_INTO_NULL                   未定义对象
    CASE_NOT_FOUND                     CASE 中若未包含相应的 WHEN ,并且没有设置
    ELSE 时
    COLLECTION_IS_NULL                集合元素未初始化
    CURSER_ALREADY_OPEN          游标已经打开
    DUP_VAL_ON_INDEX                   唯一索引对应的列上有重复的值
    INVALID_CURSOR                 在不合法的游标上进行操作
    INVALID_NUMBER                       内嵌的 SQL 语句不能将字符转换为数字
    NO_DATA_FOUND                        使用 select into 未返回行,或应用索引表未初始化的 

    TOO_MANY_ROWS                      执行 select into 时,结果集超过一行
    ZERO_DIVIDE                              除数为 0
    SUBSCRIPT_BEYOND_COUNT     元素下标超过嵌套表或 VARRAY 的最大值
    SUBSCRIPT_OUTSIDE_LIMIT       使用嵌套表或 VARRAY 时,将下标指定为负数
    VALUE_ERROR                             赋值时,变量长度不足以容纳实际数据
    LOGIN_DENIED                           PL/SQL 应用程序连接到 oracle 数据库时,提供了不
    正确的用户名或密码
    NOT_LOGGED_ON                       PL/SQL 应用程序在没有连接 oralce 数据库的情况下
    访问数据
    PROGRAM_ERROR                       PL/SQL 内部问题,可能需要重装数据字典& pl./SQL
    系统包
    ROWTYPE_MISMATCH                宿主游标变量与 PL/SQL 游标变量的返回类型不兼容
    SELF_IS_NULL                             使用对象类型时,在 null 对象上调用对象方法
    STORAGE_ERROR                        运行 PL/SQL 时,超出内存空间
    SYS_INVALID_ID                         无效的 ROWID 字符串
    TIMEOUT_ON_RESOURCE         Oracle 在等待资源时超时

7.过程内部块

    我们知道了存储过程的结构,语句块由begin开始,以end结束。这些块是可以嵌套。在语句块中可以嵌套任何以下的块:
    Declare … begin … exception … end; 
    例:
    create or replace procedure innerblock_sample
    as
    vuser_name varchar2(20);
    vuser_id varchar2(20);
    cursor select_user is select t.user_name, t.user_id from t_user t;
    begin
      open select_user;
      loop
        fetch select_user into vuser_name, vuser_id;
        exit when select_user%notfound;
        dbms_output.put_line('UserName:'||vuser_name||'UserId'||vuser_id);
      end loop;
      close select_user;

      declare 
        vuser_level varchar2(20);
        cursor get_level is select t.user_level from t_user t;
        begin
          open get_level;
          loop
            fetch get_level into vuser_level;
            exit when get_level%notfound;
            dbms_output.put_line('UserLevel:'||vuser_level);
          end loop;
          close get_level;
          exception when others then
            dbms_output.put_line(sqlcode||sqlerrm);
       end;
       exception when others then
         dbms_output.put_line(sqlcode||sqlerrm);
    end;

你可能感兴趣的:(DB,编程开发)