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 control structures and iterators

IF and ELSIF, CASE statements that can raise CASE_NOT_FOUND, loops with EXIT, CONTINUE and labels, then the iterators and extended CASE of newer releases.

  • Beginner
  • 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: IF runs the first branch whose condition is TRUE · A CASE statement must match, or it raises an error · A loop repeats until EXIT, its test or its range ends it · Newer releases add iterators and extended CASE. With a pass: 2 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Use IF, ELSIF and ELSE, and predict which branch a NULL value takes
  • Choose between a CASE statement and a CASE expression for a value that no WHEN matches
  • Write basic, WHILE and FOR loops with EXIT, CONTINUE and labels
  • Use the iteration controls of 21c and later to step, chain and filter a FOR loop and to walk a sparse collection
  • Rewrite an extended CASE of 26ai as a searched CASE that also runs on 19c

Before you start

What you’ll learn · Before you start

What you will learn

  • Use IF, ELSIF and ELSE, and predict which branch a NULL value takes
  • Choose between a CASE statement and a CASE expression for a value that no WHEN matches
  • Write basic, WHILE and FOR loops with EXIT, CONTINUE and labels
  • Use the iteration controls of 21c and later to step, chain and filter a FOR loop and to walk a sparse collection
  • Rewrite an extended CASE of 26ai as a searched CASE that also runs on 19c

Before you start

On this page

An order worth 900.00 costs nothing to deliver under the lesson’s rule, and so does an order whose value is still NULL, because the IF statement that sets the fee sends every unknown value to its ELSE. PL/SQL control structures decide which statements run and how often, and each has a rule like that one for the values nobody expected.

You will choose branches with IF and CASE, write the three kinds of loop, and then meet the iterators of newer releases.

IF runs the first branch whose condition is TRUE

The practice orders are worth 360.00, 420.00 and 900.00, and a made-up rule charges 49.00 for delivery below 400.00, 29.00 below 800.00 and nothing from 800.00 up. IF … THEN … ELSIF … ELSE … END IF; states the rule in one statement. Oracle tests the conditions from the top, runs the first branch whose condition is TRUE, and runs ELSE only when none was. The keyword is ELSIF, one word without a second E.

A comparison with NULL is neither TRUE nor FALSE under three-valued logic: The logic SQL uses once NULL is involved: a comparison can be true, false or UNKNOWN, and IF, WHILE, WHERE and each WHEN of a CASE act only on true. More in “T-SQL control flow: IF, WHILE and CASE”, so an order not priced yet fails both tests and lands in ELSE with a fee of 0.00. The second block of the script tests IS NULL first and leaves that fee unset on purpose:

if_branches.sql: delivery fees, and the NULL that reached ELSE SQL · branches/if_branches.sql
-- Engine: Oracle AI Database 26ai Free, Release 23.26.3.0.0; user LAB in FREEPDB1. Client: SQL*Plus or SQLcl.
-- IF ... ELSIF ... ELSE sets the delivery fee of the three practice orders from their values (qty x unit_price):
-- 360.00, 420.00 and 900.00. An order not priced yet has the value NULL, fails every comparison and lands in ELSE,
-- so the second version tests IS NULL first. control_model.py prints the same fees. Nothing is stored.
SET SERVEROUTPUT ON

DECLARE
  PROCEDURE show_fee (p_order PLS_INTEGER, p_value NUMBER) IS
    v_fee NUMBER(6, 2);
  BEGIN
    IF p_value < 400 THEN
      v_fee := 49.00;
    ELSIF p_value < 800 THEN                      -- ELSIF: one word, no E before IF
      v_fee := 29.00;
    ELSE
      v_fee := 0;
    END IF;
    DBMS_OUTPUT.PUT_LINE('order ' || p_order || ': value ' || NVL(TO_CHAR(p_value, 'FM9990.00'), 'NULL')
                         || ', fee ' || TO_CHAR(v_fee, 'FM990.00'));
  END show_fee;
BEGIN
  show_fee(1, 360.00);     -- 2 x TEA-250 at 180.00
  show_fee(2, 420.00);     -- 1 x RICE-5K at 420.00
  show_fee(3, 900.00);     -- 5 x TEA-250 at 180.00
  show_fee(4, NULL);       -- an order whose price is not known yet
