PL/SQL IF — NULL Comparison Silently Skips 12K Records
12,000 invoices went unprocessed because PL/SQL's Three-Valued Logic treats NULL comparisons as NULL, not FALSE.
- PL/SQL control structures add procedural decision-making and repetition to Oracle's declarative SQL engine
- IF-THEN-ELSIF handles branching; CASE selects from mutually exclusive options
- Three loop types: BASIC (run at least once), WHILE (check first), FOR (known range or cursor)
- PL/SQL uses Three-Valued Logic — NULL compared with = always returns NULL, never TRUE
- FORALL and set-based SQL are 100x faster than RBAR loops for bulk DML operations
- Forgetting EXIT WHEN or cursor cleanup causes ORA-01000 and runaway sessions
Think of PL/SQL control structures the same way you think about the decision logic baked into everyday systems — the thermostat that kicks in only when temperature drops below a threshold, the traffic light that cycles through states on a timer, the automated checkout that keeps scanning items until the belt is empty. IF-THEN is your traffic light: it looks at the current state and decides who moves. LOOP and WHILE are your shuttle routes — they keep running until a specific condition signals that the job is done. Without these constructs, your PL/SQL block is a straight line: it runs top to bottom, once, and exits. It cannot adapt, retry, or branch. These structures are what give database code actual intelligence rather than just mechanical execution.
Standard SQL is declarative — you describe the result you want and the optimizer figures out how to get there. That model works brilliantly for set-based operations, but it breaks down the moment your logic needs to branch on a runtime value, iterate until a condition is met, or recover from a failure mid-stream and continue processing. That is exactly the gap PL/SQL control structures fill.
In production, the stakes around this are not theoretical. A poorly structured loop processing 500,000 rows one at a time generates context switches between the PL/SQL and SQL engines on every single iteration — each one carrying the overhead of a round trip. The redo log swells. Row-level locks accumulate. PGA memory climbs. A batch job that should complete in under a minute ends up holding locks for thirty. The same operation expressed as a single FORALL or a well-structured UPDATE runs entirely inside the SQL engine with an optimized execution plan and no context-switching penalty.
But set-based SQL has its own limits. When each row's outcome determines how the next row should be processed, when business rules span multiple dependent queries, when you need to log partial failures and continue rather than abort — that is where procedural control structures earn their place. The judgment call between reaching for a loop versus restructuring the problem as a single SQL statement is one of the clearest signals separating a mid-level Oracle developer from a senior one.
This guide covers the mechanics, the production patterns, the failure modes, and the performance trade-offs. By the end you will know not just how each structure works, but when to use it, when to avoid it, and what to check first when something goes wrong at 2 a.m.
Conditional Logic: The IF-THEN-ELSIF Branching Model
SQL's declarative model handles set operations elegantly, but it was never designed for branching on runtime state. The moment you need to execute a completely different code path based on a value fetched from the database — not just select a different column value, but run different procedures, apply different validation rules, or write to different tables — you need procedural conditional logic. IF-THEN-ELSIF is how PL/SQL expresses that.
The evaluation model is straightforward: conditions are assessed top-down and the first branch whose condition evaluates to TRUE executes. Every subsequent branch is skipped entirely. This short-circuit behaviour is not just an optimisation — it is the mechanism. It means condition ordering matters. If you place the most expensive condition first and it is rarely TRUE, you are paying that cost on every evaluation. Put your cheapest, most commonly matched condition at the top.
The ELSE clause is where many production batch jobs have quietly failed. Without it, any input state that matches no ELSIF condition is silently ignored. In an interactive application, a missed branch is a minor UX issue. In an overnight batch job processing 300,000 invoices, it means a subset of records is never touched and nobody finds out until month-end reconciliation. The ELSE branch should always be present in batch processing, and it should always write to an audit table — not just to DBMS_OUTPUT, which is lost the moment the session ends.
Null handling is the sharpest edge in this model. PL/SQL inherits Three-Valued Logic from SQL: TRUE, FALSE, and NULL. Any comparison involving NULL using a standard operator returns NULL, not FALSE. IF v_status = NULL does not evaluate to TRUE when v_status is NULL — it evaluates to NULL, which is not TRUE, so the block never fires. This is not an edge case. It is how the language works, and it catches experienced developers who switch from other languages where null comparisons behave differently. The fix is always IS NULL and IS NOT NULL — no exceptions.
- Conditions evaluated top-down — first TRUE match wins, remainder skipped entirely
- NULL compared with any standard operator returns NULL, never TRUE — use IS NULL without exception
- ELSE branch is not optional in batch processing — it is your catch-all audit point
- Order conditions by likelihood or cost — cheapest and most common first reduces unnecessary evaluation
- Short-circuit applies to AND/OR chains too — if A is FALSE in A AND B, B is never evaluated
Iterative Logic: Master the Three Loop Types
The choice of loop type is a correctness decision before it is a performance decision. Each of the three loop types in PL/SQL encodes a specific contract about when the exit condition is evaluated, who manages the counter, and what happens to open cursors when the loop terminates.
The BASIC LOOP executes its body at least once before evaluating the EXIT WHEN condition. This is the right choice when you need to perform an action — read from a queue, generate a sequence value, attempt a connection — and then decide whether to continue. The risk is the infinite loop: if EXIT WHEN is omitted or the condition can never become TRUE, the loop runs until the session is killed or the server runs out of PGA memory. Always add a safety ceiling alongside the business exit condition.
The WHILE LOOP evaluates its condition before the first iteration. If the condition is false going in, the body never executes. This is the right model when you are processing a queue, a file, or an external resource that might already be empty. The discipline required here is ensuring the condition variable is actually modified inside the loop body — a WHILE that checks a variable nobody updates is an infinite loop waiting to happen.
The FOR LOOP is the safest of the three for the simple reason that you cannot accidentally create an infinite loop with it. The bounds are fixed at entry, the counter is implicitly declared and incremented, and the loop exits cleanly when the upper bound is reached or the cursor is exhausted. The Cursor FOR LOOP extends this safety to resource management: the cursor is opened, each row fetched into the loop record variable, and the cursor closed on exit — even if an unhandled exception fires mid-loop. This is the single most effective way to prevent ORA-01000 in production code.
The performance conversation is separate from the loop type conversation. RBAR — Row-By-Agonizing-Row — is the pattern of performing one DML operation per loop iteration when a single SQL statement or FORALL could handle the entire dataset. The cost is context switching: every transition between the PL/SQL engine and the SQL engine has overhead. For 500,000 rows, that overhead accumulates into minutes. FORALL sends the entire collection of DML operations to the SQL engine in one call. The throughput difference is not a small margin — benchmarks consistently show 50x to 100x improvement for pure DML workloads. If the loop body is a single INSERT, UPDATE, or DELETE, rewrite it as FORALL before it ships.
- FORALL replaces INSERT/UPDATE/DELETE loops — one SQL engine call instead of N, routinely 50–100x faster
- Cursor FOR LOOP handles open/fetch/close implicitly — closes the cursor even when an exception fires mid-loop, preventing ORA-01000
- FOR LOOP over a range is the safest — counter is implicit, bounds are fixed, infinite loops are impossible
- BASIC LOOP always needs a safety ceiling alongside the business exit condition — never trust the business condition alone
- CONTINUE WHEN skips to the next iteration cleanly — use it instead of wrapping the entire loop body in an IF block
- Commit in batches every N rows inside long-running loops — reduces undo/redo pressure and allows partial recovery on failure
NULL Comparison in IF Block Silently Skipped Invoice Reconciliation for 12,000 Records
IF v_status = NULL THEN expecting it to evaluate to TRUE when the variable held no value. The reasoning was intuitive: if the variable is empty, the condition should match. In PL/SQL's Three-Valued Logic, that intuition is wrong in a way that leaves no trace — the condition evaluates to NULL, not TRUE, so the entire branch is silently skipped on every row where status was NULL.IF v_status = NULL — regardless of what v_status actually contains — never fires. There was no error, no warning, no log entry. The batch job processed every non-NULL record correctly and silently skipped every NULL-status record. The only indication something was wrong was a £2.3M gap in the reconciliation figures three weeks later.= NULL comparison was replaced with IS NULL. Every <> NULL comparison became IS NOT NULL. An ELSE branch was added to each IF block in the batch procedure, writing an audit record to a dedicated exception table whenever a record did not match any expected state. A post-batch verification query was added to the job scheduler: it counts processed versus unprocessed records and raises an alert if the gap exceeds a configurable threshold. The NULL-status records were identified, manually reviewed, corrected, and reprocessed.- Never use = NULL in PL/SQL — always use IS NULL for NULL comparisons
- Three-Valued Logic means NULL comparisons silently evaluate to NULL, not TRUE — no error is raised
- Add ELSE branches with audit logging to every IF block in batch jobs — silent skipping is worse than a visible failure
- Post-batch verification queries that compare expected versus actual record counts catch silent logic failures before they compound over weeks
Key takeaways
Common mistakes to avoid
4 patternsRBAR processing instead of set-based SQL
Failing to close cursors within loops
Ignoring error handling in loop bodies
NULL comparison using = operator
Interview Questions on This Topic
What is the difference between a WHILE LOOP and a FOR LOOP in PL/SQL? When would you choose one over the other in a production environment?
Frequently Asked Questions
That's PL/SQL. Mark it forged?
5 min read · try the examples if you haven't