Hook: CASE is SQL's traffic cop: it looks at each row, decides which lane it belongs in, and sends back one clean result.
Question: What is CASE in SQL, and how do you use it in SELECT and filtering?
Answer: CASE is an expression that returns a value based on conditions. You use it to turn raw rows into labels like high, medium, or low, or to choose different values in ORDER BY, SELECT, UPDATE, and other clauses. The big idea is that SQL checks the WHEN clauses from top to bottom and returns the first match; if nothing matches, ELSE is used, and if ELSE is missing, the result is NULL.
Interview-Ready Answer: I use CASE when I need row-by-row conditional logic in SQL. It is an expression, not a control-flow statement, so it returns one value per row based on the first matching WHEN. There are two forms: simple CASE for equality checks and searched CASE for full conditions. One important detail is that if I omit ELSE, SQL returns NULL, so I usually add ELSE to avoid surprise nulls.
Detailed Explanation: CASE has been part of the SQL standard since SQL-92. Think of it as a small decision machine inside a query: it does not loop, it does not change program flow, and it does not create new rows. It simply chooses one output value for each row.
| Form | Looks like | Best for |
|---|---|---|
| Simple CASE | CASE x WHEN 1 THEN ... | Exact matches |
| Searched CASE | CASE WHEN x > 1 THEN ... | Ranges, NULL checks |
Use simple CASE when you are comparing one expression to fixed values. Use searched CASE when you need real logic like greater than, between, or IS NULL. In interviews, searched CASE is often the safer answer because it handles more cases.
The first matching branch wins. That means overlapping conditions must be ordered carefully: put the most specific rule first, then broader ones later. For example, if amount >= 100 comes before amount >= 50, a 120 order is labeled correctly. If you reverse them, the 120 order is still matched by the first broader rule and never reaches the later one.
Conceptually, CASE is O(n) per row where n is the number of WHEN clauses, because SQL may test conditions until it finds a match. In practice, a CASE with 3 to 8 branches is usually cheap compared with scanning thousands or millions of rows. The real cost is usually the table scan, join, or sort around it, not the CASE itself.
CASE x WHEN NULL THEN ... does not match NULL, because NULL means unknown, not equal.When to use it: use CASE to create labels, buckets, display text, conditional sorting, and safe fallbacks. If your goal is real row filtering, a plain WHERE predicate is usually clearer; CASE is best when you need to transform a value before showing or sorting it.
Real-World Story: Imagine a checkout service at an e-commerce company. The analytics team wants every order labeled as vip, standard, budget, or missing_data so the dashboard can track revenue mix. A CASE expression does that inside the reporting query without changing the raw table.
What goes wrong when someone misunderstands CASE? A developer forgets the ELSE branch, and orders with a new status or NULL amount fall through to NULL. The dashboard suddenly shows a blank category, the BI tool groups those rows into an unlabeled bucket, and the finance team thinks revenue disappeared. The symptoms are easy to spot: null category counts spike, scheduled report logs show unexpected NULLs, and customer support sees mismatched totals between the order page and the monthly report.
The fix is simple but important: make the rules explicit, put the most specific WHEN first, and always decide what the fallback should be. In production SQL, that small habit prevents silent data quality bugs that are painful to debug later.
-- Demo data: a tiny order table built with standard SQL so the example is runnable.
WITH orders AS (
SELECT CAST(1 AS INTEGER) AS order_id, CAST(120.00 AS DECIMAL(10,2)) AS amount, CAST('paid' AS VARCHAR(20)) AS status, CAST(2 AS INTEGER) AS ship_days
UNION ALL SELECT CAST(2 AS INTEGER), CAST(49.99 AS DECIMAL(10,2)), CAST('paid' AS VARCHAR(20)), CAST(5 AS INTEGER)
UNION ALL SELECT CAST(3 AS INTEGER), CAST(0.00 AS DECIMAL(10,2)), CAST('refunded' AS VARCHAR(20)), CAST(NULL AS INTEGER)
UNION ALL SELECT CAST(4 AS INTEGER), CAST(NULL AS DECIMAL(10,2)), CAST('pending' AS VARCHAR(20)), CAST(7 AS INTEGER)
UNION ALL SELECT CAST(5 AS INTEGER), CAST(250.00 AS DECIMAL(10,2)), CAST('cancelled' AS VARCHAR(20)), CAST(1 AS INTEGER)
UNION ALL SELECT CAST(6 AS INTEGER), CAST(75.00 AS DECIMAL(10,2)), CAST(NULL AS VARCHAR(20)), CAST(3 AS INTEGER)
)
SELECT
order_id,
amount,
status,
ship_days,
-- Searched CASE: best for real conditions like ranges and NULL checks.
CASE
WHEN status IS NULL THEN 'status_missing'
WHEN status = 'cancelled' THEN 'do_not_ship'
WHEN amount IS NULL THEN 'missing_amount'
WHEN amount >= 100 THEN 'vip'
WHEN amount >= 50 THEN 'standard'
ELSE 'budget'
END AS customer_segment,
-- Simple CASE: good for exact matches only.
CASE status
WHEN 'paid' THEN 'billable'
WHEN 'pending' THEN 'waiting'
WHEN 'cancelled' THEN 'closed'
ELSE 'other'
END AS status_bucket,
-- Conditional sort key: CASE can help ORDER BY keep urgent rows first.
CASE
WHEN status = 'cancelled' THEN 2
ELSE 1
END AS sort_priority
FROM orders
ORDER BY sort_priority, order_id;
-- Edge case: simple CASE does NOT match NULL with WHEN NULL.
WITH orders AS (
SELECT CAST(6 AS INTEGER) AS order_id, CAST(75.00 AS DECIMAL(10,2)) AS amount, CAST(NULL AS VARCHAR(20)) AS status, CAST(3 AS INTEGER) AS ship_days
)
SELECT
order_id,
status,
CASE status
WHEN NULL THEN 'missing' -- This will never match.
ELSE 'not_missing'
END AS wrong_null_check,
CASE
WHEN status IS NULL THEN 'missing' -- Correct way to detect NULL.
ELSE 'not_missing'
END AS correct_null_check
FROM orders;Follow-up & Tricky Questions:
>, BETWEEN, and IS NULL, so it is more flexible.cancelled or urgent items at the end or beginning.WHEN NULL not match? Because NULL is not equal to anything, even another NULL. Use WHEN x IS NULL in searched CASE instead.Common Mistakes:
WHEN NULL to test for missing values. Fix: use IS NULL in searched CASE.Memory Hook: Think of CASE as a stack of folder tabs: SQL checks the tabs from top to bottom, grabs the first folder that fits, and stops there.
Cheat Sheet:
Practice Tasks:
small, medium, or large by amount.