在 Oracle 数据库开发中,经常会遇到诸如行转列、单行变多行等需求,熟练使用 Oracle 自带的函数,实现这些功能会变得得心应手。

单行变多行

源字符串: A001/D/200198

SQL> SELECT 
    ROWNUM RN, 
    REGEXP_SUBSTR('A001/D/200198', '[^/]+', 1, ROWNUM) PARAM FROM DUAL 
CONNECT BY ROWNUM <= LENGTH('A001/D/200198') - LENGTH(REPLACE('A001/D/200198', '/', '')) + 1;   

        RN PARAM
---------- --------------------------
         1 A001
         2 D
         3 200198


SQL> CREATE TABLE T_CLOB(
  ID NUMBER PRIMARY KEY,
  C_CLOB CLOB
);

SQL> INSERT INTO T_CLOB(ID, C_CLOB) VALUES(1, 'A001/D/200198');
SQL> COMMIT;

SQL> SELECT
  ROWNUM,  
  ID,
  TO_CHAR(REGEXP_SUBSTR(C_CLOB, '[^/]+', 1, LEVEL)) AS PARAM
FROM (
  SELECT 
    ID, C_CLOB
  FROM T_CLOB
) T                        
CONNECT BY PRIOR ID = ID
AND LEVEL <= REGEXP_COUNT(C_CLOB, '[^/]+')
AND PRIOR DBMS_RANDOM.VALUE() IS NOT NULL;

    ROWNUM         ID PARAM
---------- ---------- --------------------
         1          1 A001
         2          1 D
         3          1 200198         


SQL> WITH T AS (
  SELECT 
    TO_CHAR(SYSDATE, 'YYYY-MM-DD')       C_DATE,
    '苹果, 橘子, 葡萄, 芒果, 荔枝, 香蕉' C_NAME
  FROM DUAL
)
SELECT 
  C_DATE,
  REGEXP_SUBSTR(C_NAME, '[^, ]+', 1, LEVEL) C_NAME,
  LEVEL
FROM T
CONNECT BY LEVEL <= REGEXP_COUNT(C_NAME, '[^, ]+')
AND PRIOR DBMS_RANDOM.VALUE IS NOT NULL;

C_DATE     C_NAME                                            LEVEL
---------- -------------------------------------------- ----------
2020-02-28 苹果                                                  1
2020-02-28 橘子                                                  2
2020-02-28 葡萄                                                  3
2020-02-28 芒果                                                  4
2020-02-28 荔枝                                                  5
2020-02-28 香蕉                                                  6

6 rows selected

拆分字符串为多行

SQL> SELECT 
  REGEXP_SUBSTR('a,b,c', '[^,]+', 1, 1) R1, 
  REGEXP_SUBSTR('a,b,c', '[^,]+', 1, 2) R2, 
  REGEXP_SUBSTR('a,b,c', '[^,]+', 1, 3) R3 
FROM DUAL;

R1 R2 R3
-- -- --
a  b  c

行转列

DECODE 行转列

SQL> WITH FRUIT AS (
  SELECT 1 ID, '苹果' NAME, 1000 NUMS FROM DUAL  
  UNION ALL
  SELECT 2 ID, '苹果' NAME, 2000 NUMS FROM DUAL  
  UNION ALL
  SELECT 3 ID, '橘子' NAME, 5000 NUMS FROM DUAL
  UNION ALL   
  SELECT 4 ID, '葡萄' NAME, 3500 NUMS FROM DUAL
  UNION ALL  
  SELECT 5 ID, '芒果' NAME, 4200 NUMS FROM DUAL
)
SELECT 
  MAX(DECODE(NAME, '苹果', NUMS)) AS APPLE,
  MAX(DECODE(NAME, '橘子', NUMS)) AS ORANGE,
  MAX(DECODE(NAME, '葡萄', NUMS)) AS GRAPE,
  MAX(DECODE(NAME, '芒果', NUMS)) AS MANGO
FROM FRUIT;  

     APPLE     ORANGE      GRAPE      MANGO
---------- ---------- ---------- ----------
      2000       5000       3500       4200

PIVOT 行转列

  • 语法
pivot(聚合函数() for 列名 in (列值))
  • 将 "PARAM" 字段,进行行转列
SQL> SELECT 
  VC_IND_CODE, 
  VC_IND_SUBCODE, 
  VC_FUNDCODE 
FROM (
  SELECT 
    ROWNUM RN, 
    REGEXP_SUBSTR('A001/D/200198', '[^/]+', 1, ROWNUM) PARAM
  FROM DUAL
  CONNECT BY ROWNUM <= LENGTH('A001/D/200198') - LENGTH(REPLACE('A001/D/200198', '/', '')) + 1
) T PIVOT (MAX(PARAM) FOR RN IN (1 AS VC_IND_CODE, 2 AS VC_IND_SUBCODE, 3 AS VC_FUNDCODE)); 

VC_IND_CODE          VC_IND_SUBCODE       VC_FUNDCODE
-------------------- -------------------- ------------------
A001                 D                    200198
  • 将 "NAME" 字段,进行行转列
SQL> WITH FRUIT AS (
  SELECT 1 ID, '苹果' NAME, 1000 NUMS FROM DUAL  
  UNION ALL
  SELECT 2 ID, '苹果' NAME, 2000 NUMS FROM DUAL  
  UNION ALL
  SELECT 3 ID, '橘子' NAME, 5000 NUMS FROM DUAL
  UNION ALL   
  SELECT 4 ID, '葡萄' NAME, 3500 NUMS FROM DUAL
  UNION ALL  
  SELECT 5 ID, '芒果' NAME, 4200 NUMS FROM DUAL
)
SELECT * FROM FRUIT;  

        ID NAME       NUMS
---------- ---- ----------
         1 苹果       1000
         2 苹果       2000
         3 橘子       5000
         4 葡萄       3500
         5 芒果       4200