END;
/
-- Expected output (Conditional Selection Statements: the first TRUE condition wins; no TRUE condition runs ELSE):
--   order 1: value 360.00, fee 49.00
--   order 2: value 420.00, fee 29.00
--   order 3: value 900.00, fee 0.00
--   order 4: value NULL, fee 0.00          <- NULL < 400 is not TRUE, and neither is NULL < 800
--   PL/SQL procedure successfully completed.

DECLARE
  PROCEDURE show_fee (p_order PLS_INTEGER, p_value NUMBER) IS
    v_fee NUMBER(6, 2);
  BEGIN
    IF p_value IS NULL THEN
      v_fee := NULL;                              -- unknown on purpose: the caller must price the order first
    ELSIF p_value < 400 THEN
      v_fee := 49.00;
    ELSIF p_value < 800 THEN
      v_fee := 29.00;
    ELSE
      v_fee := 0;
    END IF;
    DBMS_OUTPUT.PUT_LINE('order ' || p_order || ': fee ' || NVL(TO_CHAR(v_fee, 'FM990.00'), 'not set'));
  END show_fee;
BEGIN
  show_fee(3, 900.00);
  show_fee(4, NULL);
END;
/
-- Expected output:
--   order 3: fee 0.00
--   order 4: fee not set
--   PL/SQL procedure successfully completed.
if_branches.sql, run in the lab Python · if_branches_session.py
"""if_branches.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("branches/if_branches.sql")

Output

$ sqlplus -s lab@//localhost/FREEPDB1 @branches/if_branches.sql
order 1: value 360.00, fee 49.00
order 2: value 420.00, fee 29.00
order 3: value 900.00, fee 0.00
order 4: value NULL, fee 0.00

PL/SQL procedure successfully completed.

order 3: fee 0.00
order 4: fee not set

PL/SQL procedure successfully completed.

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

The model applies the same rules, and the CASE rules of the next section, to the same values. Press Edit, add an order worth 799.99 and run it:

Branches and loops of the first three sections, modelled Python · model/control_model.py
"""The choices and loops of the lesson's first three sections, worked out the way PL/SQL runs them.

1. IF ... ELSIF ... ELSE runs the first branch whose condition is TRUE. A
   comparison with NULL is unknown, never TRUE, so an order whose value is NULL
   skips every test and lands in ELSE (None stands for NULL here).
2. A CASE expression with no matching WHEN and no ELSE gives NULL; a CASE
   statement raises CASE_NOT_FOUND (ORA-06592) instead.
3. The loops of loops.sql: FOR with CONTINUE WHEN and EXIT WHEN, and a WHILE
   loop that rounds to the paisa each year, as ROUND(x, 2) does.
