PL/SQL Stored Procedures and Functions
- PL/SQL Stored Procedures and Functions is a core concept in PL/SQL that every Database developer should understand to build modular, secure, and fast applications.
- Procedures are verbs (actions), and Functions are nouns (values). Choose based on whether your primary goal is to change data or calculate it.
- Encapsulating logic in the database creates a 'Single Source of Truth' for business rules, preventing different apps from calculating the same data differently.
- PL/SQL stored procedures and functions are named, compiled subprograms that encapsulate SQL and PL/SQL logic inside the database
- Procedures perform actions (DML, batch tasks) and return values via OUT parameters; Functions compute and return exactly one value via RETURN
- Compiled once and cached in Oracle's library cache — every call skips parsing, giving consistent high-performance execution
- Parameter modes: IN (default, read-only), OUT (write-back to caller), IN OUT (bidirectional) — each passed by reference
- Never perform DML inside a function called from SQL — it violates purity level WNDS and throws ORA-14551
- 'WHEN OTHERS THEN NULL' is the most dangerous anti-pattern — it swallows errors and makes the application blind to failures
Production Incident
Production Debug GuideCommon PL/SQL procedure and function failures and how to diagnose them
NVL() or COALESCE() to provide defaults.PL/SQL stored procedures and functions are schema objects that encapsulate SQL and PL/SQL statements into reusable, named units. By moving logic from the application tier to the database tier, you reduce network traffic, enforce security boundaries, and ensure complex business rules are applied consistently across all consuming services.
These subprograms solve two production problems simultaneously: repeated logic across applications and excessive network round-trips. Oracle parses, compiles, and optimizes the code once, storing the executable form in the library cache. Every application — Java microservice, Python script, or legacy ERP — benefits from the same execution plan.
By the end, you'll have the conceptual understanding and practical code examples to use PL/SQL stored procedures and functions with confidence, including the production failure modes that catch teams off guard.
The Core Mechanics: Procedures for Action, Functions for Logic
PL/SQL Stored Procedures and Functions is a core feature of PL/SQL. It was designed to solve the problem of repeated logic and high network latency. Procedures are typically used to perform an action (DML operations like INSERT, UPDATE, or DELETE), while Functions are designed to compute and return a single value.
They exist to provide 'compiled' performance — Oracle parses, compiles, and optimizes the code once, storing the executable form in the database's library cache. This ensures that every application — whether it's a Java microservice, a Python script, or a legacy ERP — benefits from the same high-performance execution plan and centralized business logic. Furthermore, by granting EXECUTE privileges on a procedure rather than direct SELECT/UPDATE access on tables, you implement a robust 'Principle of Least Privilege' security model.
-- io.thecodeforge: Function to calculate discounted price -- Optimized with DETERMINISTIC ifecodeforge.fn_get_discounted_price( p_price IN NUMBER, p results are consistent for inputs CREATE OR REPLACE FUNCTION io.th_discount IN NUMBER ) RETURN NUMBER DETERMINISTIC IS BEGIN -- Functions must return a value and avoid side effects RETURN ROUND(p_price * (1 - (p_discount / 100)), 2); END fn_get_discounted_price; / -- io.thecodeforge: Procedure to update project budget CREATE OR REPLACE PROCEDURE io.thecodeforge.sp_update_budget( p_project_id IN NUMBER, p_new_budget IN NUMBER ) IS v_old_budget NUMBER; BEGIN -- Logical validation before DML SELECT budget INTO v_old_budget FROM forge_projects WHERE id = p_project_id; IF p_new_budget < 0 THEN RAISE_APPLICATION_ERROR(-20001, 'Budget cannot be negative.'); END IF; UPDATE forge_projects SET budget = p_new_budget, last_updated = SYSDATE WHERE id = p_project_id; -- Commit logic usually belongs to the caller, but can be here for batch tasks COMMIT; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20002, 'Project ID not found.'); WHEN OTHERS THEN ROLLBACK; RAISE; -- Propagate error to the application tier END sp_update_budget; /
Procedure SP_UPDATE_BUDGET compiled.
- Procedures: execute DML, run batch jobs, send notifications — they change the world
- Functions: compute values, look up data, transform inputs — they answer questions
- A procedure returns control to the caller; a function returns a value to the caller
- Functions called from SQL must be pure (no DML) — procedures have no such restriction
- Grant EXECUTE on subprograms instead of direct table access for least-privilege security
Parameter Modes and Production Gotchas
When learning PL/SQL Stored Procedures and Functions, most developers hit the same set of gotchas. A frequent mistake is performing DML (Insert/Update/Delete) inside a Function; while technically possible using 'Autonomous Transactions,' it prevents the function from being used in a standard SELECT statement because it violates purity levels (WNDS - Writes No Database State).
Another error is failing to specify parameter modes (IN, OUT, IN OUT) correctly. By default, parameters are 'IN' (read-only). If you need to return multiple values from a single subprogram, a Procedure with 'OUT' parameters is your best friend. However, be cautious with 'IN OUT' parameters, as they can lead to side effects where the calling environment's variable is mutated unexpectedly. Production-grade code should also consider the 'NOCOPY' hint for large collection parameters to avoid expensive memory copying.
-- io.thecodeforge: Using OUT parameters correctly for metadata retrieval CREATE OR REPLACE PROCEDURE io.thecodeforge.sp_get_project_meta( p_id IN NUMBER, p_name OUT VARCHAR2, p_version OUT VARCHAR2, p_status OUT VARCHAR2 ) IS BEGIN -- One trip to the DB to fetch multiple values (Network Efficiency) SELECT name, version_info, status INTO p_name, p_version, p_status FROM forge_projects WHERE id = p_id; EXCEPTION WHEN NO_DATA_FOUND THEN p_name := 'NOT_FOUND'; p_version := '0.0.0'; p_status := 'INACTIVE'; WHEN TOO_MANY_ROWS THEN RAISE_APPLICATION_ERROR(-20003, 'Data integrity error: duplicate project IDs.'); END sp_get_project_meta; /
Exception Handling: The Production Safety Net
Exception handling in PL/SQL is not optional — it is the difference between a procedure that fails gracefully with a meaningful error and one that silently corrupts data or leaves the application blind. Oracle provides named exceptions (NO_DATA_FOUND, DUP_VAL_ON_INDEX, TOO_MANY_ROWS) for common conditions, and RAISE_APPLICATION_ERROR for custom business-rule violations.
The single most dangerous pattern in production PL/SQL is WHEN OTHERS THEN NULL. It catches every exception — including ones you never anticipated — and discards them silently. The procedure completes 'successfully,' the caller thinks the operation worked, but the data was never written. This pattern has caused silent data corruption in every Oracle shop I've worked in.
Production-grade exception handling follows three rules: (1) catch specific named exceptions first, (2) log unexpected errors before re-raising them, (3) never let an exception handler exit without either handling the error properly or propagating it to the caller.
-- io.thecodeforge: Production-grade exception handling pattern CREATE OR REPLACE PROCEDURE io.thecodeforge.sp_process_payment( p_order_id IN NUMBER, p_amount IN NUMBER, p_status OUT VARCHAR2 ) IS v_customer_id NUMBER; v_balance NUMBER; BEGIN -- Validate order exists SELECT customer_id INTO v_customer_id FROM forge_orders WHERE order_id = p_order_id; -- Check balance SELECT balance INTO v_balance FROM forge_customers WHERE customer_id = v_customer_id; IF v_balance < p_amount THEN RAISE_APPLICATION_ERROR(-20100, 'Insufficient balance for order ' || p_order_id); END IF; -- Deduct and record UPDATE forge_customers SET balance = balance - p_amount WHERE customer_id = v_customer_id; INSERT INTO forge_payments (order_id, amount, paid_on) VALUES (p_order_id, p_amount, SYSDATE); COMMIT; p_status := 'SUCCESS'; EXCEPTION WHEN NO_DATA_FOUND THEN ROLLBACK; p_status := 'ORDER_NOT_FOUND'; WHEN DUP_VAL_ON_INDEX THEN ROLLBACK; p_status := 'DUPLICATE_PAYMENT'; WHEN OTHERS THEN ROLLBACK; -- Log the unexpected error for investigation io.thecodeforge.logging.log_error( p_proc => 'sp_process_payment', p_errm => SQLERRM, p_ctx => 'order_id=' || p_order_id ); RAISE; -- Always re-raise unexpected errors END sp_process_payment; /
- Named exceptions first: catch NO_DATA_FOUND, DUP_VAL_ON_INDEX, TOO_MANY_ROWS explicitly
- WHEN OTHERS is your last resort — always log and re-raise, never swallow
- ROLLBACK before handling to leave the transaction in a clean state
- Use RAISE_APPLICATION_ERROR with custom codes (-20000 to -20999) for business-rule violations
- Pass context (order_id, customer_id) to the error log — generic 'error occurred' messages are useless
| Feature | Stored Procedure | Function |
|---|---|---|
| Return Value | Does not return a value via header; uses OUT parameters if needed. | Must return exactly one value via the RETURN clause. |
| Call in SQL | Cannot be called in a SELECT or WHERE clause. | Can be called in SELECT/WHERE if it is 'pure' (no DML). |
| DML Operations | Standard use case; expected to change database state. | Restricted; if called from SQL, it cannot perform DML. |
| Primary Purpose | Executing business tasks, batch processing, and DML. | Computing data, transformations, and returning specific values. |
| Parameter Modes | Supports IN, OUT, and IN OUT modes. | Supports IN (standard), OUT/IN OUT (rarely used/discouraged). |
🎯 Key Takeaways
- PL/SQL Stored Procedures and Functions is a core concept in PL/SQL that every Database developer should understand to build modular, secure, and fast applications.
- Procedures are verbs (actions), and Functions are nouns (values). Choose based on whether your primary goal is to change data or calculate it.
- Encapsulating logic in the database creates a 'Single Source of Truth' for business rules, preventing different apps from calculating the same data differently.
- Always use explicit exception handling to ensure the integrity of your DML and to provide meaningful feedback to the middleware tier.
- Read the official documentation — it contains edge cases like 'Result Cache' hints which can drastically speed up functions that are called frequently with the same parameters.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QWhat is the fundamental difference between a Procedure and a Function? When is it illegal to use a Function in a SELECT statement?JuniorReveal
- QExplain the three parameter modes: IN, OUT, and IN OUT. Which is the default, and how is it passed (by value or reference)?JuniorReveal
- QWhat is 'RAISE_APPLICATION_ERROR' and why is it better than just letting Oracle throw a generic system error?Mid-levelReveal
- QHow do Stored Procedures improve performance for an application communicating over a network? Mention 'Context Switching'.Mid-levelReveal
- QCan a PL/SQL Function be overloaded? If so, what are the requirements regarding parameter signatures?Mid-levelReveal
- QWhat is the 'PRAGMA AUTONOMOUS_TRANSACTION' and how would you use it inside a function to log errors without rolling back the main transaction?SeniorReveal
Frequently Asked Questions
Can a stored procedure call a function?
Yes. A procedure can call a function anywhere an expression is allowed — in variable assignments, IF conditions, and SQL statements inside the procedure. The function must be compiled and the caller must have EXECUTE privilege on it.
What happens if a procedure with OUT parameters raises an exception before assigning values?
The OUT parameters retain their pre-call values (typically NULL unless the caller initialized them). The caller should always check SQLCODE or catch exceptions after the call rather than assuming OUT parameters were populated.
Can I use COMMIT inside a function?
Yes, but only if the function is not called from SQL (SELECT/WHERE). If called from SQL and it contains COMMIT, you must use PRAGMA AUTONOMOUS_TRANSACTION. Best practice: avoid COMMIT inside functions — let the caller control transaction boundaries.
What is the RESULT_CACHE hint and when should I use it?
RESULT_CACHE tells Oracle to cache the function's return value for a given set of input parameters. Subsequent calls with the same inputs return the cached result without re-executing. Use it for expensive, frequently-called functions where the inputs repeat. It is invalidated automatically when underlying tables are modified.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.