SQL> WITH FRUIT AS (
  SELECT 1 ID, '苹果' NAME, 1000 NUMS FROM DUAL  
  UNION ALL
  SELECT 2 ID, '苹果' NAME, 2000 NUMS FROM DUAL  
  UNION ALL
  SELECT 3 ID, '橘子' NAME, 5000 NUMS FROM DUAL
  UNION ALL   
  SELECT 4 ID, '葡萄' NAME, 3500 NUMS FROM DUAL
  UNION ALL  
  SELECT 5 ID, '芒果' NAME, 4200 NUMS FROM DUAL
)
SELECT * FROM (SELECT NAME, NUMS FROM FRUIT) PIVOT (SUM(NUMS) 
FOR NAME IN ('苹果' AS APPLE, '橘子' ORANGE, '葡萄' GRAPE, '芒果' MANGO)); 
    APPLE     ORANGE      GRAPE      MANGO
--------- ---------- ---------- ----------
     3000       5000       3500       4200

UNPOVIOT 列转行

SQL> WITH FRUIT AS (
  SELECT 1 ID, '苹果' NAME, 1000 Q1, 2000 Q2, 3300 Q3, 5000 Q4 FROM DUAL
  UNION ALL
  SELECT 2 ID, '橘子' NAME, 3000 Q1, 3000 Q2, 3200 Q3, 1500 Q4 FROM DUAL
  UNION ALL
  SELECT 3 ID, '香蕉' NAME, 2500 Q1, 3500 Q2, 2200 Q3, 2500 Q4 FROM DUAL
  UNION ALL
  SELECT 4 ID, '葡萄' NAME, 1500 Q1, 2500 Q2, 1200 Q3, 3500 Q4 FROM DUAL
)
SELECT * FROM FRUIT;

        ID NAME         Q1         Q2         Q3         Q4
---------- ---- ---------- ---------- ---------- ----------
         1 苹果       1000       2000       3300       5000
         2 橘子       3000       3000       3200       1500
         3 香蕉       2500       3500       2200       2500
         4 葡萄       1500       2500       1200       3500
SQL> WITH FRUIT AS (
  SELECT 1 ID, '苹果' NAME, 1000 Q1, 2000 Q2, 3300 Q3, 5000 Q4 FROM DUAL
  UNION ALL
  SELECT 2 ID, '橘子' NAME, 3000 Q1, 3000 Q2, 3200 Q3, 1500 Q4 FROM DUAL
  UNION ALL
  SELECT 3 ID, '香蕉' NAME, 2500 Q1, 3500 Q2, 2200 Q3, 2500 Q4 FROM DUAL
  UNION ALL
  SELECT 4 ID, '葡萄' NAME, 1500 Q1, 2500 Q2, 1200 Q3, 3500 Q4 FROM DUAL
)
SELECT ID , NAME, QUARTER, SALES FROM FRUIT UNPIVOT (SALES FOR QUARTER IN (Q1, Q2, Q3, Q4));  

        ID NAME QU      SALES
---------- ---- -- ----------
         1 苹果 Q1       1000
         1 苹果 Q2       2000
         1 苹果 Q3       3300
         1 苹果 Q4       5000
         2 橘子 Q1       3000
         2 橘子 Q2       3000
         2 橘子 Q3       3200
         2 橘子 Q4       1500
         3 香蕉 Q1       2500
         3 香蕉 Q2       3500
         3 香蕉 Q3       2200
         3 香蕉 Q4       2500
         4 葡萄 Q1       1500
         4 葡萄 Q2       2500
         4 葡萄 Q3       1200
         4 葡萄 Q4       3500

层次查询

自上而下检索

select * from table_name start with condition connect by prior id = parent_id;
SQL> SELECT 
  EMPNO, ENAME, JOB, MGR, SAL, DEPTNO, LEVEL
FROM EMP
  START WITH EMPNO = 7698   --根节点
  CONNECT BY PRIOR EMPNO = MGR
ORDER BY LEVEL, SAL;

     EMPNO ENAME      JOB              MGR        SAL     DEPTNO      LEVEL
---------- ---------- --------- ---------- ---------- ---------- ----------
      7698 BLAKE      MANAGER         7839       2850         30          1
      7900 JAMES      CLERK           7698        950         30          2
      7521 WARD       SALESMAN        7698       1250         30          2
      7654 MARTIN     SALESMAN        7698       1250         30          2
      7844 TURNER     SALESMAN        7698       1500         30          2
      7499 ALLEN      SALESMAN        7698       1600         30          2

自下而上检索

select * from table_name start with condition connect by id = prior parent_id;
SQL> SELECT 
  EMPNO, ENAME, JOB, MGR, SAL, DEPTNO, LEVEL
FROM EMP 
  START WITH EMPNO = 7698 
  CONNECT BY EMPNO = PRIOR MGR
ORDER BY LEVEL, SAL;

     EMPNO ENAME      JOB              MGR        SAL     DEPTNO      LEVEL
---------- ---------- --------- ---------- ---------- ---------- ----------
      7698 BLAKE      MANAGER         7839       2850         30          1
      7839 KING       PRESIDENT                  5000         10          2

存储过程游标输出参数

--声明存储过程
CREATE OR REPLACE PROCEDURE P_INDEX_PROFIT_RUN(I_INDCODE      IN  VARCHAR2,
                                               I_STARTDATE    IN  VARCHAR2,
                                               I_ENDDATE      IN  VARCHAR2,
                                               O_ERRCODE      OUT NUMBER,
                                               O_ERRMSG       OUT VARCHAR2,
                                               O_RETURN_VALUE OUT SYS_REFCURSOR) IS

--将数据写入游标变量
OPEN O_RETURN_VALUE FOR
SELECT 
  TO_DATE(TO_CHAR(SYSDATE, 'YYYY-MM-DD'), 'YYYY-MM-DD') D_DATE,
FROM DUAL; 

存储过程游标输出参数的调用

  • 创建存储过程,输出参数为游标
CREATE OR REPLACE PROCEDURE P_EMP_CURSOR(I_EMPNO   IN  VARCHAR2,
                                         O_RETURN  OUT SYS_REFCURSOR) IS
  /****************************************************************
  * 名     称:P_EMP_CURSOR
  * 功能 描述:存储过程游标输出参数
  *****************************************************************/
  V_EMPNO  VARCHAR2(100);
  V_CNT    NUMBER;