Edit ORDERS, KINDS or the balances and run it again.
"""
from decimal import ROUND_HALF_UP, Decimal

PAISA = Decimal("0.01")
ORDERS = [(1, Decimal("360.00")), (2, Decimal("420.00")), (3, Decimal("900.00")), (4, None)]
KINDS = ["savings", "current", "deposit"]
RATES = {"savings": Decimal("3"), "current": Decimal("0")}
BALANCES = {1: Decimal("2500.00"), 2: Decimal("18000.00"), 3: Decimal("760.25"), 4: Decimal("41250.50")}


def below(value, limit):
    """value < limit with NULL: None (unknown) when value is None."""
    return None if value is None else value < limit


def fee(value):
    if below(value, 400) is True:
        return "IF", Decimal("49.00")
    elif below(value, 800) is True:
        return "ELSIF", Decimal("29.00")
    else:
        return "ELSE", Decimal("0.00")


print("1. Delivery fee by order value")
for order_id, value in ORDERS:
    branch, amount = fee(value)
    shown = "NULL" if value is None else value
    print(f"  order {order_id}: value {shown} -> {branch}, fee {amount}")

print()
print("2. Interest rate by account kind")
for kind in KINDS:
    expression = RATES.get(kind)
    print(f"  {kind}: expression gives [{'' if expression is None else expression}]")
    if kind in RATES:
        print(f"  {kind}: statement assigns {RATES[kind]}")
    else:
        print(f"  {kind}: statement raises CASE_NOT_FOUND (ORA-06592)")

print()
print("3a. FOR id IN 1..4, CONTINUE WHEN balance < 1000, EXIT WHEN total > 50000")
total = Decimal("0")
for account_id in range(1, 5):
    balance = BALANCES[account_id]
    if balance < 1000:
        print(f"  account {account_id}: {balance} skipped by CONTINUE")
        continue
    total += balance
    print(f"  account {account_id}: {balance} added, total {total}")
    if total > 50000:
        print(f"  EXIT after account {account_id}")
        break

balance, years = Decimal("760.25"), 0
while balance < 1000:
    balance = (balance * Decimal("1.03")).quantize(PAISA, rounding=ROUND_HALF_UP)
    years += 1
print()
print(f"3b. WHILE balance < 1000 at 3 % a year: {years} years, ending at {balance}")

Output

1. Delivery fee by order value
  order 1: value 360.00 -> IF, fee 49.00
  order 2: value 420.00 -> ELSIF, fee 29.00
  order 3: value 900.00 -> ELSE, fee 0.00
  order 4: value NULL -> ELSE, fee 0.00

2. Interest rate by account kind
  savings: expression gives [3]
  savings: statement assigns 3
  current: expression gives [0]
  current: statement assigns 0
  deposit: expression gives []
  deposit: statement raises CASE_NOT_FOUND (ORA-06592)

3a. FOR id IN 1..4, CONTINUE WHEN balance < 1000, EXIT WHEN total > 50000
  account 1: 2500.00 added, total 2500.00
  account 2: 18000.00 added, total 20500.00
  account 3: 760.25 skipped by CONTINUE
  account 4: 41250.50 added, total 61750.50
  EXIT after account 4

3b. WHILE balance < 1000 at 3 % a year: 10 years, ending at 1021.71

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

A CASE statement must match, or it raises an error

A simple CASE compares one value, the selector, with the value after each WHEN; a searched CASE has no selector and tests a full condition after each WHEN. In both, the first match wins and nothing falls through to the next WHEN. The difference that hurts lies elsewhere. A CASE expression without ELSE gives NULL when nothing matches, while a CASE statement without ELSE raises the predefined exception CASE_NOT_FOUND, ORA-06592. Follow one statement for the kinds current and deposit:

Interactive · step by step A CASE statement picks one branch, or raises an error Step through the CASE statement of case_choice.sql for the kinds current and deposit.
A CASE statement picks one branch, or raises an error, step 6 of 6. Showing CASE p_kind (p_kind = 'deposit'), WHEN 'savings', WHEN 'current', No WHEN matched, v_rate := 3.00, v_rate := 0, CASE_NOT_FOUND. In focus: CASE_NOT_FOUND.not equalnot equalequalequalno ELSECASE p_kindp_kind = 'deposit'WHEN 'savings'WHEN 'current'No WHEN matchedv_rate := 3.00v_rate := 0CASE_NOT_FOUNDexpression: NULL

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

Every step as text
  1. The selector is read once. show_rate('current') starts the CASE statement. Oracle evaluates the selector p_kind once, and then compares that one value with each WHEN in turn, from the top.
  2. The first WHEN does not match. 'current' is not 'savings', so the statement after the first WHEN is skipped and the comparison moves on to the next WHEN.
  3. The second WHEN matches. 'current' equals 'current': v_rate := 0 runs and the CASE statement ends. There is no fall-through, so no later WHEN is even looked at.
  4. A kind that no WHEN names. show_rate('deposit') runs the same statement. Neither WHEN matches, and the statement has no ELSE to run instead.
  5. The statement raises an error. With no match and no ELSE, a CASE statement raises the predefined exception CASE_NOT_FOUND, ORA-06592. The handler of show_rate catches it and prints a line instead of letting the error end the block.
  6. The expression gives NULL. The CASE expression earlier in show_rate met the same kind and returned NULL without any error, so v_rate printed as an empty pair of brackets.

The script sends three account kinds through both forms:

case_choice.sql: a CASE expression and a CASE statement, no ELSE SQL · case/case_choice.sql
-- Engine: Oracle AI Database 26ai Free, Release 23.26.3.0.0; user LAB in FREEPDB1. Client: SQL*Plus or SQLcl.
-- The same rule twice, interest rates by account kind: once as a CASE expression and once as a CASE statement,
-- neither with ELSE. For the kind 'deposit', which no WHEN names, the expression gives NULL and the statement
-- raises CASE_NOT_FOUND (ORA-06592). The rates are made up; control_model.py prints the same lines.
SET SERVEROUTPUT ON

DECLARE
  PROCEDURE show_rate (p_kind VARCHAR2) IS
    v_rate NUMBER(5, 2);
  BEGIN
    v_rate := CASE p_kind                          -- a CASE expression: it has a value
                WHEN 'savings' THEN 3.00
                WHEN 'current' THEN 0
              END;
    DBMS_OUTPUT.PUT_LINE(p_kind || ': expression gives [' || v_rate || ']');
    CASE p_kind                                    -- a CASE statement: it runs statements
      WHEN 'savings' THEN v_rate := 3.00;
      WHEN 'current' THEN v_rate := 0;
    END CASE;
    DBMS_OUTPUT.PUT_LINE(p_kind || ': statement assigns ' || v_rate);
  EXCEPTION
    WHEN CASE_NOT_FOUND THEN
      DBMS_OUTPUT.PUT_LINE(p_kind || ': statement raised CASE_NOT_FOUND, SQLCODE ' || SQLCODE);
  END show_rate;
BEGIN
  show_rate('savings');
  show_rate('current');
  show_rate('deposit');
END;
/
-- Expected output (Expression page: a CASE expression without ELSE returns NULL; CASE Statement page: without ELSE,
-- no match raises CASE_NOT_FOUND):
--   savings: expression gives [3]
--   savings: statement assigns 3
--   current: expression gives [0]
--   current: statement assigns 0
--   deposit: expression gives []
--   deposit: statement raised CASE_NOT_FOUND, SQLCODE -6592
--   PL/SQL procedure successfully completed.
case_choice.sql, run in the lab Python · case_choice_session.py
"""case_choice.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("case/case_choice.sql")

