用于检索自定义消息而不是异常详细信息的 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) > 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 异常处理的主要内容,如果未能解决你的问题,请参考以下文章