BEGIN
  EXECUTE IMMEDIATE 'ALTER SESSION SET NLS_DATE_FORMAT=''YYYY-MM-DD''';
  V_EMPNO  := I_EMPNO;

  EXECUTE IMMEDIATE 'TRUNCATE TABLE EMP_C';

  SELECT
    COUNT(1) INTO V_CNT
  FROM EMP WHERE EMPNO = V_EMPNO AND ROWNUM = 1;

  IF V_CNT = 1 THEN

    INSERT INTO EMP_C SELECT * FROM EMP WHERE EMPNO = V_EMPNO;

  END IF;

  COMMIT;

  OPEN O_RETURN FOR
  SELECT
    EMPNO,
    ENAME,
    JOB,
    MGR,
    HIREDATE,
    SAL,
    COMM,
    DEPTNO
  FROM EMP_C;

EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;

END P_EMP_CURSOR;
  • 调用
CREATE OR REPLACE PROCEDURE P_EMP_CURSOR_REF(I_STARTDATE IN  VARCHAR2,
                                             I_ENDDATE   IN  VARCHAR2,
                                             O_ERRCODE   OUT INTEGER,
                                             O_ERRMSG    OUT VARCHAR2) IS
  /****************************************************************
  * 名     称:P_EMP_CURSOR_REF
  * 功能 描述:存储过程游标输出参数的调用
  *****************************************************************/
  V_STARTDATE DATE;           -- 开始日期
  V_ENDDATE   DATE;           -- 结束日期
  V_EMPNO     NUMBER;
  V_CURSOR    SYS_REFCURSOR;
  V_TABLE     EMP_REF%ROWTYPE;
BEGIN
  EXECUTE IMMEDIATE 'ALTER SESSION SET NLS_DATE_FORMAT=''YYYY-MM-DD''';
  V_STARTDATE := TO_DATE(I_STARTDATE, 'YYYY-MM-DD');
  V_ENDDATE   := TO_DATE(I_ENDDATE, 'YYYY-MM-DD');
  V_EMPNO     := 7369;
  O_ERRCODE   := 0;
  O_ERRMSG    := '运行成功';

  EXECUTE IMMEDIATE 'TRUNCATE TABLE EMP_REF';

  -- 调用存储过程
  P_EMP_CURSOR(V_EMPNO, V_CURSOR);

  -- 处理游标数据
  LOOP
    FETCH V_CURSOR INTO V_TABLE;
    EXIT WHEN V_CURSOR%NOTFOUND;

    INSERT INTO EMP_REF(
      EMPNO, 
      ENAME, 
      JOB, 
      MGR, 
      HIREDATE, 
      SAL, 
      COMM, 
      DEPTNO
    )
    VALUES (
      V_TABLE.EMPNO,
      V_TABLE.ENAME,
      V_TABLE.JOB,
      V_TABLE.MGR,
      V_TABLE.HIREDATE,
      V_TABLE.SAL,
      V_TABLE.COMM,
      V_TABLE.DEPTNO
    );

  END LOOP;

  COMMIT;

EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
    O_ERRCODE := SQLCODE;
    O_ERRMSG  := SUBSTR('未处理异常:' || SQLERRM || '。' ||
                        DBMS_UTILITY.FORMAT_ERROR_BACKTRACE,
                        1,
                        1000);

END P_EMP_CURSOR_REF;

函数返回游标类型

--声明函数
CREATE OR REPLACE FUNCTION F_INDEX_REALTIME_RUN(I_INDCODE   IN  VARCHAR2,
                                                I_STARTDATE IN  VARCHAR2,
                                                I_ENDDATE   IN  VARCHAR2) 
RETURN SYS_REFCURSOR IS

--定义游标变量
V_CURSOR       SYS_REFCURSOR;

--将返回值写入游标变量
OPEN V_CURSOR FOR 
SELECT 
  TO_DATE(TO_CHAR(SYSDATE, 'YYYY-MM-DD'), 'YYYY-MM-DD') D_DATE,
FROM DUAL;

--返回
RETURN V_CURSOR;

构造日期序列

oracle connect by 函数可用于生成日期序列

生成连续的日期序列

# 获取一年日期序列
SQL> SELECT 
  TRUNC(SYSDATE, 'YYYY') + ROWNUM - 1 "DATE" FROM DUAL 
CONNECT BY ROWNUM < (ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), 12) - TRUNC(SYSDATE, 'YYYY') + 1);

DATE
----------
2018-01-01
2018-01-02
2018-01-03
2018-01-04
2018-01-05
2018-01-06
2018-01-07  
# 获取指定日期范围内的日期序列

SQL> SELECT 
  DATE '2018-01-01' + ROWNUM - 1 "DATE" FROM DUAL 
CONNECT BY ROWNUM <= DATE'2018-01-05' - DATE'2018-01-01' + 1;

DATE
----------
2018-01-01
2018-01-02
2018-01-03
2018-01-04
2018-01-05  

生成特定的日期序列

# 获取月末日期

SQL> SELECT 
  LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'),  ROWNUM - 1)) "DATE"  
FROM DUAL CONNECT BY ROWNUM < (
  MONTHS_BETWEEN(TO_DATE(TO_CHAR(SYSDATE, 'YYYY')||'12-31', 'yyyy-mm-dd') + 1, TRUNC(SYSDATE, 'YYYY')) + 1
);

DATE
----------
2018-01-31
2018-02-28
2018-03-31
2018-04-30
2018-05-31
2018-06-30
2018-07-31
2018-08-31
2018-09-30
2018-10-31
2018-11-30
2018-12-31

每月月末日期序列

# 获取月末日期

SQL> SELECT 
  ADD_MONTHS(LAST_DAY(DATE'2021-01-01'), + ROWNUM - 1) AS LASTDATE
FROM DUAL
CONNECT BY ROWNUM <= MONTHS_BETWEEN(DATE'2022-01-01', DATE'2021-01-01');