Output

$ sqlplus -s lab@//localhost/FREEPDB1 @case/case_choice.sql
savings: expression gives [3]
savings: statement assigns 3
current: expression gives [0]
current: statement assigns 0
deposit: expression gives []
deposit: statement raised CASE_NOT_FOUND, SQLCODE -6592

PL/SQL procedure successfully completed.

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

A NULL selector never matches WHEN NULL, because NULL equals nothing, not even NULL. Oracle’s advice is a searched CASE with WHEN kind IS NULL. Oracle also allows a handler for CASE_NOT_FOUND in the block’s exception section in place of the ELSE.

Checkpoint

Check: IF, ELSIF and ELSE · CASE statements and CASE expressions

  1. Question 1 of 2 IF v_value < 400 THEN v_fee := 49; ELSIF v_value < 800 THEN v_fee := 29; ELSE v_fee := 0; END IF; runs with v_value NULL. What is v_fee?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 0, from the ELSE branch

    An unknown value fails every comparison and lands in ELSE. Test IS NULL first when NULL must be handled on purpose.

  2. Question 2 of 2 CASE v_city WHEN 'Pune' THEN v_zone := 1; WHEN 'Kochi' THEN v_zone := 2; END CASE; runs with v_city = 'Chennai'. What happens?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It raises CASE_NOT_FOUND, ORA-06592

    Give a CASE statement an ELSE, or handle CASE_NOT_FOUND, whenever a value outside its WHEN list can arrive.

A loop repeats until EXIT, its test or its range ends it

PL/SQL control structures for repetition come in three kinds. A basic LOOP repeats until an EXIT, an EXIT WHEN or an error ends it. WHILE tests its condition before every pass. FOR v_id IN 1 .. 4 declares its own index, a PLS_INTEGER that the body may read but never assign, counts up, or down with REVERSE, and forgets the index after END LOOP. In any loop, CONTINUE WHEN ends one pass early and EXIT WHEN ends the loop. T-SQL has only WHILE, as T-SQL control flow shows. Here is one loop on the practice accounts:

Interactive · stage by stage One FOR loop with CONTINUE WHEN and EXIT WHEN Step through the four passes of the loop and watch which statements each pass reaches.
One FOR loop with CONTINUE WHEN and EXIT WHEN, step 5 of 5. Showing FOR v_id IN 1..4, Read the balance, CONTINUE WHEN < 1000, Add to v_total (total 61750.50), EXIT WHEN > 50000, After END LOOP. In focus: After END LOOP.next passCONTINUEEXITFOR v_id IN 1..4the next account idRead the balanceSELECT INTO v_balanceCONTINUE WHEN < 1000ends this pass earlyAdd to v_totaltotal 61750.50EXIT WHEN > 50000leaves the loopAfter END LOOPthe block goes on

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

