用于检索自定义消息而不是异常详细信息的 PLSQL 异常处理

Posted

技术标签:

【中文标题】用于检索自定义消息而不是异常详细信息的 PLSQL 异常处理【英文标题】:PLSQL exception handling for retrieve custom message instead of exception details 【发布时间】:2021-04-09 22:31:15 【问题描述】:

我正在尝试处理在存储过程执行期间捕获的可能异常 - 通过使用 EXCEPTION WHEN OTHERS THEN 代码块 - 并返回用户友好消息(而不是异常的详细信息)。

这是我正在使用的存储过程的代码:

create or replace PROCEDURE ProcessWork
  (
    V_USER      IN VARCHAR2 DEFAULT NULL,
    CV_1            IN OUT SYS_REFCURSOR
  )
  AS
  V_TOTALREADED NUMBER(9):=0;
  V_DINITCOLLECTCOTDEC4023 DATE;
  V_MAX_COR_NCODE NUMBER(9,0):=0;
  V_LOG_NCODE NUMBER := 0; 
  V_ERRMSG VARCHAR2(1000 CHAR);
  BEGIN
    -------------START - VALIDATIONS --------------

    -- DUPLICATED LINE:
    INSERT INTO TLOG_FILE_RETURN(LFR_NCODE,RECORD_LINE,LGR_CLOG_CAUSE)
    SELECT TMP.LFR_NCODE,TMP.RECORD_LINE,'DUPLICATED LINE'
    FROM TLOADED_FILE_RETURN TMP
    WHERE TMP.RECORD_LINE IS NOT NULL
          AND EXISTS (SELECT COUNT(TMP.LFR_NCODE) CONTEO
                      FROM TLOADED_FILE_RETURN TMI
                      WHERE TMI.RECORD_LINE = TMP.RECORD_LINE
                      HAVING COUNT(TMI.RECORD_LINE) > 1)
    ;
    
    -- STATE DOES NOT EXISTS:
    INSERT INTO TLOG_FILE_RETURN(LFR_NCODE,RECORD_LINE,LGR_CLOG_CAUSE)
    SELECT TMP.LFR_NCODE,TMP.RECORD_LINE,'STATE (' || TMP.RAS_NCODE || ') DOES NOT EXISTS.'
    FROM TLOADED_FILE_RETURN TMP
    WHERE NOT EXISTS (SELECT 1
                      FROM T_REJECT_AFF_STATE RAS
                      WHERE RAS.RAS_NCODE = TMP.RAS_NCODE)
    AND NOT EXISTS (SELECT 1 FROM TLOG_FILE_RETURN TLOG WHERE TLOG.LFR_NCODE = TMP.LFR_NCODE);
    
    -- [other data validations here removed for brevity]

    -------------END - VALIDATIONS --------------

    -- NOTE HERE: Raise intentional exception for sampling:
    SELECT 1/0 
    INTO V_COUNTERROR 
    FROM DUAL;

    -- Table that stores all validated data:
    INSERT INTO TLOAD_FILE_RETURN
    (
        LFR_NCODE,
        LFR_NCODE_PROCESS,
        NSO_CLEGALCODE,
        NSO_NCODE,
        RAO_NCODE,
        RAS_NCODE,
        INT_CKEY,
        INT_NCODE,
        RAF_CTELEPHONE,
        RAT_NCODE,
        RAF_OBSERVATION_ANALYST,
        RCA_NCODE,
        RAF_CNOVELTYCAUSE,
        RECORD_LINE
    )
    SELECT
      TMP.LFR_NCODE,
      REPLACE(TMP.LFR_NCODE_PROCESS,' ',''),
      REPLACE(TMP.NSO_CLEGALCODE,' ',''),
      REPLACE(NOV.NSO_NCODE,' ',''),
      REPLACE(TMP.RAO_NCODE,' ',''),
      REPLACE(TMP.RAS_NCODE,' ',''),
      REPLACE(TMP.INT_CKEY,' ',''),
      REPLACE(IT.INT_NCODE,' ',''),
      REPLACE(TMP.RAF_CTELEPHONE,' ',''),
      REPLACE(TMP.RAT_NCODE,' ',''),
      REPLACE(TMP.RAF_OBSERVATION_ANALYST,' ',''),
      REPLACE(TMP.RCA_NCODE,' ',''),
      REPLACE(TMP.RAF_CNOVELTYCAUSE,' ',''),
      TMP.RECORD_LINE
    FROM TLOADED_FILE_RETURN TMP
    JOIN T_NOVELTY_SOURCE NOV  ON NOV.NSO_CLEGAL_CODE=TMP.NSO_CLEGALCODE
    JOIN T_ident_type ITY ON ITY.ITY_CSHORTNAME=TMP.ITY_CSHORTNAME
    JOIN T_INTERMEDIARY IT ON IT.INT_CKEY=TMP.INT_CKEY
      WHERE NOT EXISTS(SELECT 1 FROM TLOG_FILE_RETURN TLOG WHERE TLOG.LFR_NCODE=TMP.LFR_NCODE)
      ;

    -- Count of valid records:
    SELECT COUNT(1) INTO V_TOTALREADED FROM TLOAD_FILE_RETURN;

    -- If valid records are found, start process:
    IF(NVL(V_TOTALREADED,0) > 0) THEN
      BEGIN
          -- MERGE para update "_NCODE" relational-fields:
          MERGE INTO TLOAD_FILE_RETURN INF
          USING (
                SELECT TMP.LFR_NCODE,
                       NSO.NSO_NCODE,
                       ITY.ITY_NCODE,
                       INE.INT_NCODE
                FROM TLOAD_FILE_RETURN TMP INNER JOIN T_NOVELTY_SOURCE NSO
                ON TMP.NSO_CLEGALCODE = NSO.NSO_CLEGAL_CODE INNER JOIN T_IDENT_TYPE ITY
                ON TMP.ITY_CSHORTNAME = ITY.ITY_CSHORTNAME
                INNER JOIN T_IDENT_TYPE ITY
                ON TMP.ITY_CSHORTNAME = ITY.ITY_CSHORTNAME
                INNER JOIN T_INTERMEDIARY INE
                ON TMP.INT_CKEY = INE.INT_CKEY
          ) SRC
          ON (INF.LFR_NCODE=SRC.LFR_NCODE)
          WHEN MATCHED THEN UPDATE SET
                       INF.NSO_NCODE = SRC.NSO_NCODE,
                       INF.ITY_NCODE = SRC.ITY_NCODE,
                       INF.INT_NCODE = SRC.INT_NCODE
                    ;
                    
          -- Other MERGEs code removed for brevity.

          -- Data inserction to business tables:
          INSERT INTO T_REJECTED_AFFILIAT
          (
               RPT_NCODE, 
               RAF_CIDENTIFICATIONTYPE_HOLDER, 
               RAF_CIDENTIFICATIONNUMB_HOLDER,
               RAF_OBSERVATION_ANALYST, 
               RAF_OBSERVATION_ADVISER
          )
            SELECT 4, 
                   TMP.ITY_NCODE, 
                   TMP.PER_CIDENTIFICATIONNUMBER, 
                   TMP.RAF_OBSERVATION_ANALYST, 
                   NULL
            FROM TLOAD_FILE_RETURN TMP
            WHERE TMP.LFR_NCODE_PROCESS = 1;

            INSERT INTO T_REJECT_CAUSE_BY_REJECT_AFFI (RAF_NCODE, RCA_NCODE)
            SELECT T.RAF_NCODE, T.RCA_NCODE
            FROM (
                 SELECT RAF.RAF_NCODE, TMP.RCA_NCODE
                 FROM T_REJECTED_AFFILIAT RAF JOIN TLOAD_FILE_RETURN TMP
                 ON RAF.NSO_NCODE = (SELECT RAF.NSO_NCODE FROM T_NOVELTY_SOURCE NSO WHERE NSO.NSO_CLEGAL_CODE = TMP.NSO_CLEGALCODE) /* Tipo de fuente. */
                 AND RAF.RAF_CAPPLICATIONCODE = TMP.RAF_NAPPLICATIONCODE /* Número de formulario / solicitud. */
                 AND RAF.RAT_NCODE = TMP.RAT_NCODE /* Tipo de registro. */
                 WHERE TMP.LFR_NCODE_PROCESS = 1
            ) T
            WHERE NOT EXISTS (SELECT 1
                              FROM T_REJECT_CAUSE_BY_REJECT_AFFI RCB
                              WHERE RCB.RAF_NCODE =  T.RAF_NCODE
                              AND RCB.RCA_NCODE = T.RCA_NCODE)
            UNION
            SELECT TMP.RAF_NCODE, TMP.RCA_NCODE
            FROM TLOAD_FILE_RETURN TMP
            WHERE NOT EXISTS (SELECT 1
                              FROM T_REJECT_CAUSE_BY_REJECT_AFFI RCB
                              WHERE RCB.RAF_NCODE =  TMP.RAF_NCODE
                              AND RCB.RCA_NCODE = TMP.RCA_NCODE)
            AND TMP.LFR_NCODE_PROCESS = 2;

          -- Apply all changes made so far:
          COMMIT;

            DECLARE
            CURSOR C_LOAD_FILE_RET IS
                SELECT TMP.RAF_NCODE, TMP.PREV_RAS_NCODE, TMP.RAS_NCODE, SYSDATE, V_USER
                FROM TLOAD_FILE_RETURN TMP
                WHERE TMP.LFR_NCODE_PROCESS = 2
                AND NVL(TMP.RAS_NCODE, 0) <> NVL(TMP.PREV_RAS_NCODE, 0)
                AND TMP.RAF_NCODE IS NOT NULL
                AND NSO_NCODE = 35 -- 7W
                AND RAT_NCODE = 10 -- AFILIACION EN RECORD_LINE
                ;

            TYPE TBL_LOAD_FILE_RET IS TABLE OF C_LOAD_FILE_RET%ROWTYPE
            INDEX BY BINARY_INTEGER;

            TAB_LOAD_FILE_RET TBL_LOAD_FILE_RET;

            BEGIN
            OPEN C_LOAD_FILE_RET;
                LOOP

                TAB_LOAD_FILE_RET.DELETE;

                FETCH C_LOAD_FILE_RET BULK COLLECT INTO TAB_LOAD_FILE_RET LIMIT 10000;

                BEGIN
                FORALL I IN INDICES OF TAB_LOAD_FILE_RET
                UPDATE T_REJECTED_AFFILIAT
                SET RAS_NCODE = TAB_LOAD_FILE_RET(I).RAS_NCODE
                WHERE RAF_NCODE = TAB_LOAD_FILE_RET(I).RAF_NCODE;

                COMMIT;
                EXIT WHEN C_LOAD_FILE_RET%NOTFOUND;
            EXCEPTION
              WHEN OTHERS THEN
                EXIT;
            END;
            END LOOP;
            CLOSE C_LOAD_FILE_RET;
        END;
       RETURN;
      END;
    END IF;

    -- Start exception code-block: 
    EXCEPTION WHEN OTHERS THEN
       V_ERRMSG:=SQLERRM||'-'||DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
       ROLLBACK;
       
       /* Insert exception details in log table for futher investigation: */
       PROCLOG(2, V_LOG_NCODE, SUBSTR(V_ERRMSG,1,250), V_USER, CV_1);
       DBMS_OUTPUT.PUT_LINE('Check error code # ' || TO_CHAR(V_LOG_NCODE));  
       
       /* Return error for show to user: */
       OPEN CV_1 FOR
            SELECT
                   CAST(0 AS NUMBER(9,0)) TOTAL_LOADED,
                   CAST(0 AS NUMBER(9,0)) TOTAL_SUCCESS,
                   CAST(0 AS NUMBER(9,0)) TOTAL_ERROR,
                   '.' FOLDERLOGS,
                   '.' FILE_NAMELOG,
                   /* This is the custom message: */
                   'An unexpected error ocurred while processing the data - error # ' || TO_CHAR(V_LOG_NCODE) MSG_ERROR 
                FROM DUAL;
       RETURN;
    -- End exception code-block: 

  END;