DATE
----------
2021-01-31
2021-02-28
2021-03-31
2021-04-30
2021-05-31
2021-06-30
2021-07-31
2021-08-31
2021-09-30
2021-10-31
2021-11-30
2021-12-31

存储过程异常日志记录

对于 Oracle 的存储过程,可以使用输出参数来输出异常信息,也可以使用数据表来记录异常信息,为了便于统计,最好是用数据表来记录异常信息,并且记录异常的这个功能最好是通用的。

  • 创建存储过程异常日志表
SQL> conn scott/tiger;

SQL> create table pub_proc_err_log (
  vc_log_id      varchar2(50) default sys_guid(),
  vc_owner       varchar2(50),
  vc_module_name varchar2(50),
  vc_proc_name   varchar2(50),
  d_err_date     date,
  vc_sqlcode     varchar2(50),
  vc_sqlerrm     varchar2(100),
  vc_err_content varchar2(1000),
  d_updatetime   date default sysdate
)
;
comment on table pub_proc_err_log is '存储过程异常日志表';
comment on column pub_proc_err_log.vc_log_id is '唯一标识';
comment on column pub_proc_err_log.vc_owner is '所属用户';
comment on column pub_proc_err_log.vc_module_name is '模块名称';
comment on column pub_proc_err_log.vc_proc_name is '存储过程名称';
comment on column pub_proc_err_log.d_err_date is '报错日期';
comment on column pub_proc_err_log.vc_sqlcode is 'SQLCODE';
comment on column pub_proc_err_log.vc_sqlerrm is 'SQLERRM';
comment on column pub_proc_err_log.vc_err_content is '报错的具体信息';
comment on column pub_proc_err_log.d_updatetime is '更新时间';
  • 创建主键、索引
SQL> alter table pub_proc_err_log add constraint pk_pub_proc_err_log primary key(vc_log_id);

SQL> create index idx_pub_proc_err_log on pub_proc_err_log(d_err_date, vc_proc_name);
  • 登录 SYS 用户,为存储过程异常日志表创建公有同义词并授权
SQL> conn / as sysdba

SQL> create public synonym pub_proc_err_log for scott.pub_proc_err_log;

SQL> grant select, insert, update, delete on pub_proc_err_log to public;
  • 创建异常日志记录存储过程
SQL> conn scott/tiger;

CREATE OR REPLACE PROCEDURE P_RECORD_PROC_ERR_LOG(I_USER_NAME   IN VARCHAR2,
                                                  I_MODULE_NAME IN VARCHAR2,
                                                  I_PROC_NAME   IN VARCHAR2,
                                                  I_SQLCODE     IN INTEGER,
                                                  I_SQLERRM     IN VARCHAR2,
                                                  I_ERR_LINE    IN VARCHAR2) IS
  /*****************************************************************************\
  *                           xxxxxx
  *           COPYRIGHT (C) 2019, xxxxxx有限公司
  * ===========================================================================
  *
  * 名     称:P_RECORD_PROC_ERR_LOG
  * 功能 描述:异常日志记录存储过程
  *
  * 参     数:传入 -> 模块名称、存储过程名称、SQLCODE、SQL_ERRM、报错的具体行
  *            传出 -> 无
  *
  * 修订 记录:
  * 版本号   编辑时间      编辑人           修改描述
  * 1.0.0    2019-01-21    xxxxxx           创建此存储过程
  \*****************************************************************************/
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN  
  INSERT INTO PUB_PROC_ERR_LOG (
    VC_OWNER,
    VC_MODULE_NAME,
    VC_PROC_NAME,
    D_ERR_DATE,
    VC_SQLCODE,
    VC_SQLERRM,
    VC_ERR_CONTENT
  )
  SELECT 
    I_USER_NAME,
    I_MODULE_NAME, 
    I_PROC_NAME,
    TO_CHAR(SYSDATE, 'YYYY-MM-DD'),   
    I_SQLCODE,    
    I_SQLERRM,    
    I_ERR_LINE 
  FROM DUAL;

  COMMIT;

END P_RECORD_PROC_ERR_LOG;
  • 登录 SYS 用户,为异常日志记录存储过程创建公有同义词并授权
SQL> conn / as sysdba

SQL> create public synonym p_record_proc_err_log for scott.p_record_proc_err_log;

SQL> grant execute on p_record_proc_err_log to public;

创建同义词后,其它用户就不需要以 用户名. 的方式访问了。

  • 在其它存储过程的异常处理部分调用异常日志记录存储过程,记录日志
CREATE OR REPLACE PROCEDURE P_TEST(I_PARAM1 IN VARCHAR2,
                                   I_PARAM2 IN VARCHAR2) IS
BEGIN

  SELECT USERNAME INTO V_USERNAME FROM USER_USERS;

  NULL;

  COMMIT;

EXCEPTION
  WHEN OTHERS THEN
    P_RECORD_PROC_ERR_LOG (
      V_USERNAME
      'PROCEDURE',
      'P_TEST',
      SQLCODE,
      SQLERRM,
      SUBSTR('未处理异常:' || SQLERRM || '。' ||
                        DBMS_UTILITY.FORMAT_ERROR_BACKTRACE,
                        1,
                        1000)
    ); 
    ROLLBACK;

END P_TEST;

存储过程执行状态记录

  • 创建存储过程日志记录表
CREATE TABLE MPUB_F_PROC_LOGS (
  VC_ID          VARCHAR2(100) DEFAULT SYS_GUID() NOT NULL,
  VC_OWNER       VARCHAR2(50),
  VC_MODULE_NAME VARCHAR2(50) DEFAULT 'PROCEDURE',
  VC_PROC_NAME   VARCHAR2(50),
  D_STARTDATE    DATE,
  D_ENDATE       DATE,
  C_PARAMS       CLOB,
  C_PARAMS2      CLOB,
  VC_SQLCODE     VARCHAR2(50),
  VC_SQLERRM     VARCHAR2(2000),
  VC_ERR_CONTENT VARCHAR2(2000),
  T_STARTTIME    TIMESTAMP(6),
  T_ENDTIME      TIMESTAMP(6),
  F_ELAPSEDTIME  INTEGER,
  VC_STATUS      VARCHAR2(2),
  D_UPDATETIME   DATE DEFAULT SYSDATE
);

