PostgreSQL Error Handling
Manage exceptions and errors gracefully
🛡️ What is Error Handling?
Error handling in PostgreSQL allows you to catch and manage exceptions in stored procedures and functions. It prevents errors from crashing your application and enables custom error responses for better user experience.
-- Basic error handling syntax
BEGIN
-- Code that might fail
EXCEPTION
WHEN error_condition THEN
-- Handle the error
END;
Key Error Handling Concepts
EXCEPTION
Catch and handle errors
EXCEPTION WHEN ... THEN
Error Codes
Specific error conditions
division_by_zero, unique_violation
RAISE
Generate custom errors
RAISE EXCEPTION 'message'
Error Info
Access error details
SQLERRM, SQLSTATE
🔹 Basic Exception Handling
The EXCEPTION block catches errors that occur in the BEGIN section. When an error happens, control jumps to the matching WHEN clause, allowing you to handle the error gracefully.
-- Create sample table
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE,
price DECIMAL(10,2)
);
-- Function with basic error handling
CREATE OR REPLACE FUNCTION add_product(
p_name VARCHAR,
p_price DECIMAL
)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO products (name, price)
VALUES (p_name, p_price);
RETURN 'Product added successfully';
EXCEPTION
WHEN unique_violation THEN
RETURN 'Error: Product name already exists';
WHEN OTHERS THEN
RETURN 'Error: Could not add product';
END;
$$;
-- Test the function
SELECT add_product('Laptop', 999.99);
SELECT add_product('Laptop', 899.99); -- Duplicate name
Result:
add_product -------------------------------- Product added successfully add_product -------------------------------- Error: Product name already exists
🔹 Common Error Conditions
PostgreSQL provides predefined error conditions for common database errors. Using specific error conditions allows you to handle different error types with appropriate responses instead of generic error messages.
-- Function handling multiple error types
CREATE OR REPLACE FUNCTION safe_divide(
numerator DECIMAL,
denominator DECIMAL
)
RETURNS DECIMAL
LANGUAGE plpgsql
AS $$
BEGIN
RETURN numerator / denominator;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'Cannot divide by zero';
RETURN NULL;
WHEN numeric_value_out_of_range THEN
RAISE NOTICE 'Result is too large';
RETURN NULL;
WHEN OTHERS THEN
RAISE NOTICE 'Unexpected error occurred';
RETURN NULL;
END;
$$;
-- Test different error scenarios
SELECT safe_divide(100, 0); -- Division by zero
SELECT safe_divide(100, 5); -- Success
Common Error Conditions:
division_by_zero - Divide by zero unique_violation - Duplicate key foreign_key_violation - FK constraint failed not_null_violation - NULL in NOT NULL column check_violation - CHECK constraint failed numeric_value_out_of_range - Number too large/small
🔹 RAISE Statements
RAISE generates messages and errors at different severity levels. You can use it to provide feedback, warnings, or stop execution with custom error messages that help users understand what went wrong.
-- Function with RAISE statements
CREATE OR REPLACE FUNCTION update_price(
p_id INTEGER,
p_new_price DECIMAL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
-- Validation with custom error
IF p_new_price < 0 THEN
RAISE EXCEPTION 'Price cannot be negative: %', p_new_price;
END IF;
IF p_new_price = 0 THEN
RAISE WARNING 'Price is zero for product %', p_id;
END IF;
UPDATE products SET price = p_new_price WHERE id = p_id;
IF NOT FOUND THEN
RAISE NOTICE 'Product % not found', p_id;
ELSE
RAISE NOTICE 'Price updated successfully';
END IF;
END;
$$;
-- Test with different scenarios
SELECT update_price(1, 899.99); -- Success
SELECT update_price(1, 0); -- Warning
SELECT update_price(999, 100); -- Not found
SELECT update_price(1, -50); -- Exception
RAISE Levels:
DEBUG - Debugging information LOG - Server log messages INFO - Informational messages NOTICE - User notifications (default) WARNING - Warning messages EXCEPTION - Error, aborts transaction
🔹 Getting Error Information
PostgreSQL provides special variables to access detailed error information within exception handlers. These variables help you log errors, debug issues, and provide meaningful feedback to users about what went wrong.
-- Function that captures error details
CREATE OR REPLACE FUNCTION process_order(order_id INTEGER)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
error_message TEXT;
error_detail TEXT;
error_hint TEXT;
error_code TEXT;
BEGIN
-- Some operation that might fail
UPDATE products SET price = price * 1.1 WHERE id = order_id;
RETURN 'Order processed successfully';
EXCEPTION
WHEN OTHERS THEN
GET STACKED DIAGNOSTICS
error_message = MESSAGE_TEXT,
error_detail = PG_EXCEPTION_DETAIL,
error_hint = PG_EXCEPTION_HINT,
error_code = RETURNED_SQLSTATE;
RAISE NOTICE 'Error Code: %', error_code;
RAISE NOTICE 'Error Message: %', error_message;
RAISE NOTICE 'Error Detail: %', error_detail;
RETURN 'Error processing order';
END;
$$;
Available Error Variables:
SQLERRM - Error message text SQLSTATE - 5-character error code MESSAGE_TEXT - Primary error message PG_EXCEPTION_DETAIL - Detailed error information PG_EXCEPTION_HINT - Hint for fixing error PG_EXCEPTION_CONTEXT - Call stack trace
🔹 Nested Exception Handling
You can nest BEGIN-EXCEPTION blocks to handle errors at different levels. Inner blocks can catch specific errors while outer blocks handle broader error categories, providing layered error management.
-- Function with nested exception handling
CREATE OR REPLACE FUNCTION complex_operation(p_id INTEGER)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
result TEXT;
BEGIN
-- Outer block
BEGIN
-- Inner block with specific error handling
BEGIN
UPDATE products SET price = price / 0 WHERE id = p_id;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'Inner: Caught division by zero';
RETURN 'Handled division error';
END;
-- This won't execute if inner block returns
RETURN 'Operation completed';
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Outer: Caught unexpected error';
RETURN 'Handled unexpected error';
END;
END;
$$;
🔹 Transaction Error Handling
When errors occur in transactions, you can use exception handling to rollback changes and maintain data consistency. This ensures that partial updates don't leave your database in an inconsistent state.
-- Procedure with transaction error handling
CREATE OR REPLACE PROCEDURE transfer_funds(
from_account INTEGER,
to_account INTEGER,
amount DECIMAL
)
LANGUAGE plpgsql
AS $$
BEGIN
BEGIN
-- Start transaction operations
UPDATE accounts
SET balance = balance - amount
WHERE id = from_account;
-- Simulate error check
IF amount > 10000 THEN
RAISE EXCEPTION 'Transfer amount exceeds limit';
END IF;
UPDATE accounts
SET balance = balance + amount
WHERE id = to_account;
RAISE NOTICE 'Transfer completed successfully';
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Transfer failed: %', SQLERRM;
RAISE EXCEPTION 'Transaction rolled back';
END;
END;
$$;
🔹 Error Handling Best Practices
Effective error handling improves application reliability and user experience. Follow these practices to create robust database functions that handle errors gracefully and provide clear feedback when problems occur.
- Be Specific: Catch specific errors rather than using WHEN OTHERS
- Log Errors: Always log error details for debugging
- User-Friendly: Provide clear error messages to users
- Clean Up: Release resources in exception handlers
- Don't Hide: Don't catch errors you can't handle properly
- Test Errors: Test error handling paths thoroughly
- Document: Document what errors functions can raise