PL/SQL 基础---一些高阶用法

本文探讨了PL/SQL的一些高级用法,包括动态SQL的执行、参数声明中使用NOCOPY的性能优化、DML操作的RETURNING…INTO语法、AUTHID在权限管理中的应用、带默认值的参数传递、事务处理的细节以及Oracle的自治事务。这些技巧能帮助开发者更高效地使用PL/SQL。

PL/SQL 基础—一些高阶用法

主要讨论PL/SQL中一些不常见的用法;

1.动态SQL

有的时候我们希望游标对应的SQL是可以改变的,而不是在游标声明中指定,这时需要使用游标变量

TYPE REF_CURSOR IS REF CURSOR;
l_cursor REF_CURSOR;

...
sql_str := '.....:1,:2';

OPEN l_cursor FOR sql_str 
    USING parameter1, parameter2;



DECLARE
  TYPE ref_cursor IS REF CURSOR;
  l_cur         ref_cursor;
  l_employee_id NUMBER(10);
  l_dync_sql    VARCHAR(100);
  l_name        VARCHAR(30);

BEGIN
  l_employee_id := 150;
  l_dync_sql    := 'SELECT e.LAST_NAME FROM EMPLOYEES e WHERE e.EMPLOYEE_ID > :1';
  OPEN l_cur FOR l_dync_sql
    USING l_employee_id;

  LOOP
    FETCH l_cur
      INTO l_name;
    EXIT WHEN l_cur%NOTFOUND;
    dbms_output.put_line(l_name);
  END LOOP;

END;

除此之外还可以通过EXECUTE IMMEDIATE或DBMS_SQL包来完成SQL语句的动态执行;


-- EXECUTE IMMEDIATE 带参数和输出

DECLARE
  l_name        VARCHAR(30);
  l_employee_id NUMBER(10);
  l_dync_sql    VARCHAR(100);

BEGIN
  l_employee_id := 100;
  l_dync_sql    := 'SELECT e.LAST_NAME FROM EMPLOYEES e WHERE e.EMPLOYEE_ID = :1';
  EXECUTE IMMEDIATE l_dync_sql
    INTO l_name
    USING l_employee_id;

  dbms_output.put_line(l_name);

END;


-- DBMS_SQL包

DECLARE
  l_name        VARCHAR2(30);
  l_employee_id NUMBER(10);
  l_dync_sql    VARCHAR2(100);
  l_cursor      NUMBER;
  l_row         NUMBER;

BEGIN
  l_employee_id := 100;
  l_dync_sql    := 'SELECT e.LAST_NAME  FROM EMPLOYEES e WHERE e.EMPLOYEE_ID > :2';

  l_cursor := dbms_sql.open_cursor;

  dbms_sql.parse(l_cursor, l_dync_sql, dbms_sql.native);

  dbms_sql.define_column(l_cursor, 1, l_name, 30);
  dbms_sql.bind_variable(l_cursor, ':2', l_employee_id);

  l_row := dbms_sql.execute(l_cursor);
  IF dbms_sql.fetch_rows(l_cursor) > 0 THEN
    dbms_sql.column_value(l_cursor, 1, l_name);
    IF dbms_sql.is_open(l_cursor) THEN
      dbms_sql.close_cursor(l_cursor);
    END IF;
  END IF;

  dbms_output.put_line(l_name);

END;
2.参数声明中使用NOCOPY

NOCOPY 是一个可选的标示 hint,用来告诉 PLSQL 编译器传递的是变量的引用,而
不是变量真实的值。 NOCOPY 都是用在具有 OUT 或 IN OUT 参数的存储过程中, 使
用 NOCOPY 可以获取更好的程序性能,但是如果在没有很好处理异常的程序中使用
的话也是会有一定的问题需要注意。

