SQL is not COBOL; you cannot sprinkle IF/ELSE between SELECT and FROM. You can put a CASE expression almost anywhere a value is allowed. DB2 for z/OS supports simple CASE (equality switch) and searched CASE (WHEN predicates). This page covers both forms, nesting, nulls, and CASE in SELECT, WHERE, ORDER BY, GROUP BY, UPDATE, and aggregates.
| Form | Shape | Use when |
|---|---|---|
| Simple CASE | CASE expr WHEN v THEN r ... ELSE r END | Equality switch on one expression |
| Searched CASE | CASE WHEN pred THEN r ... ELSE r END | Ranges, AND/OR, IS NULL, independent tests |
Evaluation rule for both: the result is the THEN (or ELSE) of the first leftmost WHEN that is true. Later WHENs are not considered. Unknown is not true. If nothing is true: ELSE result, or NULL if ELSE is omitted.
All result-expressions must have compatible types (SQLSTATE 42804). At least one THEN/ELSE must be a non-null result-expression—you cannot write CASE with only NULL results (SQLSTATE 42625).
Simple CASE tests the expression after CASE for equality with each WHEN expression. Types must be comparable. The expression before the first WHEN must not be a non-deterministic or external-action function (SQLSTATE 42845).
12345678SELECT EMPNO, LASTNAME, CASE WORKDEPT WHEN 'A00' THEN 'SPIFFY COMPUTER SERVICE DIV.' WHEN 'B01' THEN 'PLANNING' WHEN 'C01' THEN 'INFORMATION CENTER' ELSE 'OTHER / UNKNOWN' END AS DEPT_NAME FROM DSN8C10.EMP;
Think of it as a switch on one value. You cannot write WHEN SALARY > 50000 in a simple CASE—that needs searched CASE. You also cannot match nulls: CASE WORKDEPT WHEN NULL THEN ... never fires, because null = null is unknown.
Searched CASE has no expression between CASE and the first WHEN. Each WHEN is a search-condition (a predicate, possibly AND/OR).
12345678910SELECT EMPNO, FIRSTNME, LASTNAME, CASE WHEN EDLEVEL <= 12 THEN 'HIGH SCHOOL OR LESS' WHEN EDLEVEL > 12 AND EDLEVEL <= 14 THEN 'JUNIOR COLLEGE' WHEN EDLEVEL > 14 AND EDLEVEL <= 17 THEN 'FOUR-YEAR COLLEGE' WHEN EDLEVEL > 17 THEN 'GRADUATE SCHOOL' ELSE 'UNKNOWN' END AS EDUCATION FROM DSN8C10.EMP WHERE JOB = 'FLD';
IBM uses this pattern to decode codes and to avoid division by zero:
123456SELECT EMPNO, WORKDEPT, CASE WHEN SALARY = 0 THEN NULL ELSE COMM / SALARY END AS COMMISSION_RATIO FROM DSN8C10.EMP;
Order WHEN clauses from specific to general. Overlapping ranges are legal; the first true wins, so put the tighter test first.
A THEN or ELSE can itself be a CASE. Nesting is useful for two-dimensional rules (job and department) without a huge AND mesh. Keep it readable—three levels is already a maintenance hazard.
123456789CASE WHEN JOB = 'MANAGER' THEN CASE WORKDEPT WHEN 'A00' THEN 'EXEC MGR' ELSE 'DEPT MGR' END WHEN JOB = 'SALESREP' THEN 'SALES' ELSE 'STAFF' END
SQL PL CASE statements (control flow in procedures) have a nesting limit of three for simple WHEN and no documented nesting limit for searched WHEN. That is the statement, not the expression. Expressions nest as the SQL Reference allows; prefer flattening over deep nests.
123456789-- Null-safe label CASE WHEN COMM IS NULL THEN 'NO COMMISSION' WHEN COMM = 0 THEN 'ZERO' ELSE 'HAS COMMISSION' END -- First non-null phone COALESCE(WORKPHONE, HOMEPHONE, 'NONE')
If EDLEVEL is null in the education CASE above, every comparison is unknown, ELSE runs, and IBM’s example shows UNKNOWN. That is why ELSE is not optional if you need a defined label for nulls.
The SELECT list is the most common home. Alias the result. Compatible types across THEN/ELSE matter: mixing CHAR and INTEGER forces a conversion or an error—cast explicitly (CHAR, DECIMAL) so reports stay predictable.
Legal, but usually a stage 2 predicate. Prefer:
12345-- Residual (CASE in WHERE) WHERE CASE WHEN SALARY > 100000 THEN 'Y' ELSE 'N' END = 'Y' -- Index-friendlier equivalent WHERE SALARY > 100000
Use CASE in WHERE only when the logic cannot be expressed as ordinary predicates (rare) or when the query is already filtered tightly by other indexable predicates.
Custom sort sequences that are not the column’s collating sequence:
12345678910SELECT EMPNO, JOB, LASTNAME FROM DSN8C10.EMP ORDER BY CASE JOB WHEN 'PRES' THEN 1 WHEN 'MANAGER' THEN 2 WHEN 'SALESREP' THEN 3 ELSE 4 END, LASTNAME;
Searched CASE in ORDER BY cannot use EXISTS, quantified predicates, or IN with a fullselect. Keep the WHEN conditions simple comparisons.
Bucket rows without a helper column. You must repeat the CASE in GROUP BY; the SELECT alias is not a grouping-expression.
12345678910111213SELECT CASE WHEN SALARY < 50000 THEN 'LOW' WHEN SALARY < 80000 THEN 'MID' ELSE 'HIGH' END AS SAL_BAND, COUNT(*) AS N, AVG(SALARY) AS AVG_SAL FROM DSN8C10.EMP GROUP BY CASE WHEN SALARY < 50000 THEN 'LOW' WHEN SALARY < 80000 THEN 'MID' ELSE 'HIGH' END;
Same restriction as ORDER BY: no EXISTS / quantified / IN-fullselect in the searched WHEN of a GROUP BY CASE. A nested table expression is the escape hatch: compute CASE in an inner SELECT, group on the named column outside.
SET a column from a CASE so one statement applies different formulas:
12345678UPDATE DSN8C10.EMP SET SALARY = SALARY * CASE WORKDEPT WHEN 'A00' THEN 1.10 WHEN 'B01' THEN 1.08 WHEN 'C01' THEN 1.06 ELSE 1.03 END WHERE HIREDATE < '2010-01-01';
Searched CASE works the same in SET. Combine with WHERE so you do not multiply rows you did not intend to touch. Check compatible numeric types so DECIMAL salaries do not unexpectedly become floating-point.
Conditional aggregation (pivot-style) uses CASE inside SUM/COUNT/AVG:
12345SELECT SUM(SALARY) AS TOT_SAL, SUM(CASE WHEN WORKDEPT = 'A00' THEN SALARY END) AS A00_SAL, SUM(CASE WHEN WORKDEPT = 'B01' THEN SALARY END) AS B01_SAL, COUNT(CASE WHEN JOB = 'MANAGER' THEN 1 END) AS MGR_N FROM DSN8C10.EMP;
THEN SALARY with omitted ELSE yields null for other departments; SUM ignores nulls, so you get the department total. COUNT(CASE WHEN ... THEN 1 END) counts matches; COUNT(CASE WHEN ... THEN 1 ELSE 0 END) is wrong because 0 is not null—COUNT would count every row. Use SUM(CASE WHEN ... THEN 1 ELSE 0 END) or COUNT of a non-null THEN without ELSE 0.
| Place | Tip |
|---|---|
| SELECT list | Decode codes, protect division, labels for reports |
| WHERE / HAVING | Works, but often stage 2; prefer simple predicates |
| ORDER BY | Custom sort keys (status order, not alphabetic) |
| GROUP BY | Repeat the CASE; no EXISTS/quantified/IN-fullselect in searched WHEN |
| UPDATE SET | Conditional assignment in one statement |
| Aggregates | SUM(CASE WHEN ... THEN n ELSE 0 END) pivot / conditional totals |
CASE is a stack of labeled boxes. Simple CASE looks at one toy and asks “is it a red car? a blue car?” Searched CASE asks any yes/no question: “is it heavier than five bricks?” The first box that says yes wins the prize inside. If every box says “I don’t know” (null) or “no,” and you did not put a last box labeled ELSE, the prize is empty (null). Putting CASE in WHERE is like making the librarian open every book to run the box game instead of using the catalog.
1. What is the difference between simple CASE and searched CASE?
2. If no WHEN is true and there is no ELSE, the CASE result is:
3. Can you GROUP BY a CASE alias from the SELECT list?
4. Which searched CASE is illegal in GROUP BY / ORDER BY / VALUES / IN?
5. SUM(CASE WHEN JOB = 'MANAGER' THEN 1 ELSE 0 END) counts: