PL/SQL笔记

1、基础知识

PLSQL是一种类Pascal语言,每一段程序都是由Block(代码块)组成

declare
     变量定
begin
    sql语句
    pl语句
exception
    异常处理
end;

PLSQL块分为三种:

  • 匿名块 (Anonymous)
  • 存储过程 (Procedure)
  • 函数 (function)

上面举得例子就是匿名块,因为没有名字,所以叫匿名块。存储过程和函数都是有名字的,函数有返回值。

2、PLSQL变量

PLSQL变量主要有以下四种:

  • 系统内置的常规简单变量类型
  • 用户自定义复杂类型变量
  • 引用类型变量(保存了一个指针值)
  • 大对象类型(LOB)

变量类型举例:

  • 布尔类型
  • 日期类型
  • BFILE的二进制文件类型
  • 日期类型
  • BLOB类型
  • long类型,长字符串
  • 字符串类型

变量声明举例:

DECLARE v_hiredateDATE;
v_deptno NUMBER(2) NOT NULL := 10;
v_location VARCHAR2(13) := 'Atlanta';
c_comm CONSTANT NUMBER := 1400;
  • 变量声明后再没有赋值之前其值为NULL
  • 同一个块中,应该避免多个变量使用相同的名字

常规变量的申明:

DECLARE
    v_job VARCHAR2(9);
    v_count BINARY_INTEGER := 0;
    v_total_sal NUMBER(9,2) := 0;
    v_orderdate DATE := SYSDATE + 7;
    c_tax_rate CONSTANT NUMBER(3,2) := 8.25;
    v_valid BOOLEAN NOT NULL := TRUE;
    ... 
-- 特殊申明变量方式:
    v_name employees.last_name%TYPE;
    v_min_balance v_balance%TYPE := 10;
begin
   null;
end;

PLSQL中的游标

游标概论

游标是一个私有的SQL工作区域,Oracle数据库中有两种游标,分别是隐式游标和显式游标,隐式游标不易被用户和程序员察觉和意识到,实际上Oracle服务器使用隐式游标来解析和执行我们提交的SQL语句; 而显式游标是程序员在程序中显式声明的;通常我们说的游标均指显式游标。

显示游标

对于返回多行结果的SQL语句的返回结果,可使用显式游标独立的处理器中每一行的数据。

此处图片缺失

显式游标控制的一般过程:

DECLARE
    v_empno employees.employee_id%TYPE;
    v_ename employees.last_name%TYPE;
    CURSOR emp_cursor IS --1.创建游标
      SELECT employee_id, last_name FROM employees;
BEGIN
    OPEN emp_cursor; --2.打开游标
    LOOP
      FETCH emp_cursor
        INTO v_empno, v_ename; --3.提取变量  fetch cursor_name into value1,value2;

      EXIT WHEN emp_cursor%ROWCOUNT > 10 OR emp_cursor%NOTFOUND; --emp_cursor%NOTFOUND 4.用来测试是否有数据
      DBMS_OUTPUT.PUT_LINE(TO_CHAR(v_empno) || ' ' || v_ename);
    END LOOP;
    CLOSE emp_cursor; --5.关闭游标
END;

for循环控制游标

举例1:直接使用for+sql创建并适用游标

   --一般格式:
   --for record_name in cursor_name loop ... end loop;
   BEGIN
   FOR emp_record IN (SELECT last_name, department_id
   FROM employees) LOOP
   -- implicit open and implicit fetch occur
   IF emp_record.department_id = 80 THEN
   ...
   END LOOP; -- implicit close occurs
   END;

举例2:先创建游标emp_cursor,再用for调用

--这种情况适用于多次调用游标的情况
 DECLARE
   CURSOR emp_cursor IS --先创建游标
   SELECT last_name, department_id
   FROM employees;
BEGIN
   FOR emp_record IN emp_cursor LOOP --再调用游标访问
   -- implicit open and implicit fetch occur
   IF emp_record.department_id = 80 THEN
   ...
   END LOOP; -- implicit close occurs
END;

游标带参数

直接返回和参数相关的查询结果

 DECLARE
    CURSOR emp_cursor
       (p_deptno NUMBER, p_job VARCHAR2) IS
        SELECT employee_id, last_name
        FROM employees
        WHERE department_id = p_deptno
        AND job_id = p_job;
BEGIN
    OPEN emp_cursor (80, 'SA_REP');
    . . .
    CLOSE emp_cursor;
    OPEN emp_cursor (60, 'IT_PROG');
    . . .
END;

在游标中使用FOR UPDATE NOWAIT

有的时候我们打开一个游标是为了更新或者删除一些记录,这种情况下我们希望
在打开游标的时候即锁定相关记录,应该使用for update nowait语句,倘若锁定失败我们就停止不再继续,以免出现长时间等待资源的死锁情况。