在这里我们需要弄清楚一个概念, 就是在声明 FUNCTION 和
PROCEDURE 的时候所定义的参数称为形式参数, 应用程序在调用的时候传递的参数
称为实际参数, 实际参数和形式参数时间的数据传递只有两种方式,传址法和传值法。
传址法就是在调用函数或者存储过程的时候, 将实际参数的地址指针传递给形式参数,
使得形式参数和实际参数指向内存中的同一个区域,从而实现参数数据的传递。
传值法就是在调用函数或者存储过程的时候, 将实际参数的值拷贝给形式参数, 而不
是通过实际参数的地址。 **默认的情况是, OUT 和 IN OUT 参数都是采用传值法, 在调
用的时候将实际参数数据拷贝到 OUT 和 IN OUT 参数中, 在当程序正常运行并且退出
的时候, 又将 OUT 和 IN OUT 形式参数数据拷贝到实际参数变量中**。

--NOCOPY
CREATE OR REPLACE PACKAGE BODY cux_plsql_test IS
  test_exception EXCEPTION; --自定义异常
  --定义存储过程,未使用 NOCOPY 标示形式参数
  PROCEDURE copy_parameter(p_number IN OUT NUMBER) IS
  BEGIN
    p_number := 10000;
    RAISE test_exception; --抛出异常,未进行处理
  END copy_parameter;
  --定义存储过程,使用 NOCOPY 标示形式参数
  PROCEDURE nocopy_parameter(p_number IN OUT NOCOPY NUMBER) IS
  BEGIN
    p_number := 10000;
    RAISE test_exception; --抛出异常,未进行处理HAND 技术顾问培训
  END nocopy_parameter;
  PROCEDURE test IS
    l_number NUMBER;
  BEGIN
    l_number := 20000; --赋值实际参数
    copy_parameter(p_number => l_number); --实际参数传递给形式参数,这种方式为传值法
  EXCEPTION
    WHEN test_exception THEN
      dbms_output.put_line('copy_parameter: ' || l_number);
      BEGIN
        dbms_output.new_line;
        l_number := 20000; --重新赋值实际参数
        nocopy_parameter(l_number); --实际参数传递给形式参数,这种方式为传址法
      EXCEPTION
        WHEN test_exception THEN
          dbms_output.put_line('nocopy_parameter: ' || l_number);
      END;
  END test;
END cux_plsql_test;


--执行 test 结果如下
copy_parameter: 20000
nocopy_parameter: 10000

实际使用NOCOPY需要特别注意,因为在程序处理过程中很有可能遇到异常,而此时的变量值却发生了改变,那这个值是属于错误值;

3.除了SELECT…INTO…还有RETURNING…INTO…

前面介绍了SELECT…INTO…用于将查询结果写入到变量中,其实对于DML语句(DELETE、UPDATE、INSERT)同样有类似的语句;

-- RETURNING...INTO...

DECLARE
   l_new_name VARCHAR(30);

BEGIN
  DELETE FROM EMPLOYEES e WHERE e.EMPLOYEE_ID = 100
  RETURNING e.LAST_NAME INTO l_new_name;
  dbms_output.put_line(l_new_name);
  ROLLBACK;

  UPDATE EMPLOYEES e SET e.LAST_NAME = 'UPDATE_NAME' WHERE e.EMPLOYEE_ID = 100
  RETURNING e.LAST_NAME INTO l_new_name;
  dbms_output.put_line(l_new_name);
  ROLLBACK;

  INSERT INTO EMPLOYEES e VALUES(999,'NEW_NAME','INSERT_NAME','EMAIL','PHONENUM',SYSDATE,'AD_VP',1000,0,100,90)
  RETURNING e.LAST_NAME INTO l_new_name;
  dbms_output.put_line(l_new_name);
  ROLLBACK; 
END;

--结果是:
King
UPDATE_NAME
INSERT_NAME

之所以会有这样的语句,是英文PLSQL对数据的以上操作都是先将数据读取到内存中,处理完成后再写入(同步)到数据库中;