结果是:

PL/SQL procedure finished correctly. 
ERROR # ORA-01476:  divisor is equal to zero: line 41

SQLERRM: ORA-01476: divisor is equal to zero DBMS_UTILITY.FORMAT_ERROR_BACKTRACE: ORA-06512: line 41

我面临的问题是下面的 SQL 代码没有返回名为 CV_1 的游标并继续显示 Oracle 错误详细信息。

期望的结果应该是此代码块不应返回 Oracle 错误详细信息,而是返回保存在 V_ERRMSG 变量中的示例消息。

This sample on dbfiddle.uk 是用于测试的缩减样本,但我不能说在这两种情况下我做错了什么(即在 SQL Developer 和 dbfiddle.uk 中执行此代码) .

【问题讨论】:

cv_1 是一个局部变量,因此一旦匿名块完成,无论该块是否进入异常处理程序,它都会超出范围。也许您真的想创建一个具有out 参数类型为sys_refcursor 的存储过程?与v_errmsg 相同——如果您希望将其返回给调用者,您需要一个带有out 参数的存储过程。但是,使用返回码而不是抛出异常来指示过程遇到问题通常是不受欢迎的。它使您的代码更加复杂。 您确定不想使用自定义消息引发自定义异常吗? @JustinCave 感谢 cmets,你说得对。 CV_1 是存储过程的一个参数 - CV_1 IN OUT SYS_REFCURSOR。我发布的代码是一个示例,我可以添加我正在工作的存储过程的副本 - 如果需要它来改善整体问题 - 我想 (一旦 SP 结束并且它捕获异常) ,返回自定义消息。 v_errmsg 也是out 参数吗?如果您实际使用的代码是一个存储过程,并且这是复制您实际遇到的问题所必需的,那么,是的,将您的代码更新为一个过程将非常有帮助,同时显示我们如何调用该代码。否则,我们必须做出一堆假设,这些假设对您来说可能正确,也可能不正确。 简化问题仍然有用——包括复制问题实际需要的所有代码,但仅此而已。我的模糊猜测是你想要这样的东西 dbfiddle.uk/… 但我仍然不明白你想要发生什么。该过程无法返回v_errMsg,因为v_errMsg 不是参数。我不确定您是否真的希望返回 v_errMsg,或者您是否希望它成为 cv_1 光标中的一列。 【参考方案1】:

在仔细检查源代码后,我确实注意到了某些元素:

EXCEPTION WHEN OTHERS THEN 代码块嵌套在IF(NVL(V_TOTALREADED,0) &gt; 0) THEN 代码块的BEGIN-END 内,因此,如果在整个代码中没有捕获异常,则会引发异常,但不会被处理符合预期。 在ProcessWork 过程之外,应用程序检查TLOG_FILE_RETURN 表中是否插入了任何数据 - 用于插入自定义/业务验证 - 这是通过执行COUNT() 表中的 TLOG_FILE_RETURN 记录。此计数仅计算一次 - 在调用 ProcessWork 过程之前 -.

我必须进行以下更改:

EXCEPTION WHEN OTHERS THEN 代码块移到BEGIN-END 代码块之外(在本答案的前几行中描述) - 将EXCEPTION WHEN OTHERS THEN 留在ProcessWork 过程的末尾。 在TLOG_FILE_RETURN 表中创建第二个COUNT() - 调用ProcessWork 过程之后。 删除EXCEPTION WHEN OTHERS THEN 代码块的RETURN 关键字。

通过这些更改,我得到了想要的结果(将引发异常的 oracle 详细信息保存在单独的表中,设置用户友好的返回消息并继续执行程序 )。

修改后的代码如下:

