欢迎来到 Oracle PL/SQL 编程入门 的第八章!在这一章中,我们将深入探讨 PL/SQL 中的异常处理机制。通过学习如何定义和使用自定义异常、内置异常以及异常链,你将能够编写更加健壮和可靠的程序。此外,我们还会介绍一些注意事项,并通过实际例子展示它们的用法。准备好迎接新的挑战了吗?让我们开始吧!
异常处理是编程中的重要组成部分,它允许你在运行时捕获并处理错误,从而避免程序崩溃。PL/SQL 提供了强大的异常处理机制,包括异常范围、自定义异常和异常链等。
1.1 异常范围
异常范围指的是异常处理块的作用域。通常,异常处理块包含在一个 BEGIN...END
结构内,并且可以嵌套使用。
示例:
DECLARE
v_value NUMBER := 0;
BEGIN
BEGIN
-- 内部块
DBMS_OUTPUT.PUT_LINE(10 / v_value);
EXCEPTION
WHEN ZERO_DIVIDE THEN
DBMS_OUTPUT.PUT_LINE('内部块:除数不能为零');
END;
-- 外部块
DBMS_OUTPUT.PUT_LINE('外部块:继续执行');
END;
/
输出结果:
内部块:除数不能为零
外部块:继续执行
小贴士:异常范围就像一个保护罩
异常范围就像是给你的代码加上一层保护罩,当内部发生错误时,它可以防止错误扩散到外部代码。合理使用异常范围可以让程序更加健壮。
除了内置异常外,PL/SQL 允许你定义自己的异常。自定义异常可以帮助你更好地管理和处理特定类型的错误。
2.1 使用方法
自定义异常需要先声明,然后可以在程序中通过 RAISE
语句手动触发。
示例:
DECLARE
-- 自定义异常
invalid_employee_id EXCEPTION;
v_employee_id NUMBER := 999;
v_employee_name VARCHAR2(50);
BEGIN
SELECT first_name || ' ' || last_name INTO v_employee_name
FROM employees
WHERE employee_id = v_employee_id;
DBMS_OUTPUT.PUT_LINE('员工姓名: ' || v_employee_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE invalid_employee_id;
END;
/
-- 捕获自定义异常
DECLARE
invalid_employee_id EXCEPTION;
PRAGMA EXCEPTION_INIT(invalid_employee_id, -20001); -- 绑定错误号
BEGIN
-- 可能引发自定义异常的代码
RAISE invalid_employee_id;
EXCEPTION
WHEN invalid_employee_id THEN
DBMS_OUTPUT.PUT_LINE('自定义异常:无效的员工ID');
END;
/
输出结果:
自定义异常:无效的员工ID
小贴士:自定义异常让你的代码更灵活
自定义异常就像是给你自己定制了一套警报系统,当遇到特定问题时,它会提醒你采取行动。合理使用自定义异常可以让代码更加灵活和易维护。
PL/SQL 提供了许多内置异常,用于处理常见的数据库操作错误。下面我们将介绍几种常用的内置异常及其用法。
3.1 NO_DATA_FOUND
当查询没有返回任何行时,会抛出 NO_DATA_FOUND
异常。
示例:
DECLARE
v_employee_name VARCHAR2(50);
BEGIN
SELECT first_name || ' ' || last_name INTO v_employee_name
FROM employees
WHERE employee_id = 999; -- 假设不存在该员工ID
DBMS_OUTPUT.PUT_LINE('员工姓名: ' || v_employee_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('未找到匹配的员工记录');
END;
/
输出结果:
未找到匹配的员工记录
3.2 TOO_MANY_ROWS
当查询返回多于一行的结果时,会抛出 TOO_MANY_ROWS
异常。
示例:
DECLARE
v_employee_name VARCHAR2(50);
BEGIN
SELECT first_name || ' ' || last_name INTO v_employee_name
FROM employees
WHERE department_id = 10; -- 假设有多个员工属于该部门
DBMS_OUTPUT.PUT_LINE('员工姓名: ' || v_employee_name);
EXCEPTION
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('查询返回了多于一行的结果');
END;
/
输出结果:
查询返回了多于一行的结果
3.3 ZERO_DIVIDE
当除数为零时,会抛出 ZERO_DIVIDE
异常。
示例:
DECLARE
v_result NUMBER;
BEGIN
v_result := 10 / 0; -- 除数为零
DBMS_OUTPUT.PUT_LINE('结果: ' || v_result);
EXCEPTION
WHEN ZERO_DIVIDE THEN
DBMS_OUTPUT.PUT_LINE('除数不能为零');
END;
/
输出结果:
除数不能为零
3.4 LOGIN_DENIED
当尝试使用无效的用户名或密码登录数据库时,会抛出 LOGIN_DENIED
异常。
示例:
BEGIN
EXECUTE IMMEDIATE 'CONNECT invalid_user/invalid_password@database';
EXCEPTION
WHEN LOGIN_DENIED THEN
DBMS_OUTPUT.PUT_LINE('登录失败:无效的用户名或密码');
END;
/
输出结果:
登录失败:无效的用户名或密码
3.5 PROGRAM_ERROR
当 PL/SQL 程序内部发生严重错误时,会抛出 PROGRAM_ERROR
异常。
示例:
BEGIN
RAISE PROGRAM_ERROR; -- 手动抛出 PROGRAM_ERROR 异常
EXCEPTION
WHEN PROGRAM_ERROR THEN
DBMS_OUTPUT.PUT_LINE('发生了严重的程序错误');
END;
/
输出结果:
发生了严重的程序错误
3.6 VALUE_ERROR
当 PL/SQL 程序试图执行不兼容的数据类型转换时,会抛出 VALUE_ERROR
异常。
示例:
DECLARE
v_number NUMBER;
BEGIN
v_number := TO_NUMBER('ABC'); -- 非数字字符串
DBMS_OUTPUT.PUT_LINE('转换后的数字: ' || v_number);
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('无法将非数字字符串转换为数字');
END;
/
输出结果:
无法将非数字字符串转换为数字
3.7 DUP_VAL_ON_INDEX
当尝试插入重复值到唯一索引列时,会抛出 DUP_VAL_ON_INDEX
异常。
示例:
BEGIN
INSERT INTO employees (employee_id, first_name, last_name)
VALUES (100, 'John', 'Doe'); -- 假设 employee_id 100 已存在
DBMS_OUTPUT.PUT_LINE('数据插入成功');
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
DBMS_OUTPUT.PUT_LINE('违反唯一约束:重复的 employee_id');
END;
/
输出结果:
违反唯一约束:重复的 employee_id
小贴士:内置异常让错误处理更高效
内置异常就像是一个预先准备好的工具箱,当你遇到常见问题时,可以直接从中选择合适的工具来解决问题。合理使用内置异常可以让你的错误处理更加高效。
异常链是指在一个异常处理程序中重新抛出异常,以便在更高层次上进行处理。这在复杂的程序结构中非常有用,因为它允许你将异常逐层传递,直到找到合适的处理程序。
示例:
DECLARE
invalid_employee_id EXCEPTION;
PRAGMA EXCEPTION_INIT(invalid_employee_id, -20001); -- 绑定错误号
v_employee_id NUMBER := 999;
v_employee_name VARCHAR2(50);
BEGIN
BEGIN
SELECT first_name || ' ' || last_name INTO v_employee_name
FROM employees
WHERE employee_id = v_employee_id;
DBMS_OUTPUT.PUT_LINE('员工姓名: ' || v_employee_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE invalid_employee_id;
END;
EXCEPTION
WHEN invalid_employee_id THEN
DBMS_OUTPUT.PUT_LINE('自定义异常:无效的员工ID');
END;
/
输出结果:
自定义异常:无效的员工ID
小贴士:异常链让错误处理更有层次感
异常链就像是一个层层递进的迷宫,当你在一个层次上找不到出路时,可以将问题传递到更高的层次,直到找到解决方案。合理使用异常链可以让错误处理更加有层次感。
在编写 PL/SQL 代码时,有一些常见问题需要注意,以避免潜在的错误和性能问题。
5.1 合理使用异常处理
不要滥用异常处理,尽量只捕获你需要处理的异常。过度使用异常处理会影响程序的性能,并使代码难以维护。
5.2 不要忽略异常
忽略异常(例如使用 WHEN OTHERS THEN NULL
)可能会导致潜在的问题被掩盖,影响程序的可靠性。
5.3 使用 RAISE_APPLICATION_ERROR
提供自定义错误消息
有时你需要提供更详细的错误信息,这时可以使用 RAISE_APPLICATION_ERROR
函数。
示例:
BEGIN
IF NOT EXISTS (SELECT 1 FROM employees WHERE employee_id = 100) THEN
RAISE_APPLICATION_ERROR(-20001, '员工 ID 100 不存在');
END IF;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/
输出结果:
ORA-20001: 员工 ID 100 不存在
小贴士:自定义错误消息让调试更容易
使用
RAISE_APPLICATION_ERROR
提供自定义错误消息就像是给你的程序添加了注释,帮助你更快地找到问题所在。
现在是时候来挑战一下自己了!我们将通过几个简单的练习来巩固所学的知识。
挑战一:处理无效员工ID
编写一个 PL/SQL 块,提示用户输入员工ID,并查询对应的员工姓名。如果找不到匹配的员工记录,则抛出自定义异常并捕获处理。
示例代码:
DECLARE
invalid_employee_id EXCEPTION;
PRAGMA EXCEPTION_INIT(invalid_employee_id, -20001); -- 绑定错误号
v_employee_id NUMBER := &employee_id; -- 输入员工ID
v_employee_name VARCHAR2(50);
BEGIN
SELECT first_name || ' ' || last_name INTO v_employee_name
FROM employees
WHERE employee_id = v_employee_id;
DBMS_OUTPUT.PUT_LINE('员工姓名: ' || v_employee_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE invalid_employee_id;
WHEN invalid_employee_id THEN
DBMS_OUTPUT.PUT_LINE('自定义异常:无效的员工ID');
END;
/
执行说明:
999
。自定义异常:无效的员工ID
。挑战二:处理重复插入
编写一个 PL/SQL 块,尝试插入一条新记录到 employees
表中。如果该表中已经存在相同 employee_id
的记录,则捕获并处理 DUP_VAL_ON_INDEX
异常。
示例代码:
BEGIN
INSERT INTO employees (employee_id, first_name, last_name)
VALUES (&employee_id, '&first_name', '&last_name'); -- 输入 employee_id, first_name, last_name
DBMS_OUTPUT.PUT_LINE('数据插入成功');
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
DBMS_OUTPUT.PUT_LINE('违反唯一约束:重复的 employee_id');
END;
/
执行说明:
employee_id
, first_name
, last_name
,例如 100
, 'John'
, 'Doe'
。违反唯一约束:重复的 employee_id
。小贴士:动手实践是最好的老师
理论知识固然重要,但真正的技能提升来自于不断的实践。通过这些挑战,你可以加深对 PL/SQL 的理解,并发现自己可能忽略的小细节。勇敢地面对每一个挑战,你会发现编程其实并没有那么难!
在这一章中,我们深入探讨了 PL/SQL 中的异常处理机制,包括异常范围、自定义异常、内置异常和异常链。以下是本章的主要内容总结:
总结要点:
NO_DATA_FOUND
、TOO_MANY_ROWS
、ZERO_DIVIDE
等。RAISE_APPLICATION_ERROR
提供自定义错误消息。恭喜你完成了第八章的学习!通过这节课,你已经掌握了 PL/SQL 中的异常处理机制,并为后续更高级的内容打下了坚实的基础。未来的学习中,我们将继续探索 PL/SQL 的更多功能,如存储过程、触发器等。希望你能继续保持对 PL/SQL 的兴趣,勇敢探索,成为一名熟练的 PL/SQL 用户!