COMMENT ON TABLE MPUB_F_PROC_LOGS
  IS '数据处理日志记录表';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.VC_ID
  IS '唯一标识';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.VC_OWNER
  IS '所属用户';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.VC_MODULE_NAME
  IS '模块名称';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.VC_PROC_NAME
  IS '模块代码';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.D_STARTDATE
  IS '模块参数(开始日期)';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.D_ENDATE
  IS '模块参数(结束日期)';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.C_PARAMS
  IS '模块参数(组合代码、计划代码...)';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.C_PARAMS2
  IS '预留模块参数,可为空';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.VC_SQLCODE
  IS 'SQLCODE';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.VC_SQLERRM
  IS 'SQLERRM';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.VC_ERR_CONTENT
  IS '异常信息';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.T_STARTTIME
  IS '程序开始时间';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.T_ENDTIME
  IS '程序结束时间';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.F_ELAPSEDTIME
  IS '总耗时(毫秒)';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.VC_STATUS
  IS '运行状态(1 正在运行;0 结束;-1 异常)';
COMMENT ON COLUMN MPUB_F_PROC_LOGS.D_UPDATETIME
  IS '更新时间';

ALTER TABLE MPUB_F_PROC_LOGS
  ADD CONSTRAINT PK_MPUB_F_PROC_LOGS PRIMARY KEY (VC_ID);
  • 创建用于记录时间的函数
CREATE OR REPLACE FUNCTION F_TIMESTAMP_DIFF(I_STARTTIME IN TIMESTAMP,
                                            I_ENDTIME   IN TIMESTAMP)
  RETURN INTEGER AS
  /****************************************************************\
  *                           xxxxxx
  *           COPYRIGHT (C) 2020, xxxxxx
  * ===============================================================
  *
  * 名     称:F_TIMESTAMP_DIFF
  * 功能 描述:存储过程运行时间
  *
  * 参     数:I_START_TIME    开始时间
  *            I_END_TIME      结束时间
  *
  * 修订 记录:
  * 版本号   编辑时间      编辑人           修改描述
  * 1.0.0    2020-03-10    xxx              创建此函数
  \****************************************************************/
  STR       VARCHAR2(50);
  MISECOND  INTEGER;       --毫秒
  SECONDS   INTEGER;       --秒
  MINUTES   INTEGER;       --分
  HOURS     INTEGER;       --小时
  DAYS      INTEGER;       --天
BEGIN
  STR       := TO_CHAR(I_ENDTIME - I_STARTTIME);
  MISECOND  := TO_NUMBER(SUBSTR(STR, INSTR(STR, ' ') + 10, 3));
  SECONDS   := TO_NUMBER(SUBSTR(STR, INSTR(STR, ' ') + 7, 2));
  MINUTES   := TO_NUMBER(SUBSTR(STR, INSTR(STR, ' ') + 4, 2));
  HOURS     := TO_NUMBER(SUBSTR(STR, INSTR(STR, ' ') + 1, 2));
  DAYS      := TO_NUMBER(SUBSTR(STR, 1, INSTR(STR, ' ')));

  RETURN DAYS*24*60*60*1000 + HOURS*60*60*1000 + MINUTES*60*1000 + SECONDS*1000 + MISECOND;

END;
  • 创建日志记录存储过程
CREATE OR REPLACE PROCEDURE P_MPUB_F_PROC_LOGS(I_ID          IN VARCHAR2,
                                               I_OWNER       IN VARCHAR2 DEFAULT NULL,
                                               I_MODULE_NAME IN VARCHAR2 DEFAULT NULL,
                                               I_PROC_NAME   IN VARCHAR2 DEFAULT NULL,
                                               I_STARTDATE   IN DATE     DEFAULT NULL,
                                               I_ENDDATE     IN DATE     DEFAULT NULL,
                                               I_SQLCODE     IN NUMBER   DEFAULT NULL,
                                               I_SQLERRM     IN VARCHAR2 DEFAULT NULL,
                                               I_ERR_CONTENT IN VARCHAR2 DEFAULT NULL,
                                               I_PARAMS      IN CLOB     DEFAULT NULL,
                                               I_PARAMS2     IN CLOB     DEFAULT NULL,
                                               I_STATUS      IN VARCHAR2) IS
  /****************************************************************\
  *                           xxxxxx
  *           COPYRIGHT (C) 2020, xxxxx
  * ===============================================================
  *
  * 名     称:P_MPUB_F_PROC_LOGS
  * 功能 描述:存储过程日期记录
  *
  * 参     数:I_USER_NAME     用户名
  *            I_MODULE_NAME   模块分类,默认为 'PROCEDURE'
  *            I_PROC_NAME     模块名称
  *            I_STARTDATE     模块参数(开始日期)
  *            I_ENDDATE       模块参数(结束日期)
  *            I_PARAMS        模块参数(组合代码、计划代码...)
  *            I_PARAMS2       预留模块参数,可为空
  *            I_SQLCODE       SQLCODE
  *            I_SQLERRM       SQLERRM
  *            I_ERR_CONTENT   异常信息
  *            I_STATUS        模块运行状态(1 正在运行;0 结束;-1 异常)
  *
  * 修订 记录:
  * 版本号   编辑时间      编辑人           修改描述
  * 1.0.0    2020-03-10    xxx              创建此存储过程
  \****************************************************************/
  V_STARTTIME   TIMESTAMP;   --开始时间
  V_ENDTIME     TIMESTAMP;   --结束时间
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN

  --程序运行中...
  IF I_STATUS = '1' THEN

    SELECT SYSTIMESTAMP INTO V_STARTTIME FROM DUAL;

    INSERT INTO MPUB_F_PROC_LOGS (
      VC_ID,
      VC_OWNER,
      VC_MODULE_NAME,
      VC_PROC_NAME,
      D_STARTDATE,
      D_ENDATE,
      VC_SQLCODE,
      VC_SQLERRM,
      VC_ERR_CONTENT,
      C_PARAMS,
      C_PARAMS2,
      T_STARTTIME,
      T_ENDTIME,
      F_ELAPSEDTIME,
      VC_STATUS,
      D_UPDATETIME
    )
    SELECT
      I_ID,
      I_OWNER,
      I_MODULE_NAME,
      I_PROC_NAME,
      I_STARTDATE,
      I_ENDDATE,
      NULL,
      NULL,
      NULL,
      I_PARAMS,
      I_PARAMS2,
      V_STARTTIME,
      NULL,
      NULL,
      I_STATUS,
      SYSDATE
    FROM DUAL;

  --程序结束...
  ELSIF I_STATUS = '0' THEN

    SELECT SYSTIMESTAMP INTO V_ENDTIME FROM DUAL;
    SELECT T_STARTTIME INTO V_STARTTIME FROM MPUB_F_PROC_LOGS WHERE VC_ID = I_ID;

    UPDATE MPUB_F_PROC_LOGS S
    SET S.VC_SQLCODE     = I_SQLCODE,
        S.VC_SQLERRM     = I_SQLERRM,
        S.T_ENDTIME     = V_ENDTIME,
        S.F_ELAPSEDTIME  = F_TIMESTAMP_DIFF(V_ENDTIME, V_STARTTIME),
        S.VC_STATUS      = I_STATUS
    WHERE S.VC_ID = I_ID;

  --程序异常...
  ELSIF I_STATUS = '-1' THEN

    UPDATE MPUB_F_PROC_LOGS S
    SET S.VC_SQLCODE     = I_SQLCODE,
        S.VC_SQLERRM     = I_SQLERRM,
        S.VC_ERR_CONTENT = I_ERR_CONTENT,
        S.VC_STATUS      = I_STATUS
    WHERE S.VC_ID = I_ID;

  END IF;

  COMMIT;