4.AUTHID PLSQL程序执行过程的权限问题

AUTHID 子句用来指示 Oracle 服务器在运行程序的时候是以调用者的用户权限
(invoker’s rights),还是以创建者的用户权限(Owner rights)。使用 AUTHID 子句就有
两种形式:
AUTHID CURRENT_USER 指示程序运行的时候以调用者的用户权限执行;
AUTHID DEFINER 指示程序运行的时候以创建者的用户权限执行。
默认的情况是 AUTHID DEFINER

---SYS用户创建
CREATE OR REPLACE PROCEDURE query_db_character AUTHID CURRENT_USER IS
       l_character_name VARCHAR2(30);
BEGIN
  SELECT p.value$  INTO l_character_name FROM props$ p WHERE p.name = 'NLS_CHARACTERSET';
  dbms_output.put_line(l_character_name);
END;

grant execute on query_db_character to 用户名;

--一般用户调用
BEGIN
  SYS.query_db_character();
END;

--提示:表或视图不存在

--使用AUTHID DEFINER则出现正常结果;


4.带默认值的参数传递

在函数或者存储过程中使用参数默认值,使用规则是附无默认值的参数,然后依次赋值有默认值参数;

CREATE OR REPLACE PROCEDURE PRO__TEST_P1(v1 NUMBER DEFAULT 3000, v2 NUMBER, v3 NUMBER DEFAULT 100 ,  c1 VARCHAR2 DEFAULT 'NAME') IS
BEGIN
  dbms_output.put_line(c1);
  dbms_output.put_line(GREATEST(v1,v2,v3));
END;


5.PLSQL中事务处理

定义

SET TRANSACTION {{
    READ {ONLY | WRITE}  --事务类型
    | ISOLATION LEVEL  {SERIALIZABLE | READ COMMITTED} --设置事务的隔离级别
    | USE ROLLBACK SEGMENT rollback_segment}   -- 针对当前事务指定合适的回滚段
    [NAME 'test']| NAME 'test'}     --事务名

事务类型: READ ONLY/READ WRITE

READ ONLY:事务内部不能执行DML操作,且读取到的数据为开始事务时刻的数据(即其他事务(SESSION)提交的修改对此事务没有影响);

--- SESSION 1 ---------
BEGIN
COMMIT;
SET TRANSACTION READ ONLY;
END;

SELECT LAST_NAME   FROM EMPLOYEES WHERE EMPLOYEE_ID = 100;

--- SESSION 2 --------
UPDATE EMPLOYEES e SET e.LAST_NAME = 'NEW——NAME' WHERE e.EMPLOYEE_ID = 100;
COMMIT;

--- SESSION 1 --------
SELECT LAST_NAME   FROM EMPLOYEES WHERE EMPLOYEE_ID = 100;
---查询结果不变

READ WRITE :事务内部可以执行DML操作,且读取到的数据为最新的数据(提交的更改能够查询),此事务内做的修改需要提交才能被其他事务查询;

--- SESSION --------- 
BEGIN
COMMIT;
SET TRANSACTION READ WRITE;
END;


--- SESSION 2 --------
UPDATE EMPLOYEES e SET e.LAST_NAME = 'NEW——NAME' WHERE e.EMPLOYEE_ID = 100;
COMMIT;


--- SESSION 1 --------
SELECT LAST_NAME   FROM EMPLOYEES WHERE EMPLOYEE_ID = 100;
---查询结果改变

UPDATE EMPLOYEES SET LAST_NAME = 'CHANGED' WHERE EMPLOYEE_ID = 100;

--- SESSION 2 --------
SELECT LAST_NAME   FROM EMPLOYEES WHERE EMPLOYEE_ID = 100;
---查询结果不变

--- SESSION 1 --------
COMMIT;

--- SESSION 2 --------
SELECT LAST_NAME   FROM EMPLOYEES WHERE EMPLOYEE_ID = 100;
---查询结果改变