举例:

DECLARE
    CURSOR emp_cursor IS
        SELECT employee_id, last_name, department_name
        FROM employees,departments
        WHERE employees.department_id =
              departments.department_id
        AND employees.department_id = 80
        FOR UPDATE OF salary NOWAIT; 

PLSQL中的异常

PLSQL中一般有两种异常,一种是由Oracle内部错误抛出的异常,分为预定义和非预定义两种;另外一种是由程序员显式的抛出。

常见的异常:

异常代码 异常类型
NO_DATA_FOUND 没有发现数据
INVALID_CURSOR 游标在被打开以前,引用%NOTFOUND属性会产生此异常
TOO_MANY_ROWS 返回数据过多
ZERO_DIVIDE 除数为零,出现异常
DUP_VAL_ON_INDEX 唯一索引上有重复值

对othres的处理

others表明我们未能预计的错误,所以全部归到othres中去,单发生这种情况时,我们希望了解错误号和相关信息,可以使用Oracle内置的函数SQLCODE和SQLERRM来返回错误号和错误描述,举例见下:

DECLARE
    v_error_code NUMBER;
    v_error_message VARCHAR2(255);
BEGIN
    ...
EXCEPTION
    ...
WHEN OTHERS THEN
    ROLLBACK;
    v_error_code := SQLCODE ;
    v_error_message := SQLERRM ;
    INSERT INTO errors
    VALUES(v_error_code, v_error_message);
END;

处理用户自定义异常

此处图片缺失

举例:

DECLARE
    e_invalid_department EXCEPTION;
BEGIN
    UPDATE departments
    SET department_name = &p_department_desc
    WHERE department_id = &p_department_number;
    IF SQL%NOTFOUND THEN
        RAISE e_invalid_department;
    END IF;
    COMMIT;
EXCEPTION
    WHEN e_invalid_department THEN
        DBMS_OUTPUT.PUT_LINE('No such department id.');
END;

传递异常到外层代码块

DECLARE
    . . .
    e_no_rows exception;
    e_integrity exception;
    PRAGMA EXCEPTION_INIT (e_integrity, -2292);
BEGIN
    FOR c_record IN emp_cursor LOOP
      --内层代码块
      BEGIN
        SELECT ...
        UPDATE ...
        IF SQL%NOTFOUND THEN
            RAISE e_no_rows; --将异常扔到外层代码块中,使用关键字:RAISE
        END IF;
      END;

    END LOOP;
EXCEPTION
    WHEN e_integrity THEN ...
    WHEN e_no_rows THEN ...
END;

存储过程

存储过程基本语法

语法:

CREATE [OR REPLACE] PROCEDURE procedure_name
    [(parameter1 [mode1] datatype1,
    parameter2 [mode2] datatype2,
    . . .)]
IS|AS
PL/SQL Block;

举例:

CREATE OR REPLACE PROCEDURE raise_salary
    (p_id IN employees.employee_id%TYPE)
IS
BEGIN
    UPDATE employees
    SET salary = salary * 1.10
    WHERE employee_id = p_id;
END raise_salary;

存储过程参数模式

IN OUT IN OUT
默认模式 必须显示指定 必须显示指定
用以把值传给过程 用以把值从过程返回给调用环境 用以把变量传递给过程,并返回给调用环境
参数可以是常数、变量、表 必须是个变量 必须是个变量
必须是个变量 不能赋予默认值 不能赋予默认值

举例:IN OUT型变量的应用

--程序功能为:将传入的电话号码字符串按xxx-xxx-xxxxx的形式返回
CREATE OR REPLACE PROCEDURE format_phone
    (p_phone_no IN OUT VARCHAR2)
IS
BEGIN
    p_phone_no := '(' || SUBSTR(p_phone_no,1,3) ||
    ')' || SUBSTR(p_phone_no,4,3) ||
    '-' || SUBSTR(p_phone_no,7);
END format_phone;
/

存储过程的参数传递

  • 使用默认值
  • 按顺序传递
  • 使用=>传递

举例:

首先创建存储过程add_dept,设定了两个参数,p_namep_loc

--设定默认参数使用关键字:DEFAULT
CREATE OR REPLACE PROCEDURE add_dept
    (p_name IN departments.department_name%TYPE DEFAULT 'unknown',
    p_loc IN departments.location_id%TYPE DEFAULT 1700)
IS
BEGIN
    INSERT INTO departments(department_id,
                department_name, location_id)
    VALUES (departments_seq.NEXTVAL, p_name, p_loc);
END add_dept;
/

然后,使用调用add_dept,分别使用三种方式传递参数

BEGIN
    add_dept; --1.默认参数
    add_dept ('TRAINING', 2500); --2.顺序传递
    add_dept ( p_loc => 2400, p_name =>'EDUCATION');--3.使用=>传递
    add_dept ( p_loc => 1200) ;
