Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

PL/SQL & T-SQL (Oracle and SQL Server)  Module 9 – PL/SQL foundations: blocks, types, SQL and exceptions

PL/SQL exceptions and how they propagate

Handlers that catch PL/SQL errors by name, SQLCODE and SQLERRM, the path an unhandled exception takes from block to caller, and errors your code raises.

  • Intermediate
  • 20 minutes
  • Examples run with oracle-free 23.26.3.0.0, mssql 17.0.5005.3, Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: The EXCEPTION section catches an error by its name · An unhandled exception climbs to the enclosing block, then to the caller · Code can raise errors of its own, and the compiler can flag handlers. With a pass: 2 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Handle predefined exceptions by name and read SQLCODE and SQLERRM inside a handler
  • Predict which block or caller catches an exception, or whether the client reports it
  • Trace an exception raised in a declaration or inside a handler to the block that catches it
  • Use RAISE_APPLICATION_ERROR and PRAGMA EXCEPTION_INIT to give callers errors they catch by name
  • Find handlers that hide errors with compile-time warnings, and choose what a handler should do instead

Before you start

What you’ll learn · Before you start

What you will learn

  • Handle predefined exceptions by name and read SQLCODE and SQLERRM inside a handler
  • Predict which block or caller catches an exception, or whether the client reports it
  • Trace an exception raised in a declaration or inside a handler to the block that catches it
  • Use RAISE_APPLICATION_ERROR and PRAGMA EXCEPTION_INIT to give callers errors they catch by name
  • Find handlers that hide errors with compile-time warnings, and choose what a handler should do instead

Before you start

On this page

Splitting order 3, worth 900.00, into 0 instalments stops a procedure with ORA-01476, and asking for order 7, which does not exist, stops it with ORA-01403. PL/SQL exceptions are the run-time errors behind such codes: a failing statement raises one, a handler can catch it by name, and an exception that a block does not catch leaves it for the enclosing block, then for the caller.

You will catch errors by name, read their codes and follow one exception to the client, so that you can predict how they propagate in your own code.

The EXCEPTION section catches an error by its name

The first block of handlers.sql divides the 900.00 of order 3, five bags of TEA-250 at 180.00, by 0 parts. The division raises ZERO_DIVIDE, one of the predefined exception: An Oracle error that PL/SQL has given a name in its package STANDARD, such as NO_DATA_FOUND for ORA-01403 or ZERO_DIVIDE for ORA-01476, so that a handler can catch it by that name., the names that PL/SQL gives common Oracle errors: NO_DATA_FOUND is ORA-01403, ZERO_DIVIDE ORA-01476, VALUE_ERROR ORA-06502 and DUP_VAL_ON_INDEX ORA-00001. The block stops at the failing statement and turns to its EXCEPTION section, where the first exception handler: A WHEN clause in the EXCEPTION section of a PL/SQL block that names one or more exceptions and the statements to run when one of them is raised, instead of letting the error end the block. More in “PL/SQL blocks, variables and data types” that names the exception runs; WHEN a OR b names two. WHEN OTHERS catches whatever no earlier WHEN names, and it must come last, or the block does not compile (PLS-00370). Inside a handler, SQLCODE returns the error’s code, −1476 here, and SQLERRM its message. Step through the block:

Interactive · step by step One block, one error, and the handler that catches it Step through the first block of handlers.sql, from the division by 0 parts to the end of the block.
One block, one error, and the handler that catches it, step 7 of 7. Showing SELECT … INTO (v_total = 900.00), v_total / v_parts (ORA-01476 raised), The next PUT_LINE (never runs), END of the block, NO_DATA_FOUND, ZERO_DIVIDE (SQLCODE -1476), OTHERS. In focus: END of the block, zero-done.skippedZERO_DIVIDEdoes not name itafter the handlerSELECT … INTOv_total = 900.00v_total / v_partsORA-01476 raisedThe next PUT_LINEnever runsEND of the blockNO_DATA_FOUNDfirst handlerZERO_DIVIDESQLCODE -1476OTHERSthe last handlercontrol

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. The block reads order 3. SELECT … INTO v_total reads order 3, five bags of TEA-250 at 180.00, so v_total holds 900.00 and the block prints Total: 900.00.
  2. The division raises ZERO_DIVIDE. v_total / v_parts divides by 0, and PL/SQL raises the predefined exception ZERO_DIVIDE, ORA-01476. The executable part stops at that statement.
  3. The rest of the block is skipped. The PUT_LINE after the division never runs. Control leaves the statements after BEGIN and moves to the EXCEPTION section of the same block.
  4. Handlers are read from the top. The first handler names NO_DATA_FOUND, which is a different exception, so it is passed over and the next WHEN is read.
  5. The first match runs. WHEN ZERO_DIVIDE names the exception, so its statements run: SQLCODE returns -1476 and SQLERRM returns ORA-01476: divisor is equal to zero.
  6. OTHERS is not needed. Only one handler runs for an exception. WHEN OTHERS would catch any exception that no earlier WHEN names, which is why Oracle accepts it only as the last handler.
  7. The block ends normally. After the handler, control goes to the end of the block, never back to the division. The block completes, so the client that sent it sees no error at all.

