In Oracle PL/SQL, an exception is an event that disrupts the normal flow of program execution. These events, often referred to as errors or runtime errors, can occur due to various reasons: data issues, system resource problems, logical flaws, or invalid operations. Effective PL/SQL error handling is paramount for building stable and resilient database applications. Without proper exception handling, an unhandled exception will cause the entire PL/SQL block to terminate abruptly, rolling back any uncommitted DML statements, which can lead to incomplete transactions and a poor user experience. Learning how to handle exceptions in Oracle is a core skill for any database programmer.
Note: When developing PL/SQL applications, understanding exception types, exception propagation, and best practices for error management are key concepts. We will explore how to trap errors and implement graceful error recovery using the EXCEPTION
block, a fundamental component of structured PL/SQL programming. This approach helps in building fault-tolerant PL/SQL code and ensuring data consistency.
Example 1: Oracle PL/SQL Basic Arithmetic Exception
DECLARE
v_numerator NUMBER := 10;
v_denominator NUMBER := 0;
v_result NUMBER;
BEGIN
-- Attempting a division by zero, which will raise a predefined exception
v_result := v_numerator / v_denominator;
DBMS_OUTPUT.PUT_LINE('Result: ' || v_result); -- This line will not be executed
EXCEPTION
-- Catching the specific predefined exception for division by zero
WHEN ZERO_DIVIDE THEN
DBMS_OUTPUT.PUT_LINE('Error: Division by zero occurred. Please check the denominator.');
-- Catching any other unexpected errors
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation This example demonstrates a common predefined exception: ZERO_DIVIDE
. The program attempts to divide 10 by 0, which is an invalid arithmetic operation. Instead of crashing, the EXCEPTION
block WHEN ZERO_DIVIDE
clause traps the error, printing a user-friendly message. This showcases the basic structure of PL/SQL error trapping and how exceptions prevent program termination.
Example 2: Oracle PL/SQL Invalid Number Conversion Exception
DECLARE
v_string_num VARCHAR2(20) := 'abc';
v_number_val NUMBER;
BEGIN
-- Attempting to convert a non-numeric string to a number, raising a VALUE_ERROR
v_number_val := TO_NUMBER(v_string_num);
DBMS_OUTPUT.PUT_LINE('Converted number: ' || v_number_val); -- This line will not be executed
EXCEPTION
-- Catching the specific predefined exception for data type conversion errors
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('Error: Invalid number format. Could not convert "' || v_string_num || '" to a number.');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation Here, the code tries to convert the string 'abc' into a number using TO_NUMBER
. Since 'abc' is not a valid numerical representation, Oracle raises a VALUE_ERROR
exception. The EXCEPTION
section WHEN VALUE_ERROR
clause handles this data type conversion error, providing a specific error message. This illustrates how PL/SQL exception handling helps in managing data validation issues.
Example 3: Oracle PL/SQL Handling Too Many Rows Exception
DECLARE
v_employee_name VARCHAR2(100);
BEGIN
-- Attempting to select a single value into a variable when multiple rows exist
-- This will raise the TOO_MANY_ROWS exception
SELECT full_name INTO v_employee_name FROM employees WHERE department_id = 10;
DBMS_OUTPUT.PUT_LINE('Employee name: ' || v_employee_name); -- This line will not be executed
EXCEPTION
-- Catching the specific predefined exception for multiple row fetches into a scalar variable
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Error: Multiple employees found for department 10. Cannot assign to a single variable.');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation This example demonstrates the TOO_MANY_ROWS
exception, which occurs when a SELECT INTO
statement returns more than one row but is expected to return only one. The EXCEPTION
block specifically catches this scenario, preventing the program from failing and informing the user about the data retrieval issue. This is a critical aspect of PL/SQL SELECT INTO error handling.
Example 4: Oracle PL/SQL Handling No Data Found Exception
DECLARE
v_product_name VARCHAR2(100);
BEGIN
-- Attempting to select a row that does not exist based on the product_id
-- This will raise the NO_DATA_FOUND exception
SELECT product_name INTO v_product_name FROM products WHERE product_id = 9999;
DBMS_OUTPUT.PUT_LINE('Product name: ' || v_product_name); -- This line will not be executed
EXCEPTION
-- Catching the specific predefined exception when no rows are returned
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Error: Product with ID 9999 not found in the database.');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation The NO_DATA_FOUND
exception is demonstrated here. It arises when a SELECT INTO
statement yields no rows. The EXCEPTION
block provides a graceful way to handle this situation, preventing a crash and allowing the program to respond appropriately, such as informing the user that the requested data does not exist. This is essential for robust PL/SQL query handling.
Example 5: Oracle PL/SQL Basic Custom Message with OTHERS
DECLARE
v_number1 NUMBER := 10;
v_number2 NUMBER := 0;
v_result NUMBER;
BEGIN
-- This block simulates a scenario where an unexpected error might occur
-- (e.g., if v_number2 was still 0 but we didn't specifically handle ZERO_DIVIDE)
-- For demonstration, let's explicitly raise an unhandled exception for OTHERS
v_result := v_number1 / v_number2; -- This will cause ZERO_DIVIDE, but we'll use OTHERS to catch it
DBMS_OUTPUT.PUT_LINE('Result: ' || v_result);
EXCEPTION
-- Using OTHERS to catch any exception not explicitly handled, providing SQLCODE and SQLERRM
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unforeseen error has occurred.');
DBMS_OUTPUT.PUT_LINE('Error Code: ' || SQLCODE); -- Displays Oracle error number
DBMS_OUTPUT.PUT_LINE('Error Message: ' || SQLERRM); -- Displays Oracle error message
END;
/
Explanation This example focuses on the WHEN OTHERS
clause, which acts as a catch-all for any exceptions not explicitly handled by preceding WHEN
clauses. It's a best practice to include WHEN OTHERS
at the end of your EXCEPTION
block to prevent any unhandled exceptions from terminating your program. It also demonstrates the use of SQLCODE
(the Oracle error number) and SQLERRM
(the Oracle error message) functions, which are invaluable for PL/SQL error logging and debugging to understand what went wrong. This provides a mechanism for generic error handling and error reporting in PL/SQL.
Understanding Exceptions
Understanding Exceptions in PL/SQL involves recognizing the two main categories: predefined exceptions and user-defined exceptions. Predefined exceptions are automatically raised by Oracle when specific runtime errors occur, such as NO_DATA_FOUND
or TOO_MANY_ROWS
. These have predefined names and error numbers. User-defined exceptions, on the other hand, are declared by the programmer and explicitly raised using the RAISE
statement, allowing for custom error conditions tailored to business logic. Grasping these distinctions is fundamental for effective PL/SQL error management and implementing robust exception handling strategies. Knowing when to use predefined vs. custom exceptions is key to writing clean and maintainable code.
Note: A deeper dive into PL/SQL exception types involves understanding their behavior: how they are raised, how they propagate up the call stack if not handled, and the impact they have on transaction integrity. For efficient debugging PL/SQL errors, knowing the common predefined exceptions and how to interpret SQLCODE
and SQLERRM
is essential. This section will reinforce Oracle PL/SQL error concepts and prepare you for advanced exception handling techniques.
Example 1: Oracle PL/SQL Understanding NO_DATA_FOUND
DECLARE
v_employee_id NUMBER := 1000; -- Assuming this ID does not exist in employees table
v_employee_name VARCHAR2(100);
BEGIN
-- Attempt to retrieve an employee name for a non-existent ID
-- This will explicitly raise NO_DATA_FOUND
SELECT first_name || ' ' || last_name
INTO v_employee_name
FROM employees
WHERE employee_id = v_employee_id;
DBMS_OUTPUT.PUT_LINE('Employee Found: ' || v_employee_name);
EXCEPTION
-- Specific handling for NO_DATA_FOUND
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Understanding NO_DATA_FOUND: Employee ID ' || v_employee_id || ' does not exist.');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation This example demonstrates the NO_DATA_FOUND
predefined exception. When the SELECT INTO
statement fails to find any row matching employee_id = 1000
, Oracle automatically raises this exception. The EXCEPTION
block specifically catches it, preventing the program from terminating and providing a clear message that the record was not found, illustrating how PL/SQL handles missing data.
Example 2: Oracle PL/SQL Understanding TOO_MANY_ROWS
DECLARE
v_department_id NUMBER := 90; -- Assuming department 90 has multiple employees
v_manager_id NUMBER;
BEGIN
-- Attempt to retrieve a single manager ID for a department with multiple employees
-- This will explicitly raise TOO_MANY_ROWS
SELECT manager_id
INTO v_manager_id
FROM employees
WHERE department_id = v_department_id;
DBMS_OUTPUT.PUT_LINE('Manager ID: ' || v_manager_id);
EXCEPTION
-- Specific handling for TOO_MANY_ROWS
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Understanding TOO_MANY_ROWS: Multiple managers found for department ' || v_department_id || '.');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation Here, the SELECT INTO
statement tries to assign multiple manager IDs (because department 90 has multiple employees) to a single scalar variable v_manager_id
. This causes Oracle to raise the TOO_MANY_ROWS
exception. The EXCEPTION
block's WHEN TOO_MANY_ROWS
clause catches this, explaining that the query returned more data than expected for a single-variable assignment, crucial for PL/SQL query design.
Example 3: Oracle PL/SQL Understanding DUP_VAL_ON_INDEX
-- Assume a table 'categories' with a unique index on 'category_name'
-- CREATE TABLE categories (
-- category_id NUMBER PRIMARY KEY,
-- category_name VARCHAR2(50) UNIQUE
-- );
-- INSERT INTO categories (category_id, category_name) VALUES (1, 'Electronics');
DECLARE
v_new_category_id NUMBER := 2;
v_new_category_name VARCHAR2(50) := 'Electronics'; -- Duplicate value for unique index
BEGIN
-- Attempt to insert a duplicate value into a unique indexed column
-- This will explicitly raise DUP_VAL_ON_INDEX
INSERT INTO categories (category_id, category_name)
VALUES (v_new_category_id, v_new_category_name);
COMMIT;
DBMS_OUTPUT.PUT_LINE('Category inserted successfully.');
EXCEPTION
-- Specific handling for DUP_VAL_ON_INDEX
WHEN DUP_VAL_ON_INDEX THEN
DBMS_OUTPUT.PUT_LINE('Understanding DUP_VAL_ON_INDEX: Category name "' || v_new_category_name || '" already exists.');
ROLLBACK; -- Rollback the failed transaction
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation This example illustrates the DUP_VAL_ON_INDEX
predefined exception, which occurs when an INSERT
or UPDATE
statement attempts to store a duplicate value in a column that has a unique index or primary key constraint. The EXCEPTION
block catches this specific data integrity violation, preventing the transaction from committing and explaining the reason for the failure, vital for PL/SQL data integrity enforcement.
Example 4: Oracle PL/SQL Understanding PROGRAM_ERROR
DECLARE
TYPE t_rec IS RECORD (
id NUMBER,
name VARCHAR2(10)
);
v_rec t_rec;
BEGIN
-- Simulating a situation that might lead to PROGRAM_ERROR
-- This specific example might not always raise PROGRAM_ERROR in all environments
-- as it's typically for internal PL/SQL issues, but conceptually demonstrates it.
-- A more common scenario is bad memory access or internal PL/SQL engine issues.
v_rec.name := RPAD('A', 20, 'B'); -- Trying to assign a string longer than VARCHAR2(10)
DBMS_OUTPUT.PUT_LINE('Record Name: ' || v_rec.name);
EXCEPTION
-- Catching PROGRAM_ERROR (often indicates internal PL/SQL errors)
WHEN PROGRAM_ERROR THEN
DBMS_OUTPUT.PUT_LINE('Understanding PROGRAM_ERROR: An internal PL/SQL error occurred.');
DBMS_OUTPUT.PUT_LINE('Details: ' || SQLERRM);
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation The PROGRAM_ERROR
exception is less common in typical application code, as it usually signals an internal PL/SQL problem, such as memory corruption or issues with the PL/SQL engine itself. In this example, attempting to assign a string longer than the record field's declared length might sometimes trigger PROGRAM_ERROR
(though VALUE_ERROR
is more typical for string truncation). Handling it, even if rare, provides robustness against unexpected system-level issues, crucial for advanced PL/SQL debugging and system stability.
Example 5: Oracle PL/SQL Understanding LOGIN_DENIED
-- This example cannot be directly executed within a single anonymous block
-- as it requires attempting a database login.
-- However, we can simulate the scenario conceptually for understanding.
-- Conceptual understanding:
-- IF attempting to connect with invalid credentials, Oracle will raise LOGIN_DENIED.
-- In a real application, this would be handled at the connection layer,
-- but PL/SQL blocks might encounter it if they try to establish a new
-- session or proxy connection within the database using invalid credentials.
/*
BEGIN
-- Simulate a login attempt within a PL/SQL block (conceptual)
-- This specific syntax is not valid for direct execution as a login.
-- It's for understanding the type of error.
EXECUTE IMMEDIATE 'CONNECT invalid_user/invalid_password';
EXCEPTION
WHEN LOGIN_DENIED THEN
DBMS_OUTPUT.PUT_LINE('Understanding LOGIN_DENIED: Authentication failed due to invalid credentials.');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred during login simulation: ' || SQLERRM);
END;
/
*/
-- Actual executable example to demonstrate a similar error, e.g., missing privileges.
-- Although not strictly LOGIN_DENIED, it falls under security-related access issues.
DECLARE
v_count NUMBER;
BEGIN
-- Attempting to query a table for which the current user might not have privileges
-- This could lead to ORA-00942 (table or view does not exist) or ORA-01031 (insufficient privileges)
-- The closest predefined exception in PL/SQL that might cover this broadly is OTHERS.
-- A direct LOGIN_DENIED would happen at the connection level.
SELECT COUNT(*) INTO v_count FROM dba_users; -- Requires DBA role or specific grants
DBMS_OUTPUT.PUT_LINE('Number of DBA Users: ' || v_count);
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -942 THEN
DBMS_OUTPUT.PUT_LINE('Error ORA-00942: Table or view does not exist, or insufficient privileges.');
ELSIF SQLCODE = -1031 THEN
DBMS_OUTPUT.PUT_LINE('Error ORA-01031: Insufficient privileges to access DBA_USERS.');
ELSE
DBMS_OUTPUT.PUT_LINE('An unexpected access error occurred: ' || SQLERRM);
END IF;
END;
/
Explanation The LOGIN_DENIED
exception typically occurs at the database connection level, not usually within a running PL/SQL block. However, if a PL/SQL procedure attempts to establish a new session (e.g., using DBMS_SESSION
or EXECUTE IMMEDIATE
for a proxy connection) with invalid credentials, this exception could be raised. The executable example provided simulates a related access error (insufficient privileges) to demonstrate how PL/SQL security exceptions are handled, often through the OTHERS
clause combined with SQLCODE
checks, crucial for database security best practices.
Handling Exceptions with the EXCEPTION Block
The EXCEPTION
block is the cornerstone of PL/SQL error handling. It is an optional section in a PL/SQL block (anonymous block, procedure, function, or package body) dedicated to managing exceptions that occur during the execution of the BEGIN...END
block. When an exception is raised, either automatically by Oracle or explicitly by the programmer, control immediately transfers to the EXCEPTION
section. Inside this section, you define exception handlers using WHEN exception_name THEN
clauses, allowing your program to perform error recovery actions instead of crashing. This structured approach to exception management is vital for building robust PL/SQL applications.
Note: The sequence of exception handlers within the EXCEPTION
block matters: Oracle evaluates them from top to bottom. Specific handlers should come before generic ones (WHEN OTHERS
). Utilizing the EXCEPTION
block effectively involves understanding exception scope, exception propagation, and how to use SQLCODE
and SQLERRM
for detailed error information. This mechanism ensures graceful degradation and allows for PL/SQL error logging and transaction control (e.g., ROLLBACK
).
Example 1: Oracle PL/SQL Basic EXCEPTION Block Structure
DECLARE
v_num1 NUMBER := 10;
v_num2 NUMBER := 0;
v_result NUMBER;
BEGIN
-- Main execution section where operations are performed
DBMS_OUTPUT.PUT_LINE('Attempting division...');
v_result := v_num1 / v_num2; -- This will raise ZERO_DIVIDE
DBMS_OUTPUT.PUT_LINE('Division successful: ' || v_result);
EXCEPTION
-- EXCEPTION block starts here
WHEN ZERO_DIVIDE THEN
-- Handler for ZERO_DIVIDE exception
DBMS_OUTPUT.PUT_LINE('Caught ZERO_DIVIDE: Cannot divide by zero.');
WHEN OTHERS THEN
-- Generic handler for any other unhandled exception
DBMS_OUTPUT.PUT_LINE('Caught OTHERS: An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation This example demonstrates the fundamental structure of an EXCEPTION
block. The BEGIN...END
section contains the main logic. When ZERO_DIVIDE
occurs, control jumps directly to the EXCEPTION
section. The WHEN ZERO_DIVIDE THEN
clause catches the specific exception, preventing the program from terminating and allowing for controlled error handling. This is the most basic form of PL/SQL error trapping.
Example 2: Oracle PL/SQL Handling Multiple Exceptions in one block
DECLARE
v_id NUMBER := 100;
v_name VARCHAR2(50);
v_salary_str VARCHAR2(10) := 'abc'; -- Will cause VALUE_ERROR
v_salary NUMBER;
BEGIN
-- First potential error: NO_DATA_FOUND
SELECT employee_name INTO v_name FROM employees WHERE employee_id = 9999; -- Assumed non-existent
DBMS_OUTPUT.PUT_LINE('Employee Name: ' || v_name);
-- Second potential error: VALUE_ERROR
v_salary := TO_NUMBER(v_salary_str);
DBMS_OUTPUT.PUT_LINE('Salary: ' || v_salary);
EXCEPTION
-- Handler for NO_DATA_FOUND
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Error: No data found for the specified ID.');
-- Handler for VALUE_ERROR
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('Error: Invalid data format for salary conversion.');
-- Generic handler for any other errors
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unhandled exception occurred: ' || SQLERRM);
END;
/
Explanation This example showcases how a single EXCEPTION
block can handle multiple distinct predefined exceptions. The code first attempts a SELECT INTO
that would raise NO_DATA_FOUND
, then a TO_NUMBER
conversion that would raise VALUE_ERROR
. Oracle processes the handlers in order. When VALUE_ERROR
is raised by the TO_NUMBER
function, control is transferred to WHEN VALUE_ERROR
, demonstrating efficient multi-exception handling in PL/SQL.
Example 3: Oracle PL/SQL Using SQLCODE and SQLERRM in EXCEPTION Block
DECLARE
v_string VARCHAR2(5) := 'Hello World'; -- Too long for VARCHAR2(5)
BEGIN
DBMS_OUTPUT.PUT_LINE('Original String: ' || v_string); -- This line is executed
-- Assigning a longer string to a smaller variable will raise VALUE_ERROR (string truncation)
-- The implicit assignment happens at declaration or first use. Let's make it explicit.
DECLARE
v_short_string VARCHAR2(5);
BEGIN
v_short_string := 'Hello World'; -- This assignment will raise VALUE_ERROR
END;
DBMS_OUTPUT.PUT_LINE('This line will not be reached.');
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('Caught VALUE_ERROR due to string truncation.');
DBMS_OUTPUT.PUT_LINE('Oracle Error Code: ' || SQLCODE); -- e.g., -6502 for PL/SQL: numeric or value error
DBMS_OUTPUT.PUT_LINE('Oracle Error Message: ' || SQLERRM); -- Provides the full error description
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation This example demonstrates the critical use of SQLCODE
and SQLERRM
functions within the EXCEPTION
block. When a VALUE_ERROR
occurs due to string truncation, SQLCODE
returns the specific Oracle error number (e.g., -6502), and SQLERRM
returns the associated error message. These functions are indispensable for PL/SQL debugging, error logging, and providing detailed information for troubleshooting PL/SQL runtime errors.
Example 4: Oracle PL/SQL Nested Block Exception Handling
DECLARE
v_outer_var NUMBER := 10;
BEGIN
DBMS_OUTPUT.PUT_LINE('Entering outer block.');
BEGIN -- Nested PL/SQL block
DECLARE
v_inner_var NUMBER := 0;
BEGIN
DBMS_OUTPUT.PUT_LINE('Entering inner block.');
v_outer_var := v_outer_var / v_inner_var; -- ZERO_DIVIDE in inner block
DBMS_OUTPUT.PUT_LINE('Inner block operation successful.');
EXCEPTION
WHEN ZERO_DIVIDE THEN
DBMS_OUTPUT.PUT_LINE('Inner block caught ZERO_DIVIDE.');
-- If an exception is caught and handled here, it does not propagate further
v_outer_var := -1; -- Reset variable to a known state
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Inner block caught OTHERS: ' || SQLERRM);
END; -- End of nested block
DBMS_OUTPUT.PUT_LINE('Exiting outer block. Outer var: ' || v_outer_var);
EXCEPTION
WHEN OTHERS THEN
-- This outer block handler will only be reached if an exception
-- from the inner block was NOT handled.
DBMS_OUTPUT.PUT_LINE('Outer block caught OTHERS: ' || SQLERRM);
END;
/
Explanation This advanced example illustrates nested EXCEPTION
blocks and exception propagation. An inner block performs a division by zero, which is caught and handled within the inner block's EXCEPTION
section. Because the inner block handles the exception, it does not propagate to the outer block, allowing the outer block to continue execution normally. This demonstrates localized error handling and the control flow when exceptions are managed within their scope, a key concept for complex PL/SQL architectures.
Example 5: Oracle PL/SQL Transaction Control with EXCEPTION Block
-- Assume a table 'accounts' with columns account_id, balance
-- CREATE TABLE accounts (
-- account_id NUMBER PRIMARY KEY,
-- balance NUMBER
-- );
-- INSERT INTO accounts (account_id, balance) VALUES (1, 1000);
-- INSERT INTO accounts (account_id, balance) VALUES (2, 500);
DECLARE
v_from_account NUMBER := 1;
v_to_account NUMBER := 2;
v_amount NUMBER := 700;
BEGIN
-- Start of transaction
UPDATE accounts
SET balance = balance - v_amount
WHERE account_id = v_from_account;
-- Simulate an error condition (e.g., insufficient funds or invalid account)
-- For demonstration, let's artificially raise a NO_DATA_FOUND if balance drops too low
-- A more realistic scenario would be a check before update.
-- Let's cause a VALUE_ERROR to demonstrate rollback
DECLARE
v_test_num VARCHAR2(5) := 'INVALID';
v_converted_num NUMBER;
BEGIN
v_converted_num := TO_NUMBER(v_test_num); -- This will raise VALUE_ERROR
END;
UPDATE accounts
SET balance = balance + v_amount
WHERE account_id = v_to_account;
COMMIT; -- This will only be reached if no exception occurs
DBMS_OUTPUT.PUT_LINE('Transaction committed successfully.');
EXCEPTION
WHEN VALUE_ERROR THEN
ROLLBACK; -- Rollback all changes made in the transaction
DBMS_OUTPUT.PUT_LINE('Transaction rolled back due to data conversion error.');
DBMS_OUTPUT.PUT_LINE('Details: ' || SQLERRM);
WHEN OTHERS THEN
ROLLBACK; -- Always rollback on any unexpected error
DBMS_OUTPUT.PUT_LINE('Transaction rolled back due to an unexpected error.');
DBMS_OUTPUT.PUT_LINE('Details: ' || SQLERRM);
END;
/
-- Verify accounts table after running
-- SELECT * FROM accounts;
Explanation This example highlights how the EXCEPTION
block is crucial for transaction control and ensuring data consistency. The PL/SQL block attempts to simulate a funds transfer. An artificial VALUE_ERROR
is introduced to simulate a failure within the transaction. When this exception is caught, the ROLLBACK
statement within the EXCEPTION
handler undoes all uncommitted DML operations (the initial UPDATE
statement in this case), preserving the database's integrity. This is a critical aspect of reliable PL/SQL programming and ACID compliance.
Predefined Exceptions (e.g., NO_DATA_FOUND, TOO_MANY_ROWS)
Predefined exceptions in Oracle PL/SQL are system-defined errors that have dedicated names assigned by Oracle. These exceptions are automatically raised by the Oracle engine when specific runtime conditions occur. They cover a wide range of common database and programming errors, making your PL/SQL error handling more readable and specific than relying solely on generic error codes. Examples include NO_DATA_FOUND
(when a SELECT INTO
returns no rows), TOO_MANY_ROWS
(when a SELECT INTO
returns more than one row), DUP_VAL_ON_INDEX
(when inserting duplicate values into a unique column), and ZERO_DIVIDE
(arithmetic division by zero). Utilizing these named exceptions improves PL/SQL code clarity and maintainability.
Note: Familiarity with the most common Oracle predefined exceptions is essential for any PL/SQL developer. While SQLCODE
and SQLERRM
can identify any error, using named predefined exceptions allows for more precise and understandable exception handlers. This section will focus on the practical application of these named exceptions, demonstrating how to catch specific errors for more granular PL/SQL error management and effective fault tolerance.
Example 1: Oracle PL/SQL Handling NO_DATA_FOUND for Employee Lookup
DECLARE
v_employee_id NUMBER := 500; -- Assuming employee_id 500 does not exist
v_employee_info employees%ROWTYPE;
BEGIN
-- Attempt to retrieve an entire row for a non-existent employee
SELECT *
INTO v_employee_info
FROM employees
WHERE employee_id = v_employee_id;
DBMS_OUTPUT.PUT_LINE('Employee Name: ' || v_employee_info.first_name || ' ' || v_employee_info.last_name);
EXCEPTION
-- Specifically handling the case where no employee is found
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Predefined Exception: NO_DATA_FOUND - Employee with ID ' || v_employee_id || ' does not exist.');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation This example specifically handles the NO_DATA_FOUND
predefined exception. When the SELECT * INTO
statement fails to locate an employee with ID 500
, Oracle automatically raises this exception. The dedicated WHEN NO_DATA_FOUND
handler catches it, providing a clear message about the missing data, which is a common scenario in PL/SQL data retrieval.
Example 2: Oracle PL/SQL Handling TOO_MANY_ROWS for Department Manager
DECLARE
v_department_name VARCHAR2(50) := 'Sales'; -- Assuming multiple employees in Sales
v_manager_id NUMBER;
BEGIN
-- Attempt to find a single manager ID for a department that has multiple
-- employees, leading to TOO_MANY_ROWS
SELECT manager_id
INTO v_manager_id
FROM employees
WHERE department_id = (SELECT department_id FROM departments WHERE department_name = v_department_name);
DBMS_OUTPUT.PUT_LINE('Manager ID for Sales: ' || v_manager_id);
EXCEPTION
-- Specifically handling the case where more than one row is returned
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Predefined Exception: TOO_MANY_ROWS - Multiple employees found for ' || v_department_name || ' department.');
DBMS_OUTPUT.PUT_LINE('Consider using a cursor or aggregate function for multiple rows.');
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Department ' || v_department_name || ' not found.');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation This example demonstrates the TOO_MANY_ROWS
predefined exception. If the subquery or the main query implicitly returns more than one manager_id
for the 'Sales' department, this exception is raised. The WHEN TOO_MANY_ROWS
clause effectively captures this, advising the developer on how to handle scenarios where multiple records are returned, crucial for PL/SQL cursor management and query result sets.
Example 3: Oracle PL/SQL Handling DUP_VAL_ON_INDEX on User Creation
-- Assume a simple 'app_users' table with a unique constraint on 'username'
-- CREATE TABLE app_users (
-- user_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
-- username VARCHAR2(50) UNIQUE NOT NULL,
-- email VARCHAR2(100)
-- );
-- INSERT INTO app_users (username, email) VALUES ('john.doe', 'john.doe@example.com');
DECLARE
v_new_username VARCHAR2(50) := 'john.doe'; -- Existing username
v_new_email VARCHAR2(100) := 'j.doe@example.com';
BEGIN
-- Attempt to insert a user with a username that already exists, violating a unique constraint
INSERT INTO app_users (username, email)
VALUES (v_new_username, v_new_email);
COMMIT;
DBMS_OUTPUT.PUT_LINE('User ' || v_new_username || ' created successfully.');
EXCEPTION
-- Specifically handling the unique constraint violation
WHEN DUP_VAL_ON_INDEX THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('Predefined Exception: DUP_VAL_ON_INDEX - Username "' || v_new_username || '" already taken. Please choose another.');
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred during user creation: ' || SQLERRM);
END;
/
Explanation This example illustrates the DUP_VAL_ON_INDEX
predefined exception. When an INSERT
statement attempts to add a record with a username
that already exists in the app_users
table (which has a unique constraint on username
), this exception is raised. The EXCEPTION
handler prevents the insertion, rolls back the transaction, and informs the user about the duplicate key violation, a common requirement for data integrity in PL/SQL.
Example 4: Oracle PL/SQL Handling VALUE_ERROR for Input Validation
DECLARE
v_age_str VARCHAR2(10) := 'twenty'; -- Non-numeric input
v_age NUMBER;
BEGIN
-- Attempt to convert a non-numeric string to a number
v_age := TO_NUMBER(v_age_str);
DBMS_OUTPUT.PUT_LINE('Age: ' || v_age);
EXCEPTION
-- Specifically handling data conversion errors
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('Predefined Exception: VALUE_ERROR - Invalid input for age: "' || v_age_str || '". Please enter a numeric value.');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation The VALUE_ERROR
predefined exception is demonstrated here when attempting to convert the non-numeric string 'twenty' into a NUMBER
type. The EXCEPTION
block catches this data conversion issue, allowing the program to gracefully handle invalid user input without crashing. This is a crucial aspect of PL/SQL input validation and robust application design.
Example 5: Oracle PL/SQL Handling ZERO_DIVIDE in Financial Calculation
DECLARE
v_total_revenue NUMBER := 10000;
v_total_units NUMBER := 0; -- Simulating zero units sold
v_revenue_per_unit NUMBER;
BEGIN
-- Attempt to calculate revenue per unit when total units is zero
v_revenue_per_unit := v_total_revenue / v_total_units;
DBMS_OUTPUT.PUT_LINE('Revenue Per Unit: ' || v_revenue_per_unit);
EXCEPTION
-- Specifically handling division by zero
WHEN ZERO_DIVIDE THEN
DBMS_OUTPUT.PUT_LINE('Predefined Exception: ZERO_DIVIDE - Cannot calculate revenue per unit when total units is zero.');
DBMS_OUTPUT.PUT_LINE('Consider handling this by setting revenue_per_unit to 0 or NULL, or reporting the error.');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An unexpected error occurred: ' || SQLERRM);
END;
/
Explanation This example explicitly handles the ZERO_DIVIDE
predefined exception. When the calculation attempts to divide v_total_revenue
by v_total_units
, which is 0, this arithmetic error is raised. The EXCEPTION
handler provides a specific message, preventing the program from terminating and guiding the developer on how to manage such mathematical errors in PL/SQL, common in financial applications.