ORACLE Procedure & Package & PL/SQL

TABLE of contents

  1. oracle PL 专栏
  2. PL/SQL 相关
    1. PLSQL Developer解决中文乱码问题
    2. 存储过程
    3. oralce 获取异常的栈信息
  3. 存储过程案例
    1. 缓存Map数据
    2. 归档案例

oracle PL 专栏

Oracle 在线学习:https://livesql.oracle.com

PL/SQL 相关

PLSQL Developer解决中文乱码问题

1.查服务端字符集编码

SELECT userenv('language') FROM dual;

结果

    userenv('language')
1	SIMPLIFIED CHINESE_CHINA.AL32UTF8

2.执行语句 SELECT * FROM V$NLS_PARAMETERS 查看第一行中PARAMETER项中为NLS_LANGUAGE 对应的VALUE项中是否和第一步得到的值一样。

SELECT * FROM V$NLS_PARAMETERS 

结果

    PARAMETER              VALUE               CON_IN  
1	NLS_LANGUAGE 	       SIMPLIFIED CHINESE	0
2	NLS_TERRITORY	       CHINA	            0
3	NLS_CURRENCY	       ¥	               0
4	NLS_ISO_CURRENCY	       CHINA	        0
5	NLS_NUMERIC_CHARACTERS	   .,	            0
6	NLS_CALENDAR	       GREGORIAN	        0
7	NLS_DATE_FORMAT	       DD-MON-RR	        0
8	NLS_DATE_LANGUAGE	   SIMPLIFIED CHINESE	0
9	NLS_CHARACTERSET	   AL32UTF8         	0
10	NLS_SORT	           BINARY	            0
11	NLS_TIME_FORMAT	       HH.MI.SSXFF AM	    0
12	NLS_TIMESTAMP_FORMAT	DD-MON-RR HH.MI.SSXFF AM	    0
13	NLS_TIME_TZ_FORMAT	    HH.MI.SSXFF AM TZR	            0
14	NLS_TIMESTAMP_TZ_FORMAT	DD-MON-RR HH.MI.SSXFF AM TZR	0
15	NLS_DUAL_CURRENCY	    ¥	               0
16	NLS_NCHAR_CHARACTERSET	AL16UTF16	        0
17	NLS_COMP	            BINARY	            0
18	NLS_LENGTH_SEMANTICS	BYTE	            0
19	NLS_NCHAR_CONV_EXCP	    FALSE	            0

如果不是,需要设置环境变量.PLSQL客户端使用的编码和服务器端编码不一致,插入中文时就会出现乱码.

NLS_LANGUAGE_NLS_TERRITORY.NLS_CHARACTERSET

3.设置环境变量计算机->属性->高级系统设置->环境变量->新建

设置变量名:NLS_LANG, 变量值:SIMPLIFIED CHINESE_CHINA.AL32UTF8

4.重启PL/SQL

存储过程

-- 尚未测试
create or replace PROCEDURE ZZY(demo in ats_back_ti % rowtype) is
   v_back_ti  ats_back_ti % rowtype ;
begin
   v_back_ti := demo;
   v_back_ti.d
   dbms_output.put_line(v_back_ti.id);
   dbms_output.put_line(v_back_ti.TRANSCODE);
end 
-- 尚未测试
create or replace procedure test_dblink(out_cursor out int)  Authid Current_User  as

begin
      --  test db link 数据库 连接
     execute immediate 'create database link dblink1 
     connect to  用户名 identified by "密码"
     using ''192.168.1.100:1521/orcl'' ';

   --    open out_cursor for 'SELECT * FROM A@dblink1';
  

  -- 返回个数值
    out_cursor :=0;
    execute immediate 'SELECT count(*) FROM A@dblink1'  into out_cursor ;
    
    -- 取消连接
    execute immediate 'drop database link dblink1';
    
    return ;
    
end  ;

oralce 获取异常的栈信息

DECLARE
  excep   EXCEPTION;
  pragma exception_init(excep, -20001);
  v_count number(16,2);
BEGIN
  -- 你的代码逻辑,可能会引发异常
  select 1/0 into v_count from dual ;
  raise_application_error(-20001,'测试抛出异常');  
EXCEPTION
  WHEN excep THEN
    DBMS_OUTPUT.PUT_LINE('Exception Message: ' || SQLERRM);
    DBMS_OUTPUT.PUT_LINE('Exception Stack:');
    DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
END;
/

存储过程案例

缓存Map数据

数据量比较小的可以这样缓存,减少反复查询。

(1)基于 TYPE 使用 TABLE OF ... INDEX 模式的Map, 索引 key 和 值 value 绑定。

-- 索引key模式
DECLARE
  TYPE NAME_MAP IS TABLE OF VARCHAR2(100) INDEX BY VARCHAR2(10);
  NAMECACHE NAME_MAP;
  CODE VARCHAR2(10);
  NAME VARCHAR2(100);