After a handler, control goes to the end of its block, never back to the failing statement, so the script’s loop gives each division a block of its own:

handlers.sql: ZERO_DIVIDE caught, then a loop that carries on SQL · handlers/handlers.sql
-- Engine: Oracle AI Database 26ai Free, Release 23.26.3.0.0; user LAB in FREEPDB1. Client: SQL*Plus or SQLcl.
-- Handlers in one block. Order 3 (900.00) split into 0 parts raises ZERO_DIVIDE: the rest of the block is skipped and
-- the ZERO_DIVIDE handler prints SQLCODE and SQLERRM. The second block puts the division in an inner block with its
-- own handler, so the loop goes on after the error. The third block shows the order Oracle demands. No data changes.
SET SERVEROUTPUT ON

DECLARE
  v_total NUMBER(10, 2);
  v_parts PLS_INTEGER := 0;
BEGIN
  SELECT o.qty * s.unit_price INTO v_total
    FROM orders o JOIN stock s ON s.sku = o.sku
   WHERE o.order_id = 3;
  DBMS_OUTPUT.PUT_LINE('Total: ' || TO_CHAR(v_total, 'FM999990.00'));
  DBMS_OUTPUT.PUT_LINE('Each: ' || TO_CHAR(v_total / v_parts, 'FM999990.00'));   -- raises ZERO_DIVIDE
  DBMS_OUTPUT.PUT_LINE('never printed');
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('No such order');
  WHEN ZERO_DIVIDE THEN
    DBMS_OUTPUT.PUT_LINE('Handled: SQLCODE ' || SQLCODE || ', ' || SQLERRM);
  WHEN OTHERS THEN                          -- every other exception; it must come last
    DBMS_OUTPUT.PUT_LINE('Unexpected: ' || SQLERRM);
    RAISE;                                  -- report it, then raise it again for the client
END;
/
-- Expected (Predefined Exceptions page: ZERO_DIVIDE is ORA-01476 with code -1476; text from the ORA-01476 page):
--   Total: 900.00
--   Handled: SQLCODE -1476, ORA-01476: divisor is equal to zero

DECLARE
  v_total NUMBER(10, 2) := 900;
BEGIN
  FOR v_parts IN REVERSE 0 .. 3 LOOP
    BEGIN                                   -- inner block: its handler lets the loop go on
      DBMS_OUTPUT.PUT_LINE('split in ' || v_parts || ': ' || TO_CHAR(v_total / v_parts, 'FM999990.00'));
    EXCEPTION
      WHEN ZERO_DIVIDE THEN
        DBMS_OUTPUT.PUT_LINE('split in ' || v_parts || ': skipped');
    END;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE('loop finished');
END;
/
-- Expected (Continuing Execution After Handling Exceptions page: a handler never returns to its own block, so the
-- division gets a block of its own):
--   split in 3: 300.00
--   split in 2: 450.00
--   split in 1: 900.00
--   split in 0: skipped
--   loop finished

BEGIN
  DBMS_OUTPUT.PUT_LINE(900 / 0);
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('caught by OTHERS');
  WHEN ZERO_DIVIDE THEN
    DBMS_OUTPUT.PUT_LINE('caught by ZERO_DIVIDE');
END;
/
-- Expected (error help PLS-00370): the block does not compile, so nothing runs:
--   PLS-00370: OTHERS handler must be last among the exception handlers of a block
handlers.sql, run in the lab Python · handlers_session.py
"""handlers.sql in the lab: SQL*Plus runs it as LAB on a freshly loaded practice schema."""

from oracle_session import Lab

