在 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/