Every step as text
  1. Pass 1 adds 2500.00. The loop declares v_id itself and sets it to 1. Account 1 holds 2500.00, which is not below 1000, so the pass goes on and the total becomes 2500.00.
  2. Pass 2 adds 18000.00. Account 2 holds 18000.00. The total, 20500.00, is not above 50000, so EXIT WHEN lets the loop continue with the next id.
  3. Pass 3 is cut short. Account 3 holds 760.25. CONTINUE WHEN is true, so the rest of this pass is skipped: nothing is added, nothing is printed, and control goes back to the FOR header.
  4. Pass 4 passes the limit. Account 4 adds 41250.50 and the total reaches 61750.50. Now EXIT WHEN is true and the loop ends here; with a longer range, any later ids would never be read.
  5. After the loop. Control continues after END LOOP. v_id no longer exists there: a FOR loop index belongs to the loop, and the loop body could read it but never assign it.

A loop label: A name written in double angle brackets just before a PL/SQL loop and repeated after its END LOOP; EXIT and CONTINUE can name it to act on that loop from inside a nested one. lets an inner loop leave both loops at once: EXIT rows WHEN r * c = 4 ends the outer loop named rows, while a plain EXIT leaves only the inner one. The script holds all three kinds:

loops.sql: FOR with CONTINUE and EXIT, WHILE, and a labelled exit SQL · loops/loops.sql
-- Engine: Oracle AI Database 26ai Free, Release 23.26.3.0.0; user LAB in FREEPDB1. Client: SQL*Plus or SQLcl.
-- Three loops on the practice accounts: a FOR loop that skips small balances with CONTINUE WHEN and stops with
-- EXIT WHEN, a WHILE loop that grows 760.25 at 3 % a year until it reaches 1000.00, and two nested FOR loops that
-- a labelled EXIT leaves together. control_model.py prints the same numbers. Nothing is stored.
SET SERVEROUTPUT ON

DECLARE
  v_balance accounts.balance%TYPE;
  v_total   NUMBER(14, 2) := 0;
BEGIN
  FOR v_id IN 1 .. 4 LOOP                          -- v_id: declared by the loop, PLS_INTEGER, read-only
    SELECT balance INTO v_balance FROM accounts WHERE account_id = v_id;
    CONTINUE WHEN v_balance < 1000;                -- skip the rest of this pass only
    v_total := v_total + v_balance;
    DBMS_OUTPUT.PUT_LINE('account ' || v_id || ' added, total ' || TO_CHAR(v_total, 'FM99999990.00'));
    EXIT WHEN v_total > 50000;                     -- leave the loop for good
  END LOOP;
END;
/
-- Expected output (accounts 1 to 4 hold 2500.00, 18000.00, 760.25 and 41250.50):
--   account 1 added, total 2500.00
--   account 2 added, total 20500.00
--   account 4 added, total 61750.50
--   PL/SQL procedure successfully completed.
-- Account 3 is skipped by CONTINUE WHEN, and EXIT WHEN ends the loop after account 4.

DECLARE
  v_balance NUMBER(14, 2) := 760.25;
  v_years   PLS_INTEGER := 0;
BEGIN
  WHILE v_balance < 1000 LOOP                      -- tested before every pass
    v_balance := ROUND(v_balance * 1.03, 2);
    v_years   := v_years + 1;
  END LOOP;
  DBMS_OUTPUT.PUT_LINE(v_years || ' years, ending at ' || TO_CHAR(v_balance, 'FM99990.00'));
END;
/
-- Expected output: 10 years, ending at 1021.71

BEGIN
  <<rows>>
  FOR r IN 1 .. 3 LOOP
    FOR c IN 1 .. 3 LOOP
      EXIT rows WHEN r * c = 4;                    -- leaves BOTH loops: the label names the outer one
      DBMS_OUTPUT.PUT_LINE(r || ' x ' || c || ' = ' || r * c);
    END LOOP;
  END LOOP rows;
  DBMS_OUTPUT.PUT_LINE('left both loops');