注意:此代码是ProcessWork 过程的简化版本 具有此答案中描述的更改。


create table BH_LOG (id number(9, 0), error_msg varchar2(1000));
DECLARE 
  V_COUNTERROR NUMBER :=0;
  CV_1          SYS_REFCURSOR;
  V_ERRMSG            VARCHAR2(1000 CHAR);--

  --STATISTICS: 
    V_TOTAL_LOADED              NUMBER(9,0) := 0;
    V_TOTAL_SUCCESS             NUMBER(9,0) := 0;
    V_TOTAL_ERROR                 NUMBER(9,0) := 0;

  V_FOLDERNAME    VARCHAR2(50) := 'SAMPLE';
  V_FOLDERLOGS    VARCHAR2(50) DEFAULT V_FOLDERNAME || '\Logs';
  V_FILE_NAMELOG     VARCHAR2(50):= 'FileResult_' || TO_CHAR(SYSDATE,'yyyymmddhh24miss')  ||'.TXT';--
BEGIN
     BEGIN
        /*
         SELECT (1) CONTEO
         INTO V_COUNTERROR
         FROM T_BH_LOG_FILE_RETURN;

         FOR GENERATE ORA-01403 ERROR:
         */

         SELECT 1/0 
         INTO V_COUNTERROR 
         FROM DUAL;

        EXCEPTION WHEN OTHERS THEN
          --V_ERRMSG:=SQLERRM||'-'||DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
          V_ERRMSG:='An error ocurred, please try again';-- Just an example.
          ROLLBACK;
          /*OPEN CV_1 FOR
               SELECT
                      CAST(0 AS NUMBER(9,0)) TOTAL_LOADED,
                      CAST(0 AS NUMBER(9,0)) TOTAL_SUCCESS,
                      CAST(0 AS NUMBER(9,0)) TOTAL_ERROR,
                      '.' FOLDERLOGS,
                      '.' FILE_NAMELOG,
                      V_ERRMSG
                   FROM DUAL;

                   DBMS_OUTPUT.PUT_LINE('ERROR # ' || V_ERRMSG);
                   DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
                   DBMS_OUTPUT.PUT_LINE('DBMS_UTILITY.FORMAT_ERROR_BACKTRACE: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);*/
          
          INSERT INTO BH_LOG (id, error_msg) 
          VALUES (1, V_ERRMSG); 
     END;
     OPEN CV_1 FOR
        SELECT
           V_TOTAL_LOADED TOTAL_LOADED,
           V_TOTAL_SUCCESS TOTAL_SUCCESS,
           V_TOTAL_ERROR TOTAL_ERROR,
           V_FOLDERLOGS FOLDERLOGS,
           V_FILE_NAMELOG FILE_NAMELOG,
           V_ERRMSG ERROMSG
        FROM DUAL;
END;
/
 1 行受影响
SELECT * FROM BH_LOG;
身份证 | ERROR_MSG -: | :-------------------------------- 1 |发生错误,请重试

db小提琴here

【讨论】:

以上是关于用于检索自定义消息而不是异常详细信息的 PLSQL 异常处理的主要内容,如果未能解决你的问题,请参考以下文章

PLSQL

Linux编程之自定义消息队列

Linux编程之自定义消息队列

WPF - 如何从视图而不是视图模型显示自定义异常

处理所有异常并显示自定义错误页面而不是默认框架错误页面

Spring Boot Security OAuth2 自定义异常消息