with Lab() as lab:
    lab.sqlplus("handlers/handlers.sql", errors=1)

Output

$ sqlplus -s lab@//localhost/FREEPDB1 @handlers/handlers.sql
Total: 900.00
Handled: SQLCODE -1476, ORA-01476: divisor is equal to zero

PL/SQL procedure successfully completed.

split in 3: 300.00
split in 2: 450.00
split in 1: 900.00
split in 0: skipped
loop finished

PL/SQL procedure successfully completed.

  WHEN OTHERS THEN
  *
ERROR at line 4:
ORA-06550: line 4, column 3:
PLS-00370: OTHERS handler must be last among the exception handlers of a block
ORA-06550: line 0, column 0:
PL/SQL: Compilation unit analysis terminated
Help: https://docs.oracle.com/error-help/db/ora-06550/

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 handlers_session.py

Expecting the block to resume after its handler

Wrong: one block holding the division and the lines that must run after it, trusting the handler to return. Right: an inner BEGIN … EXCEPTION … END; block around the risky statement, whose handler ends only that block.

Checkpoint

Check: Handlers in the EXCEPTION section

  1. Question 1 of 2 A block runs v_each := v_total / 0; then PUT_LINE('first') and PUT_LINE('second'). Its only handler is WHEN ZERO_DIVIDE THEN PUT_LINE('handled'). What does it print?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: handled, and nothing else

    A failing statement ends the executable part of its block. The first handler that names the exception runs, and the block then ends; statements that must still run need their own inner block.

  2. Question 2 of 2 Which EXCEPTION section compiles?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: WHEN NO_DATA_FOUND THEN … WHEN OTHERS THEN …

    OTHERS catches whatever no earlier WHEN names, so it is allowed only once, as the last handler.

An unhandled exception climbs to the enclosing block, then to the caller

In propagation.sql, print_instalment does its lookup and division in an inner block that handles only VALUE_ERROR. The procedure body handles ZERO_DIVIDE, and the caller, an anonymous block: A PL/SQL block without a name, DECLARE ... BEGIN ... EXCEPTION ... END, sent to the database and run once rather than stored. More in “PL/SQL and T-SQL: what they are and where they run”, handles NO_DATA_FOUND. An exception that no handler of its block names exception propagation: What happens to an exception that no handler of its block names: it leaves the block and is raised again in the enclosing block, then in the caller, until a handler catches it or the client reports it.: it leaves the block and is raised again in the enclosing block, then in the caller, at the call. If no level handles it, SQL*Plus or SQLcl prints the error with ORA-06512 lines naming each place it passed. PL/SQL exceptions climb this way from wherever they start, so knowing how they propagate tells you which handler runs:

Interactive · step by step One exception climbing from the inner block to the client Step through print_instalment(3, 0) and print_instalment(7, 2), then the call that nothing handles.
One exception climbing from the inner block to the client, step 9 of 9. Showing The client (prints ORA-01403), Caller block (no handler), print_instalment, Inner block (ORA-01403 raised), NO_DATA_FOUND (not in this block), ZERO_DIVIDE (procedure's handler), VALUE_ERROR. In focus: The client.raised againraised againunhandled errorThe clientprints ORA-01403Caller blockno handlerprint_instalmentthe procedure bodyInner blockORA-01403 raisedNO_DATA_FOUNDnot in this blockZERO_DIVIDEprocedure's handlerVALUE_ERRORinner block's handler

The drawing shows the whole scene; its 9 steps are listed below.

Every step as text
  1. Zero parts raise ZERO_DIVIDE. print_instalment(3, 0) finds order 3, worth 900.00, and divides it by 0 inside the inner block. PL/SQL raises ZERO_DIVIDE, ORA-01476, and the inner block stops.
  2. The inner handler does not match. The inner block has one handler, WHEN VALUE_ERROR. It names a different exception, so the exception leaves the inner block and is raised again in the procedure body.
  3. The procedure catches it. WHEN ZERO_DIVIDE in print_instalment matches and prints SQLCODE -1476. The procedure's own 'done' line is skipped, the call returns normally, and the caller goes on to its next call.
  4. Order 7 raises NO_DATA_FOUND. print_instalment(7, 2) looks for order 7, which does not exist. The SELECT INTO in the inner block raises NO_DATA_FOUND, ORA-01403, before any division.
  5. Up to the procedure. WHEN VALUE_ERROR does not name NO_DATA_FOUND, so the exception is raised again in the procedure body, which skips the rest of its statements.
  6. Up to the caller. The procedure's only handler names ZERO_DIVIDE, so NO_DATA_FOUND leaves print_instalment as well and is raised again in the caller block, at the call itself.
  7. The caller catches it. WHEN NO_DATA_FOUND in the caller matches and prints SQLCODE 100. The caller's last line is skipped, and its block then ends normally, so the client sees no error.
  8. A caller with no handler. The second caller block of the script makes the same call with no handler. Nothing names NO_DATA_FOUND at any level, so PL/SQL returns the error to the client.
  9. The client reports the path. SQL*Plus or SQLcl prints ORA-01403 with ORA-06512 lines naming each place the error passed, the line of the SELECT INTO in PRINT_INSTALMENT first, then line 2 of the caller block.

The script holds the procedure and both caller blocks, with the lines each one prints:

propagation.sql: one procedure, a caller with a handler and one without SQL · propagation/propagation.sql
-- Engine: Oracle AI Database 26ai Free, Release 23.26.3.0.0; user LAB in FREEPDB1. Client: SQL*Plus or SQLcl.
-- One exception climbing from an inner block to the procedure around it, then to the caller and the client.
-- print_instalment splits an order's total into equal parts: order 3 is 5 x TEA-250 at 180.00 = 900.00, and order 7
-- does not exist. examples/model/propagation_model.py prints the same path for every call. The script changes no
-- data and drops its procedure at the end.
SET SERVEROUTPUT ON

CREATE OR REPLACE PROCEDURE print_instalment (p_order_id IN NUMBER, p_parts IN NUMBER)
  AUTHID DEFINER
IS
BEGIN
  DECLARE                                   -- the inner block: the lookup and the division
    v_total NUMBER(10, 2);
  BEGIN
    SELECT o.qty * s.unit_price INTO v_total
      FROM orders o JOIN stock s ON s.sku = o.sku
     WHERE o.order_id = p_order_id;         -- order 7: NO_DATA_FOUND
    DBMS_OUTPUT.PUT_LINE('Order ' || p_order_id || ': ' || p_parts || ' parts of '
                         || TO_CHAR(v_total / p_parts, 'FM999990.00'));   -- 0 parts: ZERO_DIVIDE
  EXCEPTION
    WHEN VALUE_ERROR THEN
      DBMS_OUTPUT.PUT_LINE('inner block: VALUE_ERROR');
  END;
  DBMS_OUTPUT.PUT_LINE('Order ' || p_order_id || ': done');
EXCEPTION
  WHEN ZERO_DIVIDE THEN
    DBMS_OUTPUT.PUT_LINE('print_instalment: ZERO_DIVIDE, SQLCODE ' || SQLCODE);
END print_instalment;
/

-- The first caller handles NO_DATA_FOUND.
BEGIN
  print_instalment(3, 3);
  print_instalment(3, 0);
  print_instalment(7, 2);
  DBMS_OUTPUT.PUT_LINE('caller: all three calls done');   -- skipped: the third call fails
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('caller: NO_DATA_FOUND, SQLCODE ' || SQLCODE);
END;
/
-- Expected (Overview of Exception Handling and Exception Propagation pages; codes from Predefined Exceptions):
--   Order 3: 3 parts of 300.00
--   Order 3: done
--   print_instalment: ZERO_DIVIDE, SQLCODE -1476
--   caller: NO_DATA_FOUND, SQLCODE 100

-- The second caller handles nothing.
BEGIN
  print_instalment(7, 2);
END;
/
-- Expected: no level handles NO_DATA_FOUND, so the client prints the error stack, as the Exception Propagation
-- page shows for an unhandled exception (line 8 is the SELECT INTO of the stored procedure):
--   ORA-01403: no data found
--   ORA-06512: at "LAB.PRINT_INSTALMENT", line 8
--   ORA-06512: at line 2

DROP PROCEDURE print_instalment;
propagation.sql, run in the lab Python · propagation_session.py
"""propagation.sql in the lab: SQL*Plus runs it as LAB on a freshly loaded practice schema."""

from oracle_session import Lab

with Lab() as lab:
    lab.sqlplus("propagation/propagation.sql", errors=1)

Output

$ sqlplus -s lab@//localhost/FREEPDB1 @propagation/propagation.sql
Procedure created.

Order 3: 3 parts of 300.00
Order 3: done
print_instalment: ZERO_DIVIDE, SQLCODE -1476
caller: NO_DATA_FOUND, SQLCODE 100

PL/SQL procedure successfully completed.

BEGIN
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at "LAB.PRINT_INSTALMENT", line 8
ORA-06512: at line 2
Help: https://docs.oracle.com/error-help/db/ora-01403/



Procedure dropped.

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 propagation_session.py

The model prints the level that catches each call’s exception. Press Edit, add "NO_DATA_FOUND" to the inner block’s handlers and run it: order 7 is then caught inside the procedure, and both callers finish every call.

Which level catches each exception, modelled Python · model/propagation_model.py
"""Which handler catches an exception: Oracle's propagation rules, modelled.

The model replays the two caller blocks of propagation.sql on the practice
orders. print_instalment looks up an order and divides its total into parts
inside an inner block. The levels an exception can reach, innermost first:
the inner block, the body of print_instalment, the caller, then the client.

The rules (PL/SQL Language Reference 26ai, Overview of Exception Handling
and Exception Propagation):
1. A failing statement stops its block. The block's handlers are read from
   the top; the first WHEN that names the exception, or OTHERS, runs, and
   control then goes to the end of that block, never back.
2. With no such handler, the exception leaves the block and is raised
   again in the enclosing block, then in the caller, at the call.
3. If no level handles it, the client (SQL*Plus or SQLcl) reports it.

Edit HANDLERS or the calls and run it again: give the inner block a
NO_DATA_FOUND handler, or take the caller's handler away.
"""
from decimal import Decimal

ORDER_TOTAL = {1: Decimal("360.00"), 2: Decimal("420.00"), 3: Decimal("900.00")}
ERRORS = {  # name: (Oracle error, SQLCODE), from the Predefined Exceptions page
    "NO_DATA_FOUND": ("ORA-01403", 100),
    "ZERO_DIVIDE": ("ORA-01476", -1476),
    "VALUE_ERROR": ("ORA-06502", -6502),
}
HANDLERS = {
    "inner block": ["VALUE_ERROR"],
    "print_instalment": ["ZERO_DIVIDE"],
    "caller": ["NO_DATA_FOUND"],
}
FIRST_CALLER = [(3, 3), (3, 0), (7, 2)]   # the first caller block
SECOND_CALLER = [(7, 2)]                   # the second one, no handler


def catcher(level, handlers, name):
    """The handler of one level that catches the exception, or None."""
    for h in handlers.get(level, []):
        if h in (name, "OTHERS"):
            return h
    return None


def instalment(order_id, parts):
    """The inner block: raises an exception name, or returns the text."""
    if order_id not in ORDER_TOTAL:
        raise LookupError("NO_DATA_FOUND")
    if parts == 0:
        raise LookupError("ZERO_DIVIDE")
    each = (ORDER_TOTAL[order_id] / parts).quantize(Decimal("0.01"))
    return f"Order {order_id}: {parts} parts of {each}"


def run_caller(title, calls, handlers):
    """Runs one caller block; returns False if the client saw an error."""
    print(title)
    for order_id, parts in calls:
        print(f"  print_instalment({order_id}, {parts})")
        try:
            print("    prints:", instalment(order_id, parts))
            print(f"    prints: Order {order_id}: done")
            continue
        except LookupError as exc:
            name = str(exc)
        code, sqlcode = ERRORS[name]
        print(f"    {name} ({code}) raised in the inner block")
        for level in ("inner block", "print_instalment", "caller"):
            hit = catcher(level, handlers, name)
            if hit is None:
                print(f"    {level}: no handler names it")
                continue
            print(f"    {level}: WHEN {hit} runs, SQLCODE {sqlcode}")
            if level == "inner block":
                print(f"    prints: Order {order_id}: done")
            elif level == "print_instalment":
                print(f"    skipped: Order {order_id}: done")
                print("    the call returns normally")
            else:
                print("    skipped: the rest of the caller")
                return True
            break
        else:
            print(f"    client: reports {code} with ORA-06512 lines")
            return False
    print("  the caller ran every call")
    return True


run_caller("First caller block:", FIRST_CALLER, HANDLERS)
no_caller_handler = dict(HANDLERS, caller=[])
reached_end = run_caller("Second caller block (no handler):", SECOND_CALLER,
                         no_caller_handler)

Output

First caller block:
  print_instalment(3, 3)
    prints: Order 3: 3 parts of 300.00
    prints: Order 3: done
  print_instalment(3, 0)
    ZERO_DIVIDE (ORA-01476) raised in the inner block
    inner block: no handler names it
    print_instalment: WHEN ZERO_DIVIDE runs, SQLCODE -1476
    skipped: Order 3: done
    the call returns normally
  print_instalment(7, 2)
    NO_DATA_FOUND (ORA-01403) raised in the inner block
    inner block: no handler names it
    print_instalment: no handler names it
    caller: WHEN NO_DATA_FOUND runs, SQLCODE 100
    skipped: the rest of the caller
Second caller block (no handler):
  print_instalment(7, 2)
    NO_DATA_FOUND (ORA-01403) raised in the inner block
    inner block: no handler names it
    print_instalment: no handler names it
    caller: no handler names it
    client: reports ORA-01403 with ORA-06512 lines

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 propagation_model.py

In T-SQL an error travels outwards in a similar way, to the caller’s CATCH block, as the TRY…CATCH lesson shows. Which changes an error leaves behind is covered in SQL inside PL/SQL.

Key idea

An exception its block does not handle moves out one level at a time, to the enclosing block and then the caller, until a handler names it; with none, the client reports it.

Checkpoint

Check: How an unhandled exception travels

  1. Question 1 of 2 A caller block runs p; then q; and has WHEN NO_DATA_FOUND THEN PUT_LINE('missing'). Procedure p raises NO_DATA_FOUND and has no handler for it. What happens?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The caller prints missing, and q is never called

    An unhandled exception leaves its block and is raised again one level out, at the call. The first level whose handler names it catches it; everything between the error and that handler is skipped.

  2. Question 2 of 2 No block or caller on the way has a handler for the exception. What happens to it?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The client, such as SQL*Plus or SQLcl, reports the error with ORA-06512 lines

    The ORA-06512 lines under the error name each unit and line the exception passed on its way out.

Code can raise errors of its own, and the compiler can flag handlers

Programs also report broken rules of their own. The table names four tools for that, where else an exception can start and the setting for compiler warnings:

Name or place What it does
e_limit EXCEPTION; A user-defined exception: An exception name that a PL/SQL block, subprogram or package declares itself, such as e_limit EXCEPTION, for a rule of the program; only a RAISE in the program raises it. is a name you declare, raised only by your own RAISE e_limit;.
PRAGMA EXCEPTION_INIT: A PL/SQL declaration that ties a user-defined exception name to an error number, so that a handler written for the name catches the error with that number. It ties such a name to an error number, so a handler can catch that error by name.
RAISE_APPLICATION_ERROR Called from a stored procedure: A named unit of procedural code kept in the database and run with one call, with parameters and the permissions of the database, instead of being sent as text each time. More in “PL/SQL and T-SQL: what they are and where they run” or function, it raises an application error: An error that a stored procedure or function raises itself with RAISE_APPLICATION_ERROR, with a number from -20000 to -20999 and a message of its own; callers receive it like an Oracle error.: a number from −20000 to −20999 with your own message.
RAISE; Inside a handler, it raises the exception being handled again.
A declaration or a handler An exception can also start in a declaration’s initial value or inside a handler, which changes who can catch it.
PLSQL_WARNINGS It switches on compile-time warning: A message from the PL/SQL compiler about a stored unit that compiles but may behave wrongly, run slowly or be hard to maintain, such as a handler that hides errors; the PLSQL_WARNINGS parameter switches them on. about stored code that may behave wrongly, such as a handler that hides errors.

Checkpoint

Check: Errors your own code raises

  1. Question 1 of 2 Which statement raises an error that a caller receives as ORA-20005?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: RAISE_APPLICATION_ERROR(-20005, 'Limit exceeded'), in a stored procedure

    Application errors take numbers from −20000 to −20999 and come from RAISE_APPLICATION_ERROR in a stored procedure or function.

  2. Question 2 of 2 An OTHERS handler writes SQLERRM to a log. Which last statement passes the exception being handled on to the enclosing block?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: RAISE;

    A handler that logs and then ends in RAISE; does its own part and still lets the next level decide.

Where an exception starts decides which handler catches it, reproduced Pro: opens with a Pro, Premium or Ultimate pass

Where an exception is raised changes which handlers get a chance to catch it. Two such places, a declaration and a handler, run below on the practice data, with RAISE; and the codes that SQLCODE reports along the way.

In this part:

  • Errors raised in a declaration, traced to the handler that catches them
  • Errors raised inside a handler, and RAISE without a name

1 diagram · 5 runnable examples (Python, SQL) · about 8 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Error numbers callers can catch by name, and handlers the compiler flags Pro: opens with a Pro, Premium or Ultimate pass

A procedure that refuses work should tell its caller why, with an error number the caller can catch by name. The compiler, for its part, can point at the handlers that hide errors. Both run below on the practice accounts.

In this part:

  • Application errors that a caller catches by name
  • Numbers outside the range, and what happens to the error stack
  • Compile-time warnings that find a handler hiding errors
  • Choosing what a handler does with the error

7 runnable examples (Python, SQL) · about 13 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Practise: predict which handler runs, or whether the client sees the error

Most questions give a block and an error. Decide where the exception goes before you choose:

Practice that adapts to you

Practise this lesson

9 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 Which predefined exception does a division by zero raise?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: ZERO_DIVIDE

    Predefined exceptions are names for common Oracle errors, so a handler can say WHEN ZERO_DIVIDE instead of testing a number.

  2. Question 2 of 9 Inside a WHEN ZERO_DIVIDE THEN handler, what number does SQLCODE return?

    Type a number.

    Show the answer: Question 2 of 9

    Answer: -1476

    Inside a handler SQLCODE returns the code of the exception being handled, the negative Oracle error number, here ORA-01476. NO_DATA_FOUND is the one predefined exception with a positive code, +100.

  3. Question 3 of 9 A block ends EXCEPTION WHEN NO_DATA_FOUND OR TOO_MANY_ROWS THEN v_name := 'unknown'; WHEN OTHERS THEN RAISE; Its SELECT INTO finds two rows. What happens?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: v_name becomes unknown and the block ends normally

    WHEN a OR b lets one handler cover several exceptions; OTHERS sees only what is left.

  4. Question 4 of 9 A loop divides 900 by v_parts for 0, 1, 2 and 3, in that order, and must print a line for every value, dividing where it can. Where does the ZERO_DIVIDE handler belong?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: In an inner block around the division, inside the loop

    A handler cannot send control back to its own block, so a statement that may fail and must not stop the loop gets a block of its own.

  5. Question 5 of 9 An inner block handles NO_DATA_FOUND, the procedure around it handles VALUE_ERROR, and the caller handles nothing. The inner block raises VALUE_ERROR. Where is it handled?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: In the procedure, by WHEN VALUE_ERROR

    The search for a handler starts in the block that raised the exception and moves out one level at a time.

  6. Question 6 of 9 Procedure print_line calls lookup, then runs PUT_LINE('printed'), and handles NO_DATA_FOUND with PUT_LINE('missing'). lookup raises NO_DATA_FOUND and has no handler. What does a call of print_line print?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: missing

    Propagation skips everything between the failing call and the handler that catches it, at every level.

  7. Question 7 of 9 An unhandled error reaches SQL*Plus with ORA-06512 lines under it. What do those lines report?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: Each unit and line the error passed on its way out

    Read the stack from the top: the error itself, then the places it passed, ending at the client's own block.

  8. Question 8 of 9 What raises a user-defined exception e_limit?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: A RAISE e_limit; in your own code

    Declare the name where the rule lives and raise it with RAISE when the rule breaks.

  9. Question 9 of 9 A stored procedure must report card blocked to callers in other programs as an error of its own. Which call fits?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: RAISE_APPLICATION_ERROR(-20410, 'Card blocked')

    An application error carries your number and your message to every caller, which can catch it like any Oracle error.

Interview questions

Warm-up (fresher): which exceptions come with a name, and which do you declare yourself?

Predefined exceptions come with a name: PL/SQL declares them in package STANDARD for common Oracle errors, such as NO_DATA_FOUND for ORA-01403, and the runtime raises them by itself. A user-defined exception is a name that your own block, subprogram or package declares with EXCEPTION for a rule of yours, and only a RAISE in your code raises it. A WHEN that names either kind catches it.

Three longer questions follow: a nightly load whose failures nobody saw, a default value that stopped a procedure, and the error numbers of a package that other teams call:

  1. Mid to senior

    A nightly job updates stock prices from a supplier file, row by row, and has reported success for weeks, yet 300 of 12,000 items still carry old prices. Each row's UPDATE sits in a block ending in WHEN OTHERS THEN NULL. Explain what happened, how you would fix the job, and how you would find similar handlers.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A procedure declares v_limit NUMBER(5,2) := limit_for(p_card); and handles VALUE_ERROR by logging the card and using a limit of 500.00. Since limits of 1500.00 were added, calls for those cards fail with ORA-06502 and the log stays empty. Why does the handler never run, and how would you restructure the procedure?

    Model answer, Pro. First step and follow-ups

  3. Mid to staff

    Six teams call your payments package. It raises errors with RAISE_APPLICATION_ERROR, but its numbers grew ad hoc: -20001 means three different things, and one procedure uses -19050. Design the error contract callers can rely on: the numbers, how callers catch them by name, and how the original Oracle error stays visible.

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Which handler runs when a statement fails, and where does control go afterwards?

    The block stops at the failing statement and the first handler that names the exception runs; WHEN a OR b names two, and WHEN OTHERS catches what no earlier WHEN names and must come last, or the block does not compile (PLS-00370). Control then goes to the end of that block, never back to the failing statement.

  2. Which Oracle errors do the commonest predefined exceptions name?

    NO_DATA_FOUND is ORA-01403, ZERO_DIVIDE is ORA-01476, VALUE_ERROR is ORA-06502 and DUP_VAL_ON_INDEX is ORA-00001.

  3. What do SQLCODE and SQLERRM give you inside a handler?

    SQLCODE returns the error's code, -1476 for a division by zero, and SQLERRM its message.

  4. How do you keep the statements after a risky one running?

    Put the risky statement in an inner BEGIN ... EXCEPTION ... END; block, whose handler ends only that block. A handler never returns to the failing statement, so one block holding both the risky statement and the work after it loses the rest.

  5. Where does an exception go when its own block has no handler for it?

    It leaves the block and is raised again in the enclosing block, then in the caller, at the call, one level at a time until a handler names it. With none, SQL*Plus or SQLcl prints the error with ORA-06512 lines naming each place it passed.

  6. What is a user-defined exception, and how does a handler catch an Oracle error by name?

    e_limit EXCEPTION; declares a name raised only by your own RAISE e_limit;. PRAGMA EXCEPTION_INIT ties such a name to an error number, so a handler can catch that numbered error by name.

  7. What does RAISE_APPLICATION_ERROR raise, and what does a bare RAISE do?

    Called from a stored procedure or function, RAISE_APPLICATION_ERROR raises an application error: a number from -20000 to -20999 with your own message. RAISE; inside a handler raises the exception being handled again.

  8. What does PLSQL_WARNINGS do for exception handling?

    It switches on compile-time warnings about stored code that may behave wrongly, such as a handler that hides errors. An exception can also start in a declaration's initial value or inside a handler, which changes who can catch it.

Key takeaways

  • A failing statement stops its block, the first handler that names the exception runs, with OTHERS last, and control then goes to the end of that block.
  • Predefined exceptions name Oracle’s common errors, and inside a handler SQLCODE and SQLERRM give the code and the message.
  • An exception that no handler names leaves its block for the enclosing block, then the caller, and reaches the client when no level handles it.
  • PL/SQL exceptions can also come from your own code, raised by name or with an error number, and the compiler can warn about handlers that hide them.

Terms in this lesson

application error
An error that a stored procedure or function raises itself with RAISE_APPLICATION_ERROR, with a number from -20000 to -20999 and a message of its own; callers receive it like an Oracle error.
compile-time warning
A message from the PL/SQL compiler about a stored unit that compiles but may behave wrongly, run slowly or be hard to maintain, such as a handler that hides errors; the PLSQL_WARNINGS parameter switches them on.
exception propagation
What happens to an exception that no handler of its block names: it leaves the block and is raised again in the enclosing block, then in the caller, until a handler catches it or the client reports it.
PRAGMA EXCEPTION_INIT
A PL/SQL declaration that ties a user-defined exception name to an error number, so that a handler written for the name catches the error with that number.
predefined exception
An Oracle error that PL/SQL has given a name in its package STANDARD, such as NO_DATA_FOUND for ORA-01403 or ZERO_DIVIDE for ORA-01476, so that a handler can catch it by that name.
user-defined exception
An exception name that a PL/SQL block, subprogram or package declares itself, such as e_limit EXCEPTION, for a rule of the program; only a RAISE in the program raises it.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.