END;
/
SELECT department_id, department_name, location_id
FROM departments; --sql查询验证

存储过程处理异常

此处图片缺失

函数

自治事务

关键字:

一个事务A在另一个事务B内被调用,那个事务A是自治事务,自治事务A执行过程中会脱离其session内未执行完毕的事务的影响。如果session从B事务开始——A事务开始和结束——B事务结束,上述的A事务不受没有完成的B事务的影响,然后A事务执行完毕后再次回到B事务执行没有完成的B事务。

创建自治事务语法:

Create or replace procedure pro_name as pragma AUTONOMOUS_TRANSACTION;
Begin
 null;
End;

下面通过一个例子来对自治事务进行说明和解释,首先创建一个procedure—noautonomous_transaction01

Create or replace procedure noautonomous_transaction01 as
Begin
    Insert into test01 select * from test01 where rownum=1;
    Commit;
End;
--进行查询测试
Select count(*) from test01
--结果:Count(*)|25

此时,发现test01有25条数据

declare
begin
    Insert into test01 select * from test01 where rownum=1;
    Noautonomous_transaction01;--调用事务
    Rollback;
End;

上述pl/sql程序块内嵌套了一个dml的procedure过程。执行pl/sql程序块结束后发现test01有27条数据,也就说明了procedure中的commit完成了上述procedure和pl/sql程序块的提交。最后的rollback也就没有任何效果。

结论:非自治事务内的procedure中得commit或者rollback会影响前面所有dml的影响。

现在创建一个自治事务:

--Create Procedure的autonomous_transaction01
Create or replace procedure autonomous_transaction01 as pragma autonomous_transaction01;
Begin
    Insert into test01select * from test01 where rownum=1;
    Commit;
End;
--查询进行测试
Select count(*) from test01
--Count(*)|25

此时test01有25条数据。然后,在pl/sql程序块中调用自治事务autonomous_transaction01

Begin
Insert into test01 select * from test01 where rownum=1;
    Autonomous_transaction01;
    Rollback;--rollback并没有影响到自治事务内的操作
End;

自治事务内的commit只会影响自治事务内的没有提交的dml,调用自治事务完毕后又会回到调用自治事务的事务内,此时最后的rollback也只能回滚自治事务外的dml,所以此时的test01中还是只有25条数据。

自治事务与被调用事务完全独立,不能共享调用者事务使用的锁和其他资源,而且自治事务内还可以调用其他自治事务。自治事务与其调用者可能会发生死锁,oracle会检测此类死锁并返回错误信息。

文件操作

系统函数:

utl_file.fopen(三个参数:文件对象,文件名,打开方式)       打开文件
utl_file.put_line(文件对象,字符串)    输出文件
utl_file.get_line()    读取文件
utl_file.fclose()      关闭文件
utl_file.fflush()      强制输出缓冲

文件打开方式

也就是fopen()的第三个参数:

r -- read text
w -- write text
a -- append text
rb -- read byte mode
wb -- write byte mode
ab -- append byte mode
--如果指定'a'或者'ab'但是文件不存在会先创建。

举例:

CREATE OR REPLACE PROCEDURE FILE_TEMP IS
   FILEHANDLE UTL_FILE.FILE_TYPE;--定义文件对象
BEGIN
  --打开文件
  FILEHANDLE := UTL_FILE.fopen('FILENAME' ,'FILE_TEMP.TXT' ,'W' );
  --写入文件
  UTL_FILE.put_line(FILEHANDLE,'这是一个练习和测试');
  FOR TEMP_1 IN (SELECT * FROM EMPLOYEES) LOOP
   --写入文件
   UTL_FILE.put_line(FILEHANDLE,TEMP_1.LAST_NAME || '|' || TEMP_1.SALARY);
  END LOOP;
  UTL_FILE.FCLOSE(FILEHANDLE);--关闭文件
END;

引用游标

create or replace procedure test_ref1
  is
   type s_table is ref cursor;
   f  s_table;
   f_rec t1% ;
   stmt varchar2(100) := 'select empno,ename from emp';
begin
   insert into t1 select empno,ename,sal from emp;
   open f for select * from t1;
   loop
       fetch f into f_rec;
       dbms_output.put_line(f_rec.empno ||'  '||f_rec.ename);
       exit when f%rowcount=3 ;
   end loop;
   close f;
end;
/

上课记录

  • begin ...end; 为一个程序块
  • 匿名块不能被调用,因为没有名字,一般用于测试,可以直接执行
  • 存储过程和函数相比,没有返回值,两者都需要编译后进行调用,才可以执行。
  • 返回值和参数定义不需要指定长度,定义变量需要指定长度

你可能感兴趣的:(PL/SQL笔记)