BEGIN
  -- 查询表并缓存到关联数组中
  FOR REC IN (SELECT  T.UNITCODE,  T.UNITNAME  FROM  T_UNIT_TC T ) LOOP
    NAMECACHE(REC.UNITCODE) := REC.UNITNAME;
  END LOOP;

  -- 根据 CODE 直接获取缓存中的 NAME
  CODE := '001';
  NAME := NAMECACHE(CODE);
  DBMS_OUTPUT.PUT_LINE('名称: ' || NAME);
END;

(2)基于 TYPE 使用 RECORD 行记录模式的Map, 真的是一个Map 的实现。

DECLARE
  -- 定义一个类型,用于表示键和值
  TYPE pair IS RECORD (
    key VARCHAR2(100),
    value VARCHAR2(100)
  );

  -- 声明一个基于上述类型的嵌套表
  TYPE key_value_pairs IS TABLE OF pair;

  -- 实例化嵌套表
  my_map key_value_pairs := key_value_pairs();

  -- 添加键值对
  PROCEDURE put(key VARCHAR2, value VARCHAR2) IS
  BEGIN
    my_map.EXTEND;
    my_map(my_map.LAST).key := key;
    my_map(my_map.LAST).value := value;
  END;

  -- 获取键对应的值, 这种需要循环查找
  FUNCTION get(key VARCHAR2) RETURN VARCHAR2 IS
    value VARCHAR2(100);
  BEGIN
    FOR i IN 1..my_map.COUNT LOOP
      IF my_map(i).key = key THEN
        value := my_map(i).value;
        EXIT;
      END IF;
    END LOOP;
    RETURN value;
  END;
BEGIN
  -- 添加数据到映射
  put('name', 'John Doe');
  put('age', '30');

  -- 获取映射中的数据
  DBMS_OUTPUT.PUT_LINE('Name: ' || get('name'));
  DBMS_OUTPUT.PUT_LINE('Age: ' || get('age'));
END;

归档案例

CREATE OR REPLACE PACKAGE AMS_DATA_ARCHIVE_PKG AS
    -- AMS_APPLICATIONS 持续归档
    PROCEDURE DO_ARCHIVE_APPLICATIONS(V_SUBCOMPANY IN VARCHAR2);