END;
/
-- Expected output:
--   1 x 1 = 1
--   1 x 2 = 2
--   1 x 3 = 3
--   2 x 1 = 2
--   left both loops
-- Writing r := 2; inside the loop would not compile (PLS-00363: the index cannot be an assignment target).
loops.sql, run in the lab Python · loops_session.py
"""loops.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("loops/loops.sql")

Output

$ sqlplus -s lab@//localhost/FREEPDB1 @loops/loops.sql
account 1 added, total 2500.00
account 2 added, total 20500.00
account 4 added, total 61750.50

PL/SQL procedure successfully completed.

10 years, ending at 1021.71

PL/SQL procedure successfully completed.

1 x 1 = 1
1 x 2 = 2
1 x 3 = 3
2 x 1 = 2
left both loops

PL/SQL procedure successfully completed.

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

Its WHILE loop needs 10 years at 3 % to grow 760.25 past 1000.00, ending at 1021.71.

A basic loop with no way out

Wrong: an EXIT WHEN that tests a variable the loop body never changes, so the loop runs until the session is stopped. Right: change the tested value in every pass, or use FOR, whose range ends the loop by itself.

Newer releases add iterators and extended CASE

Oracle Database 21c gave the FOR loop new iterators, built from iteration control: The part of a PL/SQL FOR loop header after IN that produces the index values: a range such as 1 .. 10, or, from Oracle Database 21c, a stepped range, a list of controls or a collection. that produce the index values, and 26ai extended the simple CASE statement. Code that must also run on 19c can use neither:

Timeline · 3 events in order Loops and CASE across three Oracle releases Walk through the releases in order to see which forms of FOR and CASE each one accepts.
  1. 19c

    Classic loops and both kinds of CASE long-term release

    Basic LOOP, WHILE and FOR i IN low..high, with EXIT, CONTINUE and labels; IF, the simple CASE that compares values and the searched CASE that tests conditions. Code that must run on 19c keeps to these.

  2. 21c

    Iteration controls in the FOR loop new syntax

    The FOR loop gains iterators: stepped ranges with BY, several controls in one header, WHEN and WHILE filters, a declared type for the index, and loops over the indexes and values of a collection.

  3. 26ai

    Extended CASE controls new syntax

    A simple CASE statement accepts dangling predicates such as < 0 or > 100 after WHEN, and comma lists of choices, so one WHEN can name several values.

The timeline has 3 events, from 19c to 26ai.

Each new form has a plain meaning. A stepped range such as 1 .. 10 BY 3 moves by more than one. Several controls separated by commas run one after another in a single loop. A WHEN filter skips values whose condition is not TRUE. A declared index type, such as NUMBER(3,1), allows fractional steps. INDICES OF, VALUES OF and PAIRS OF walk the indexes, the values or both of a collection. A dangling predicate: A comparison with its left operand missing, such as < 0 or BETWEEN 4 AND 30, written after WHEN in a simple CASE statement of Oracle AI Database 26ai; the CASE selector fills the gap. is a comparison with its left side missing, such as < 0, and a choice list puts several values after one WHEN.

Checkpoint

Check: Basic, WHILE and FOR loops · Which release adds which form

  1. Question 1 of 3 Inside FOR i IN 1 .. 5 LOOP, a line says i := i + 1; What happens?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: The block does not compile, because the loop index cannot be assigned

    A FOR loop declares its own index, read-only and gone after END LOOP. Use a WHILE loop or a basic loop when the counter must change inside the body.

  2. Question 2 of 3 Two nested FOR loops, the outer one labelled rows. Inside the inner loop, EXIT rows WHEN r * c = 4; becomes TRUE. Where does control go?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: After END LOOP rows, out of both loops

    Label the outer loop when an inner loop must be able to leave it; without the label, EXIT reaches only the inner loop.

  3. Question 3 of 3 Which is the first release that accepts FOR i IN 1 .. 10 BY 2 LOOP?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: Oracle Database 21c

    Iteration controls need 21c or later and extended CASE needs 26ai. Code shared with 19c keeps to classic loops and the searched CASE.

Stepped, chained and filtered FOR loops, and sparse collections walked safely Pro: opens with a Pro, Premium or Ultimate pass

The iteration controls of 21c change what a single FOR header can say. Each one runs below on the lesson’s values, next to the classic loop it replaces.

In this part:

  • Stepping, chaining and filtering one FOR header
  • Walking a sparse collection without a missing-index error

3 runnable examples (Python, SQL) · about 7 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).

Extended CASE in 26ai, and the searched CASE that runs on 19c Pro: opens with a Pro, Premium or Ultimate pass

A late fee by days overdue shows the extended CASE controls at work. The same rules then move to a searched CASE, line by line, so that the code also runs on 19c.

In this part:

  • Dangling predicates and choice lists in one CASE statement
  • Rewriting it for 19c, and which form to keep
  • Testing a rule table at its boundaries, with a slip reproduced

3 runnable examples (Python, SQL) · about 6 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 branch runs and when a loop stops

Each question gives a few lines of PL/SQL. Decide which statements run and what the variables hold before you choose:

Practice that adapts to you

Practise this lesson

9 questions about 8 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 With the lesson's rule (below 400.00 pays 49.00, below 800.00 pays 29.00, else 0.00), what fee does an order worth 380.00 pay?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: 49.00

    IF takes the first TRUE condition from the top, so order the tests from the narrowest to the widest.

  2. Question 2 of 9 Which keyword starts the second condition of a PL/SQL IF statement?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: ELSIF

    IF … THEN … ELSIF … THEN … ELSE … END IF; is one statement with one END IF.

  3. Question 3 of 9 v_zone := CASE v_city WHEN 'Pune' THEN 1 WHEN 'Kochi' THEN 2 END; runs with v_city = 'Lucknow'. What is v_zone?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: NULL

    A CASE expression that nothing matches hides the problem as NULL. Add ELSE with a value, or test the result for NULL.

  4. Question 4 of 9 A simple CASE v_email WHEN NULL THEN ... never runs its WHEN for customers without an email. What should replace it?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: A searched CASE with WHEN v_email IS NULL THEN

    A simple CASE compares with =, and = never matches NULL. Write the IS NULL test in a searched CASE.

  5. Question 5 of 9 FOR i IN 1 .. 6 LOOP CONTINUE WHEN MOD(i, 2) = 0; DBMS_OUTPUT.PUT_LINE(i); END LOOP; How many lines does it print?

    Type a number.

    Show the answer: Question 5 of 9

    Answer: 3 lines

    CONTINUE WHEN skips the rest of the pass for 2, 4 and 6, so only 1, 3 and 5 are printed. EXIT WHEN would have ended the loop at 2 after printing just 1.

  6. Question 6 of 9 v_balance is 1200.00 before WHILE v_balance < 1000 LOOP ... END LOOP; How many times does the body run?

    Type a number.

    Show the answer: Question 6 of 9

    Answer: 0 passes

    WHILE tests before every pass, including the first, so a test that is false at the start means no pass at all. A basic LOOP with EXIT WHEN at its end would run the body once first.

  7. Question 7 of 9 In which order does FOR i IN REVERSE 1 .. 3 LOOP give i its values?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: 3, 2, 1

    Write the range as low .. high and add REVERSE to count down; FOR i IN 3 .. 1 gives no value, because a range runs from its lower bound up to its upper bound.

  8. Question 8 of 9 Which loop header compiles on Oracle Database 19c?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: FOR i IN REVERSE 1 .. 12 LOOP

    Only the classic loop, whose header holds a range and perhaps REVERSE, compiles on every release; the other forms arrived with 21c.

  9. Question 9 of 9 Which release first allows WHEN < 0 THEN in a simple CASE statement?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: Oracle AI Database 26ai

    Code that must also run on 19c keeps to the searched CASE, whose WHEN clauses hold full conditions.

Interview questions

Warm-up (fresher): how do EXIT and CONTINUE differ inside a loop?

EXIT ends the whole loop and control moves to the statement after END LOOP; CONTINUE ends only the current pass and starts the next one. Both have a WHEN form that acts only when its condition is TRUE, and both can name a label to act on an enclosing loop from inside a nested one.

Three longer questions follow, on a new status code that broke a nightly job, deletes that broke a loop, and a port to 19c:

  1. Fresher to senior

    A nightly settlement job maps each order's status to an action with a CASE statement on new, paid, shipped and cancelled. After the shop adds a returned status, the job stops with ORA-06592 on the first such order. Explain the failure and how you would change the code and its tests.

    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 loads pending orders into an associative array indexed by order id, deletes those that fail a check, then sends the rest with FOR i IN 1 .. v_orders.COUNT. It passed its tests and now fails with ORA-01403. What is wrong, and how would you write the loop on 19c and on 26ai?

    Model answer, Pro. First step and follow-ups

  3. Mid to staff

    Your team wrote a billing package on Oracle AI Database 26ai with FOR loops that use BY and WHEN, and simple CASE statements with dangling predicates. A client must run it on Oracle Database 19c. How do you plan the port, and how do you show that the 19c version behaves the same?

    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. How does IF ... ELSIF ... ELSE choose a branch, and where does a NULL value land?

    Oracle tests the conditions from the top, runs the first branch whose condition is TRUE and runs ELSE only when none was. Comparing a value with NULL gives neither TRUE nor FALSE, so an unpriced order fails every test and reaches ELSE. The keyword is ELSIF, one word without a second E.

  2. What happens when nothing matches a CASE, and there is no ELSE?

    A CASE expression gives NULL; a CASE statement raises the predefined exception CASE_NOT_FOUND, ORA-06592. A handler for CASE_NOT_FOUND in the block's exception section can stand in place of the ELSE.

  3. How do a simple and a searched CASE differ, and why does a NULL selector need the searched form?

    A simple CASE compares one selector with the value after each WHEN; a searched CASE has no selector and tests a full condition. In both, the first match wins and nothing falls through. A NULL selector never matches WHEN NULL, because NULL equals nothing, not even NULL, so use WHEN kind IS NULL.

  4. What are PL/SQL's three kinds of loop?

    A basic LOOP repeats until an EXIT, an EXIT WHEN or an error ends it. WHILE tests its condition before every pass. FOR declares its own index, a PLS_INTEGER the body may read but never assign, counts up or down with REVERSE, and forgets the index after END LOOP.

  5. What do CONTINUE WHEN, EXIT WHEN and a loop label each do?

    CONTINUE WHEN ends one pass early, EXIT WHEN ends the loop, and a label lets an inner loop leave an outer one: EXIT rows WHEN ... ends the loop named rows, where a plain EXIT would leave only the inner loop.

  6. Which EXIT WHEN never fires?

    One that tests a variable the loop body never changes: the loop then runs until the session is stopped. Change the tested value on every pass, or use FOR, whose range ends the loop by itself.

  7. What did Oracle Database 21c add to the FOR loop?

    Iteration controls that produce the index values: a stepped range such as 1 .. 10 BY 3, several controls separated by commas running one after another in one loop, a WHEN filter that skips values whose condition is not TRUE, a declared index type for fractional steps, and INDICES OF, VALUES OF and PAIRS OF for collections.

  8. What did 26ai add to the simple CASE statement, and what may 19c code use?

    A dangling predicate, a comparison with its left side missing such as < 0, and a choice list that puts several values after one WHEN. Code that must also run on 19c can use neither those nor the 21c iterators.

Key takeaways

  • IF … ELSIF … ELSE runs the first branch whose condition is TRUE, and a NULL value fails every test and reaches ELSE.
  • A CASE statement that matches nothing and has no ELSE raises CASE_NOT_FOUND; a CASE expression gives NULL.
  • Basic, WHILE and FOR loops end by EXIT, by their test or by their range; CONTINUE ends one pass, and a label lets an inner loop leave an outer one.
  • PL/SQL control structures gained iterators in 21c and extended CASE controls in 26ai, and 19c code uses neither.

Terms in this lesson

dangling predicate
A comparison with its left operand missing, such as < 0 or BETWEEN 4 AND 30, written after WHEN in a simple CASE statement of Oracle AI Database 26ai; the CASE selector fills the gap.
iteration control
The part of a PL/SQL FOR loop header after IN that produces the index values: a range such as 1 .. 10, or, from Oracle Database 21c, a stepped range, a list of controls or a collection.
loop label
A name written in double angle brackets just before a PL/SQL loop and repeated after its END LOOP; EXIT and CONTINUE can name it to act on that loop from inside a nested one.
sparse collection
A PL/SQL collection whose indexes have gaps, such as an associative array that holds elements at indexes 1, 10 and 100 only.

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.