EXCEPTION
 WHEN OTHERS THEN
  ROLLBACK;

END P_MPUB_F_PROC_LOGS;
  • 主存储过程中调用日志记录存储过程
PROCEDURE P_TEST(I_STARTDATE IN  VARCHAR2,
                 I_ENDDATE   IN  VARCHAR2,
                 I_PARAM     IN  CLOB,
                 O_ERRCODE   OUT INTEGER,
                 O_ERRMSG    OUT VARCHAR2) IS
  /*****************************************************************************\
  *                           xxxxxx
  *           COPYRIGHT (C) 2020, xxxxxx
  * ===========================================================================
  *
  * 名     称:P_TEST
  * 功能 描述:xxxxx
  *
  * 参     数:传入 -> xxxxx
  *            传出 -> xxxxx
  *
  * 修订 记录:
  * 版本号   编辑时间      编辑人           修改描述
  * 1.0.0    2020-02-28    Li.chaoxiang     创建此存储过程
  \*****************************************************************************/
  V_STARTDATE DATE;          --开始日期
  V_ENDDATE   DATE;          --结束日期
  V_OWNER     VARCHAR2(20);  --所属用户
  V_ID        VARCHAR2(100); --唯一标识
BEGIN
  EXECUTE IMMEDIATE 'ALTER SESSION SET NLS_DATE_FORMAT=''YYYY-MM-DD''';
  O_ERRCODE   := 0;
  O_ERRMSG    := '运行成功';
  V_STARTDATE := TO_DATE(I_STARTDATE, 'YYYY-MM-DD');
  V_ENDDATE   := TO_DATE(I_ENDDATE, 'YYYY-MM-DD');

  --获取所属用户
  SELECT USERNAME INTO V_OWNER FROM USER_USERS;

  WHILE (V_LOOPDATE <= V_ENDDATE) LOOP

   --获取唯一标识
   SELECT SYS_GUID() INTO V_ID FROM DUAL;

   --程序开始,写入日志信息
   P_MPUB_F_PROC_LOGS(I_ID          => V_ID,
                      I_OWNER       => V_OWNER,
                      I_MODULE_NAME => 'PROCEDURE',
                      I_PROC_NAME   => 'P_TEST',
                      I_STARTDATE   => V_STARTDATE,
                      I_ENDDATE     => V_ENDDATE,
                      I_PARAMS      => V_FUNDCODE,
                      I_STATUS      => '1');


   --程序结束,更新状态
   P_MPUB_F_PROC_LOGS(I_ID      => V_ID,
                      I_SQLCODE => O_ERRCODE,
                      I_SQLERRM => O_ERRMSG,
                      I_STATUS  => '0');

   V_LOOPDATE := V_LOOPDATE + 1;

  END LOOP;

  COMMIT;

EXCEPTION
  WHEN OTHERS THEN
   --程序异常,写入日志信息
   P_MPUB_F_PROC_LOGS(I_ID          => V_ID,
                      I_SQLCODE     => SQLCODE,
                      I_SQLERRM     => SQLERRM,
                      I_ERR_CONTENT => SUBSTR('未处理异常:' || SQLERRM || '。' ||
                                              DBMS_UTILITY.FORMAT_ERROR_BACKTRACE,
                                              1,
                                              1000),
                      I_STATUS      => '-1');
   ROLLBACK;
   O_ERRCODE := SQLCODE;
   O_ERRMSG  := SUBSTR('未处理异常:' || SQLERRM || '。' ||
                       DBMS_UTILITY.FORMAT_ERROR_BACKTRACE,
                       1,
                       1000);

END P_TEST;

删除重复记录

要删除一张表中重复的记录,目前遇到的效率最高的方法就是通过 ROWID 来解决。

  • 构造数据
SQL> create table t_emp as select * from emp;

SQL> insert into t_emp select * from emp where rownum <= 5;

SQL> COMMIT;
  • 删除重复记录
SQL> delete from t_emp t1 
where t1.rowid > (
  select 
    min(t2.rowid)
  from t_emp t2
  where t1.empno = t2.empno /* 根据能唯一区分一行记录的字段进行匹配 */
);

