由于不当操作或硬件出现的异常,会导入DMSQL 程序运行发生错误,这种错误超出了DMSQL 程序可处理范围,此种错误称为异常。DMSQL 程序提供了对异常捕获和处理模块。
1.异常处理流程
异常通常由执行DMSQL 程序所在发生服务器抛出,也可以由用户在DMSQL 程序主动抛出。异常抛出后,程序执行会被中止,转至异常处理模块,后续代码将不再执行。
2.预定义异常
在DM SQL程序中,数据库系统提供了预定义异常,这些异常与常见的DM 错误相对应, 如下表所示。
除了上面的预定义异常外,所有没有明确列出的异常,由特殊的异常名OTHERS处理,OTHERS对应的异常处理语句须放在其他异常处理语句之后。
对指定异常进行处理的示例:
DECLARE
v_name VARCHAR(50);
v_phone VARCHAR(50);
BEGIN
SELECT NAME,PHONE INTO V_NAME,V_PHONE FROM PERSON.PERSON A,RESOURCES.EMPLOYEE B
WHERE A.PERSONID=B.PERSONID AND B.TITLE='销售代表';
PRINT v_name||' '||v_phone;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
PRINT 'SELECT INTO 中包含多行数据';
END;
执行成功, 执行耗时73毫秒. 执行号:884
SELECT INTO 中包含多行数据
影响了1条记录
1条语句执行成功
3.用户自定义异常
当然,用户还可以在DMSQL 程序中自定义异常。程序员可以把一些特定的状态定义为异常,在一定的条件下抛出,然后利用DMSQL 程序的异常机制进行处理。方法有两种:
(1)使用EXCEPTION FOR 将异常变量与错误号绑定
语法如下:
<异常变量名> EXCEPTION [FOR <错误号> [, <错误描述>]];
在DMSQL 程序的声明部分使用该语法声明一个异常变量。其中,FOR 子句用来为异常变量绑定错误号(SQLCODE 值)及错误描述。<错误号>必须是-20000 到-30000 间的负数 值,<错误描述>则为字符串类型。如果未显式指定错误号,则系统在运行中在-10001 到15000 区间中顺序为其绑定错误值。
CREATE OR REPLACE PROCEDURE proc_exception (v_personid INT)
IS
v_name VARCHAR(50);
v_email VARCHAR(50);
e1 EXCEPTION FOR -20001, 'EMAIL 为空';
BEGIN
SELECT NAME,EMAIL INTO v_name,v_email FROM PERSON.PERSON WHERE PERSONID=v_personid;
IF v_email='' THEN
RAISE e1;
ELSE
PRINT v_name || v_email;
END IF;
EXCEPTION
WHEN E1 THEN
PRINT v_name ||' '||SQLCODE||' '||SQLERRM;
END;
/
CALL proc_exception(2);
[执行语句1]:
CALL proc_exception(2);
执行成功, 执行耗时0毫秒. 执行号:886
王刚 -20001 EMAIL 为空
影响了1条记录
1条语句执行成功
(2)使用EXCEPTION_INIT将一个特定的错误号与程序中所声明的异常标示符关联起来
语法如下:
<异常变量名> EXCEPTION; PRAGMA EXCEPTION_INIT(<异常变量名>, <错误号>);
在DMSQL 程序的声明部分使用这个语法先声明一个异常变量,再使用EXCEPTION_INIT将一个特定的错误号与这个异常变量关联起来。这样可以通过名字引用任意的DM 服务器内部异常(DM 服务器内部异常可参考《DM8 程序员手册》附录1),并且可以通过名字为异常编写一适当的异常处理。如果希望使用RAISE 语句抛出一个用户自定义异常,则与异常关联的错误号必须是-20000 到-30000 之间的负数值。
下面的例子使用EXCEPTION_INIT 将DM 服务器的特定错误“-2206:缺少参数值” 与异常变量e1 关连起来。
CREATE OR REPLACE PROCEDURE proc_exception2(v_personid INT)
IS
TYPE Rec_type IS RECORD(V_NAME VARCHAR(50),V_EMAIL VARCHAR(50));
v_col Rec_type;
e1 EXCEPTION;
PRAGMA EXCEPTION_INIT(e1, -2206);
BEGIN
SELECT NAME,EMAIL INTO v_col FROM PERSON.PERSON WHERE PERSONID=v_personid;
IF v_col.V_EMAIL=''THEN
RAISE e1;
ELSE
PRINT v_col.V_NAME || v_col.V_EMAIL;
END IF;
EXCEPTION
WHEN e1 THEN
PRINT v_col.V_NAME ||' '||SQLCODE||':' ||SQLERRM(SQLCODE);
END;
/
[执行语句1]:
CALL proc_exception2(2);
执行成功, 执行耗时0毫秒. 执行号:921
王刚 -2206:[-2206]:无效的参数值
影响了1条记录
1条语句执行成功
下面的例子则使用EXCEPTION_INIT 定义用户自定义异常,并使用RAISE 语句抛出该异常。
CREATE OR REPLACE PROCEDURE proc_exception3 (v_personid INT)
IS
TYPE Rec_type IS RECORD (V_NAME VARCHAR(50),V_EMAIL VARCHAR(50));
v_col Rec_type;
e2 EXCEPTION;
PRAGMA EXCEPTION_INIT(e2, -25000);
BEGIN
SELECT NAME,EMAIL INTO v_col FROM PERSON.PERSON WHERE PERSONID=v_personid;
IF v_col.V_EMAIL='' THEN
RAISE e2;
ELSE
PRINT v_col.V_NAME || v_col.V_EMAIL;
END IF;
EXCEPTION
WHEN e2 THEN
PRINT v_col.V_NAME ||'的邮箱为空';
END;
/
[执行语句1]:
CALL proc_exception3(2);
执行成功, 执行耗时1毫秒. 执行号:923
王刚的邮箱为空
影响了1条记录
1条语句执行成功
4.异常的抛出
DMSQL 程序运行时如果发生错误,系统会自动抛出一个异常,还可以使用RAISE 主动抛出一个异常。例如,当程序运行并不违反数据库规则,但是不满足应用的业务逻辑时,可以主动抛出一个异常并进行处理。
使用RAISE 抛出异常分为有异常名和无异常名两种情况。
(1)有异常名
语法如下:
RAISE <异常名>
可以使用RAISE 语句抛出一个系统预定义异常或用户自定义异常。
(2)无异常名
如果没有在声明部分定义异常变量,也可以在执行部分使用DM 提供的系统过程直接抛 出自定义异常,语法如下:
RAISE_APPLICATION_ERROR (
ERR_CODE IN INT,
ERR_MSG IN VARCHAR(2000)
);
ERR_CODE:错误码,取值范围为-20000~-30000; ERR_MSG:用户自定义的错误信息,长度不能超过2000字节。
例如:
CREATE OR REPLACE PROCEDURE proc_exception4 (v_personid INT)
IS
TYPE Rec_type IS RECORD (V_NAME VARCHAR(50),V_EMAIL VARCHAR(50));
v_col Rec_type;
BEGIN
SELECT NAME,EMAIL INTO v_col FROM PERSON.PERSON WHERE PERSONID=v_personid;
IF v_col.V_EMAIL='' THEN
RAISE_APPLICATION_ERROR(-20001, '邮箱为空');
ELSE PRINT v_col.V_NAME || v_col.V_EMAIL;
END IF;
EXCEPTION
WHEN OTHERS THEN
PRINT SQLCODE ||' '||v_col.V_NAME ||':' ||SQLERRM;
END;
/
[执行语句2]:
CALL proc_exception4(2);
执行成功, 执行耗时1毫秒. 执行号:925
-20001 王刚:邮箱为空
影响了1条记录
2条语句执行成功
5.内置函数SQLCODE 和SQLERRM
DMSQL 程序提供了内置函数SQLCODE 和SQLERRM,程序员可以在异常处理部分通过 这两个函数获取异常对应的错误码和描述信息。SQLCODE 返回错误码,为一个负数。 SQLERRM 返回异常的描述信息,为字符串类型。
总共1个语句正依次执行...
[执行语句1]:
DECLARE
e1 EXCEPTION FOR -20001, 'EMAIL 为空';
BEGIN
RAISE e1;
EXCEPTION
WHEN e1 THEN
PRINT SQLCODE ||' '|| SQLERRM;
END;
执行成功, 执行耗时1毫秒. 执行号:926
-20001 EMAIL 为空
影响了0条记录
1条语句执行成功
若异常为DM 服务器错误,则SQLERRM 返回该错误的描述信息,否则SQLERRM 的返 回值遵循以下规则:
如果错误码在-15000 至-19999 间,返回’User-Defined Exception’;
如果错误码在-20000 至-30000 之间,返回’DM-<错误码绝对值>’;
如果错误码大于0 或小于-65535,返回’-<错误码绝对值>: non-DM exception’;
否则,返回’DM-<错误码绝对值>: Message <错误码绝对值> not found;’。
如果程序员想查询某个DM 服务器错误码对应的错误描述信息,也可以在执行部分或异 常处理部分使用错误码参数调用SQLERRM 函数,语法如下: VARCHARSQLERRM(ERROR_NUMBER INT(4));
这种SQLERRM 调用方式的返回值也遵循上面所述的规则。
例如 :
[执行语句1]:
BEGIN
PRINT 'SQLERRM(-6815): ' || SQLERRM(-6815);
END;
执行成功, 执行耗时5毫秒. 执行号:927
SQLERRM(-6815): [-6815]:指定的对象数据库中不存在
影响了0条记录
1条语句执行成功
6.异常处理部分
DMSQL 程序的异常处理部分对执行过程中抛出的异常进行处理,可以处理一个或多个异常。
语法如下:
EXCEPTION {<异常处理语句>;} <异常处理语句> ::= WHEN <异常处理器> <异常处理器> ::= <异常名>{ OR <异常名>} THEN <执行部分>;
EXCEPTION 关键字表示异常处理部分的开始。如果在语句块的执行中出现异常,执行 就被传递给语句块的异常处理部分。而如果在本语句块的异常处理部分没有相应的异常处理器对它进行处理,系统就会中止此语句块的执行,并将此异常传递到该语句块的上一层语句块或其调用者,这样一直到最外层。如果始终没有找到相应的异常处理器,则中止本次调用 语句的处理,并向用户报告错误。
异常处理部分是可选的。但是如果出现EXCEPTION 关键字时,必须至少有一个异常处理器。异常处理器可以按任意次序排列,只有OTHERS 异常处理器必须在最后,它处理所 有没有明确列出的异常。此外,同一个语句块内的异常处理器不允许处理重复的异常。一个异常处理器可以同时对多个异常进行统一处理,在WHEN子句中用OR分隔多个异常名。