END AMS_DATA_ARCHIVE_PKG;
/
CREATE OR REPLACE PACKAGE BODY AMS_DATA_ARCHIVE_PKG IS
    PROCEDURE DO_ARCHIVE_APPLICATIONS(V_SUBCOMPANY IN VARCHAR2) IS
        V_SUB_OPDATE    DATE;           --对应分公司的可归档最小日期
        V_OPDATE        DATE;           --可归档数据的归档最小日期
        V_SQL           VARCHAR2(1000);
        V_SUB_SQL       VARCHAR2(1000);
        V_COUNT_SQL     VARCHAR2(2000); --统计分公司最小归档日的数据量
        V_TMP_SQL       VARCHAR2(2000);
        V_ARCHIVE_COUNT NUMBER ;
        V_BATCH_SIZE    NUMBER    := 10000;
        V_SIZE          NUMBER ;        --单日总次数
        V_DO_COUNT      NUMBER ;        --执行次数累计
        V_GUID          VARCHAR2(200);  --并发区分
        V_COND          VARCHAR2(100);
        V_START_TIME    TIMESTAMP := SYSTIMESTAMP;--开始时间
        V_CURRENT_TIME  TIMESTAMP;      --当前时间
        V_TIME_EXCEEDED BOOLEAN   := FALSE;

    BEGIN
        --取三年前可归档区间的分公司最小日期
        V_SUB_SQL := 'SELECT MIN(OPDATE) FROM AMS_APPLICATIONS WHERE SUBCOMPANY = :SUBCOMPANY AND STATUS IN (''C'', ''G'') AND OPDATE <= ADD_MONTHS(SYSDATE, -36)';
        --取可归档区间最小日期
        V_SQL := 'SELECT MIN(OPDATE) FROM AMS_APPLICATIONS WHERE STATUS IN (''C'', ''G'') AND OPDATE <= ADD_MONTHS(SYSDATE, -36) ';
        --获取最小OPDATE
        --DBMS_OUTPUT.PUT_LINE('V_SQL : ' || V_SQL );
        EXECUTE IMMEDIATE V_SUB_SQL INTO V_SUB_OPDATE USING V_SUBCOMPANY;
        EXECUTE IMMEDIATE V_SQL INTO V_OPDATE;
        IF V_SUB_OPDATE IS NULL OR V_OPDATE IS NULL THEN
            RETURN;
        END IF;
        IF V_SUB_OPDATE > V_OPDATE THEN
            --防止一直归一家分公司,强制最早时间优先归档
            RETURN;
        END IF;

        --归档基本条件[分公司 SUBCOMPANY,数据状态 STATUS,日期 OPDATE ]
        V_COND := ' WHERE SUBCOMPANY = :SUBCOMPANY AND OPDATE = :OPDATE AND STATUS IN (''C'',''G'') ';
        V_COUNT_SQL := ' SELECT COUNT(1) FROM AMS_APPLICATIONS ' || V_COND;
        EXECUTE IMMEDIATE V_COUNT_SQL INTO V_ARCHIVE_COUNT USING V_SUBCOMPANY, V_SUB_OPDATE;
        --获取最小OPDATE +分公司+数据状态 的数据量
        IF V_ARCHIVE_COUNT = 0 THEN
            RETURN;
        END IF;

        --计算本次归档次数,分页归档
        V_SIZE := CEIL(V_ARCHIVE_COUNT / V_BATCH_SIZE);
        V_DO_COUNT := 0;
        WHILE (V_DO_COUNT < V_SIZE AND V_TIME_EXCEEDED = FALSE)
            LOOP
                --检查执行时间
                V_CURRENT_TIME := SYSTIMESTAMP;
                IF EXTRACT(MINUTE FROM (V_CURRENT_TIME - V_START_TIME)) +
                   EXTRACT(HOUR FROM (V_CURRENT_TIME - V_START_TIME))*60 > 30 THEN
                    --检查是否超过 30分钟
                    V_TIME_EXCEEDED := TRUE; -- 设置超时标志
                    EXIT; --退出循环
                END IF;

                V_GUID := V_SUBCOMPANY || SYS_GUID() || TO_CHAR(V_SUB_OPDATE, 'YYYY-MM-DD');
                -- APPLYNO, SUBCOMPANY 是主键
                V_TMP_SQL := 'INSERT INTO AMS_APPLICATIONS_IDX ( APPLYNO, SUBCOMPANY, GUID ) '
                    || ' SELECT APPLYNO, SUBCOMPANY,' || V_GUID || 'FROM AMS_APPLICATIONS '
                    || V_COND 
                    || ' AND ROWNUM <= ' || V_BATCH_SIZE ||' ORDER BY APPLYNO  ';

                --插入AMS_APPLICATIONS_IDX 索引表
                --DBMS_OUTPUT.PUT_LINE('V_TMP_SQL : ' || V_TMP_SQL );
                EXECUTE IMMEDIATE V_TMP_SQL USING V_SUBCOMPANY, V_SUB_OPDATE;

                V_TMP_SQL := 'INSERT INTO AMS_APPLICATIONS_BAK SELECT * FROM AMS_APPLICATIONS ' 
                    || V_COND
                    || ' AND ( APPLYNO, SUBCOMPANY ) IN (SELECT APPLYNO, SUBCOMPANY FROM AMS_APPLICATIONS_IDX WHERE GUID = :GUID )';
                --插入BAK 归档表
                EXECUTE IMMEDIATE V_TMP_SQL USING V_SUBCOMPANY, V_SUB_OPDATE, V_GUID;

                
                V_TMP_SQL := 'DELETE FROM AMS_APPLICATIONS WHERE ( APPLYNO, SUBCOMPANY ) IN (SELECT APPLYNO, SUBCOMPANY FROM AMS_APPLICATIONS_IDX WHERE GUID = :GUID )' ;
                --从主表中删除数据
                EXECUTE IMMEDIATE V_TMP_SQL USING V_GUID;

                --DBMS_OUTPUT.PUT_LINE('V_TMP_SQL : ' || V_TMP_SQL);
                --清空本次执行临时表
                V_TMP_SQL := 'DELETE FROM AMS_APPLICATIONS_IDX WHERE GUID = :GUID ';
                -- DBMS_OUTPUT.PUT_LINE('V_TMP_SQL :' || V_TMP_SQL );
                EXECUTE IMMEDIATE V_TMP_SQL USING V_GUID;
                COMMIT;
                V_DO_COUNT := V_DO_COUNT + 1;
            END LOOP;
    EXCEPTION
        WHEN OTHERS THEN
            ROLLBACK;
            AMS_ERRORLOG_PKG.LOG_ERROR(PROCNAME_IN => 'AMS_DATA_ARCHIVE_PKG',
                                       KEYWORD1_IN => 'DO_ARCHIVE_APPLICATIONS',
                                       KEYWORD3_IN => TO_CHAR(V_OPDATE, 'YYYY-MM-DD'),
                                       INFO_IN => 'V_GUID =' || V_GUID || SUBSTR(SQLERRM, 1, 2000));

    
    END DO_ARCHIVE_APPLICATIONS;

END AMS_DATA_ARCHIVE_PKG;