>注:
>PLSQL默认事务为Read Write
>调用 SET TRANSACTION XXX 时, 在前面加COMMIT,保证他在事务的第一条语句;

事务隔离级别
如果未指定事务类型,可以通过设置事务隔离级别来对解决数据访问冲突,大概有四种类型:
在 Oracle 的事务处理中只允许使用两个隔离级别 SERIALIZABLE 和 READ COMMITTED

未提交读(read uncommitted)
提交读(read committed)
重复读(repeatable read)
序列化(serializable)

SQL92定义了的这四种事务隔离级别(transaction isolation level),主要是为了在并发事务执行时阻止下列现象发生:
脏读:事务读取了被其他事务写入但未提交的数据。

不可重复读:一个事务再次读取其之前曾经读取过的数据时,发现数据已被其他已提交的事务修改或删除。

幻象读(phantom read):事务按照之前的条件重新查询时,返回的结果集中包含其他已提交事务插入的满足条件的新数据。

Oracle事务隔离级别脏读不可重复读幻象读DML操作
READ COMMITTED不允许允许允许允许
SERIALIZABLE不允许不允许不允许允许
Read Write不允许允许允许允许
Read only不允许不允许不允许不允许
6.Oracle的自治事务

Oracle 的自治事务(AUTONOMOUS TRANSACTION)简称 AT,自治事务在程序的开
发过程中是个很有用的功能, 它是有主事务调用,但是又独立于主事务。即,内部事务(代码块)的事务提交和回滚不影响主事务;

可以定义自治事务的对象:PLSQL 程序块、程序包函数和存储过程、SQL Object 的方法、数据库触发器

CREATE OR REPLACE PACKAGE  transaction_test_pkg  IS
       PROCEDURE sub_transaction;
       PROCEDURE main_transaction;
END;

CREATE OR REPLACE PACKAGE BODY transaction_test_pkg IS
  PROCEDURE sub_transaction IS
    PRAGMA AUTONOMOUS_TRANSACTION; --声明自治事务
  BEGIN
    --DML 语句
    UPDATE EMPLOYEES SET LAST_NAME = 'INNER' WHERE EMPLOYEE_ID = 101;   -- 防止死锁
    COMMIT;  -- 必须COMMIT,否则报错
  END sub_transaction;
  PROCEDURE main_transaction IS
  BEGIN
    --DML 语句
    UPDATE EMPLOYEES SET LAST_NAME = 'OUTER' WHERE EMPLOYEE_ID = 100; 
    sub_transaction;
    ROLLBACK;
  END main_transaction;
END transaction_test_pkg;


--执行
BEGIN
  transaction_test_pkg.main_transaction;
END;


SELECT LAST_NAME   FROM EMPLOYEES WHERE EMPLOYEE_ID IN (100,101);

--结果:INNER写入到数据库

附:Oracle中DATE与MySQL时间类型的区别

MySQL

类型格式范围备注
datetimeYYYY-MM-DD HH:MM:SS1000-01-01 00:00:00 ~ 9999-12-31 23:59:59-
timestampYYYY-MM-DD HH:MM:SS1970-01-01 00:00:01 ~ 2038默认情况下,在 insert, update 数据时,timestamp 列会自动以当前时间
dateYYYY-MM-DD1000-01-01~ 9999-12-31-
yearYYYY1901 ~ 2155-

ORACLE

类型格式范围备注
timestampYYYY-MM-DD HH:MM:SS1970-01-01 00:00:01 ~ 2038不会自动写入系统时间
dateYYYY-MM-DD1000-01-01~ 9999-12-31不指定日期直接写入,则为服务器时间

TIMESTAMP和DATE可实现隐式转换

如果DATE类型不保存时间,默认是00:00:00, 具体需不需要时间需要程序主动获取,返回的结果中是包含时间的;

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值