SQL> COMMIT;

随机数

  • 从表中随机取记录
SQL> select * from (
  select * from emp order by dbms_random.random
) where rownum < 3;

EMPNO ENAME      JOB         MGR HIREDATE          SAL      COMM DEPTNO
----- ---------- --------- ----- ----------- --------- --------- ------
 7900 JAMES      CLERK      7698 1981/12/3      950.00               30
 7844 TURNER     SALESMAN   7698 1981/9/8      1500.00      0.00     30

表示从 EMP 表中随机取 2 条记录。

  • 产生随机数
# 产生一个任意大小的随机数
SQL> select dbms_random.random from dual;

    RANDOM
----------
 777926139


# 产生一个100以内的随机数
SQL> select abs(mod(dbms_random.random, 100)) from dual;

ABS(MOD(DBMS_RANDOM.RANDOM,100))
--------------------------------
                              25


# 产生一个100~1000之间的随机数
SQL> select trunc(100 + 900 * dbms_random.value) from dual;

TRUNC(100+900*DBMS_RANDOM.VALUE)
--------------------------------
                             269


# 产生一个0~1之间的随机数
SQL> select dbms_random.value from dual;

     VALUE
----------
0.48788019


# 产生一个10~20之间的随机数
SQL> select dbms_random.value(10, 20) from dual;

DBMS_RANDOM.VALUE(10,20)
------------------------
        10.6273692301178
  • 产生随机字符串
SQL> select dbms_random.string('P', 30) from dual;

DBMS_RANDOM.STRING('P',30)
--------------------------------------------------------------------------------
Y(b}<[;ne/S L1"5AD?YmxrT5mF6't

第一个参数 P 表示 printable,即字符串由任意可打印字符构成
第二个参数表示返回字符串的长度

'u','U' 表示大写字母字符串
'l','L' 表示小写字母字符串
'a','A' 表示大小写混合的字符串
'x','X' 表示大写字母加数字字符串
'p','P' 表示任何的可打印字符
  • 生成带分隔符(-)的 GUID 自定义函数
CREATE OR REPLACE FUNCTION MY_GUID RETURN VARCHAR2 IS     
  GUID   VARCHAR(36);     
  TEMP   VARCHAR(32); 
BEGIN     
  TEMP   := SYS_GUID();     
  GUID   := SUBSTR(TEMP, 1,  8) || '-' || 
            SUBSTR(TEMP, 9,  4) || '-' ||
            SUBSTR(TEMP, 13, 4) || '-' ||
            SUBSTR(TEMP, 17, 4) || '-' ||
            SUBSTR(TEMP, 21);     
  RETURN GUID; 
END;


SQL> select my_guid() from dual;

MY_GUID()
--------------------------------------------------------------------------------
E1229C82-26F1-4D0A-8E3A-38ABEB6C3A4A

日期函数

序号 函数 描述
1 TRUNC(DATE, 'iw') 本周以来日期
2 TRUNC(DATE, 'MM') 本月以来日期
3 TRUNC(DATE, 'Q') 本季以来日期
4 TRUNC(DATE, 'YYYY') 今年以来日期
5 DATE - 6 最近一周日期
6 ADD_MONTHS(DATE, -1) + 1 最近一月日期
7 ADD_MONTHS(DATE, -12) + 1 最近一年日期
8 LAST_DAY(ADD_MONTHS(DATE, -1)) 上月末
9 LAST_DAY(DATE) 本月末
10 TRUNC(DATE, 'd') 本周第一天
  • 本周以来日期
# 本周以来日期以星期一为第一天

SQL> select 
  to_char(trunc(sysdate, 'iw') + 1, 'YYYY-MM-DD') as "本周以来日期",  
  to_char(sysdate, 'YYYY-MM-DD')                  as "当前日期"
from dual;

本周以来日期 当前日期
---------- ----------
2019-12-23 2019-12-25
  • 本月以来日期
SQL> select 
  to_char(trunc(sysdate, 'MM'), 'YYYY-MM-DD')  as "本月以来日期",  
  to_char(sysdate, 'YYYY-MM-DD')               as "当前日期"
from dual;

本月以来日期 当前日期
---------- ----------
2019-12-01 2019-12-25
  • 本季以来日期
SQL> select 
  to_char(trunc(sysdate, 'Q'), 'YYYY-MM-DD')  as "本季以来日期",  
  to_char(sysdate, 'YYYY-MM-DD')              as "当前日期"
from dual;

本季以来日期 当前日期
---------- ----------
2019-10-01 2019-12-25
  • 今年以来日期
SQL> select 
  to_char(trunc(sysdate, 'YYYY'), 'YYYY-MM-DD')  as "今年以来日期",  
  to_char(sysdate, 'YYYY-MM-DD')                 as "当前日期"
from dual;

今年以来日期 当前日期
---------- ----------
2019-01-01 2019-12-25
  • 最近一周日期
SQL> select 
  to_char((sysdate - 6), 'YYYY-MM-DD')  as "最近一周日期",  
  to_char(sysdate, 'YYYY-MM-DD')        as "当前日期"
from dual;

最近一周日期 当前日期
---------- ----------
2019-12-19 2019-12-25
  • 最近一月日期
SQL> select 
  to_char(add_months(sysdate, -1) + 1, 'YYYY-MM-DD')  as "最近一月日期",  
  to_char(sysdate, 'YYYY-MM-DD')                      as "当前日期"
from dual;

最近一月日期 当前日期
---------- ----------
2019-11-26 2019-12-25
  • 最近一年日期
SQL> select 
  to_char(add_months(sysdate, -12) + 1, 'YYYY-MM-DD')  as "最近一年日期",  
  to_char(sysdate, 'YYYY-MM-DD')                       as "当前日期"
from dual;

最近一年日期 当前日期
---------- ----------
2018-12-26 2019-12-25
  • 上月末
SQL> select 
  to_char(last_day(add_months(sysdate, -1)), 'YYYY-MM-DD')  as "上月末日期",  
  to_char(sysdate, 'YYYY-MM-DD')                            as "当前日期"
from dual;

上月末日期 当前日期
---------- ----------
2019-11-30 2019-12-25
  • 本月末
SQL> select 
  to_char(last_day(sysdate), 'YYYY-MM-DD')  as "本月末日期",  
  to_char(sysdate, 'YYYY-MM-DD')            as "当前日期"
from dual;

本月末日期 当前日期
---------- ----------
2019-12-31 2019-12-25
  • 本周第一天
SQL> select 
  to_char(trunc(sysdate, 'd'), 'YYYY-MM-DD') as "本周第一天",  
  to_char(sysdate, 'YYYY-MM-DD')             as "当前日期"
from dual;

本周第一天 当前日期
---------- ----------
2022-10-16 2022-10-19

空值处理函数

序号 函数 描述
1 NVL(exp1, exp2) 如果 exp1 为空,则返回 exp2;否则返回 exp1
2 NVL2(exp1, exp2, exp3) 如果 exp1 为空,则返回 exp3;否则返回 exp2
3 NULLIF(exp1, exp2) 如果 exp1 等于 exp2,则返回空;否则返回 exp1
4 COALESCE(exp1, exp2 ... expn) 返回列表中第1个不为空的值,如果全部元素为空,则返回空
  • NVL
SQL> select nvl(1, 2) from dual;

  NVL(1,2)
----------
         1


SQL> select nvl(null, 2) from dual;

NVL(NULL,2)
-----------
          2
  • NVL2
SQL> select nvl2(2, 3, 4) from dual;

NVL2(2,3,4)
-----------
          3


SQL> select nvl2(null, 3, 4) from dual;

NVL2(NULL,3,4)
--------------
             4
  • NULLIF
SQL> select nullif(3, 2) from dual;

NULLIF(3,2)
-----------
          3


SQL> select nullif(3, 3) from dual;

NULLIF(3,3)
-----------
  • COALESCE
SQL> select coalesce(null, 1, null, 2, 3) from dual;

COALESCE(NULL,1,NULL,2,3)
-------------------------
                        1


SQL> select coalesce(null, null, null, 2, 3) from dual;

COALESCE(NULL,NULL,NULL,2,3)
----------------------------
                           2


SQL> select nvl(coalesce(null, null), 3) from dual;

NVL(COALESCE(NULL,NULL),3)
--------------------------
                         3

EXISTS 和 NOT EXISTS 的改写

  • EXISTS

exists 改写成 inner join

select * from emp a 
where exists (
  select 1 from emp b where a.empno = b.empno
  and b.deptno = 20
);

EMPNO ENAME      JOB         MGR HIREDATE          SAL      COMM DEPTNO
----- ---------- --------- ----- ----------- --------- --------- ------
 7369 SMITH      CLERK      7902 1980/12/17     800.00               20
 7566 JONES      MANAGER    7839 1981/4/2      2975.00               20
 7788 SCOTT      ANALYST    7566 1987/4/19     3000.00               20
 7876 ADAMS      CLERK      7788 1987/5/23     1100.00               20
 7902 FORD       ANALYST    7566 1981/12/3     3000.00               20
select a.* 
from emp a inner join emp b 
  on  a.empno = b.empno
  and b.deptno = 20;

EMPNO ENAME      JOB         MGR HIREDATE          SAL      COMM DEPTNO
----- ---------- --------- ----- ----------- --------- --------- ------
 7369 SMITH      CLERK      7902 1980/12/17     800.00               20
 7566 JONES      MANAGER    7839 1981/4/2      2975.00               20
 7788 SCOTT      ANALYST    7566 1987/4/19     3000.00               20
 7876 ADAMS      CLERK      7788 1987/5/23     1100.00               20
 7902 FORD       ANALYST    7566 1981/12/3     3000.00               20  
  • NOT EXISTS

not exists 改写成 left outer join + is null

select * from emp a 
where not exists (
  select 1 from emp b where a.empno = b.empno
  and b.deptno = 20
);

EMPNO ENAME      JOB         MGR HIREDATE          SAL      COMM DEPTNO
----- ---------- --------- ----- ----------- --------- --------- ------
 7499 ALLEN      SALESMAN   7698 1981/2/20     1600.00    300.00     30
 7521 WARD       SALESMAN   7698 1981/2/22     1250.00    500.00     30
 7654 MARTIN     SALESMAN   7698 1981/9/28     1250.00   1400.00     30
 7698 BLAKE      MANAGER    7839 1981/5/1      2850.00               30
 7782 CLARK      MANAGER    7839 1981/6/9      2450.00               10
 7839 KING       PRESIDENT       1981/11/17    5000.00               10
 7844 TURNER     SALESMAN   7698 1981/9/8      1500.00      0.00     30
 7900 JAMES      CLERK      7698 1981/12/3      950.00               30
 7934 MILLER     CLERK      7782 1982/1/23     1300.00               10
select a.* 
from emp a left outer join emp b 
  on  a.empno = b.empno
  and b.deptno = 20
where b.empno is null;

EMPNO ENAME      JOB         MGR HIREDATE          SAL      COMM DEPTNO
----- ---------- --------- ----- ----------- --------- --------- ------
 7499 ALLEN      SALESMAN   7698 1981/2/20     1600.00    300.00     30
 7521 WARD       SALESMAN   7698 1981/2/22     1250.00    500.00     30
 7654 MARTIN     SALESMAN   7698 1981/9/28     1250.00   1400.00     30
 7698 BLAKE      MANAGER    7839 1981/5/1      2850.00               30
 7782 CLARK      MANAGER    7839 1981/6/9      2450.00               10
 7839 KING       PRESIDENT       1981/11/17    5000.00               10
 7844 TURNER     SALESMAN   7698 1981/9/8      1500.00      0.00     30
 7900 JAMES      CLERK      7698 1981/12/3      950.00               30
 7934 MILLER     CLERK      7782 1982/1/23     1300.00               10

原创文章,转载请注明出处:http://www.opcoder.cn/article/13/