Readineer
PATTERN 5SQL Patterns / Summarising Data

SQL CASE WHEN: adding conditional logic and categorising data

Learn how to create values based on conditions with CASE WHEN, translate business rules into categories, handle defaults with ELSE, and avoid mistakes when conditions overlap.

8 min read11 sections2 predictions
01Foundation

Creating values from conditions

Data stored in a table often contains raw values, while the result we need contains business-friendly categories or labels. Consider the same orders table:

Orders
order_idcustomerproductquantityunit_pricediscount_amountstatus
1001AshaWireless Mouse2750150paid
1002BenMechanical Keyboard13200NULLpending
1003CarlaLaptop Sleeve31200300paid
1004DevOffice Chair18500NULLcancelled
1005AshaWireless Mouse1750NULLpaid
1006BenMonitor Stand21800200paid

Suppose the business wants orders to be classified as Small, Medium, or Large based on their order amount. The table does not contain an order_size column. We can calculate the order amount quantity * unit_price and then use SQL to assign a category based on that value. This is what CASE is designed for.

02The expression

CASE WHEN

A CASE expression evaluates conditions and returns a value based on which condition matches. A common structure is:

Query
CASE
    WHEN condition_1 THEN value_1
    WHEN condition_2 THEN value_2
    ELSE default_value
END

You can read this as: if condition 1 is true, return value 1; otherwise, if condition 2 is true, return value 2; otherwise, return the default value. Consider this requirement: label orders worth 5,000 or more as Large, orders worth 2,000 or more as Medium, and everything else as Small.

Query
SELECT
    order_id,
    customer,
    quantity * unit_price AS order_amount,
    CASE
        WHEN quantity * unit_price >= 5000 THEN 'Large'
        WHEN quantity * unit_price >= 2000 THEN 'Medium'
        ELSE 'Small'
    END AS order_size
FROM orders
ORDER BY order_id;
Result
order_idcustomerorder_amountorder_size
1001Asha1500Small
1002Ben3200Medium
1003Carla3600Medium
1004Dev8500Large
1005Asha750Small
1006Ben3600Medium

The values Large, Medium, and Small do not need to exist in the original table. CASE creates them in the query result.

How CASE evaluates conditions

Consider order 1004, whose amount is 8500. SQL evaluates WHEN order_amount >= 5000. The condition is TRUE, so SQL returns Large and stops evaluating additional WHEN conditions for that CASE. Now consider order 1002, amount 3200: the first condition 3200 >= 5000 is false, so SQL moves to 3200 >= 2000, which is true, and the result is Medium. Finally, order 1005 has amount 750: neither condition matches, so SQL reaches ELSE 'Small'.

Key idea
CASE returns the result for the first WHEN condition that evaluates to TRUE. This becomes especially important when conditions overlap.
03The featured failure

When the conditions are in the wrong order

Suppose someone writes the same categorisation like this. At first glance, both rules appear to be present. But consider the order worth 8500.

PAUSE & PREDICTWhat category does SQL assign?
Query
CASE
    WHEN quantity * unit_price >= 2000 THEN 'Medium'
    WHEN quantity * unit_price >= 5000 THEN 'Large'
    ELSE 'Small'
END
Your prediction

The fix: put more specific conditions first

For overlapping ranges, check the more restrictive condition first:

Query
CASE
    WHEN quantity * unit_price >= 5000 THEN 'Large'
    WHEN quantity * unit_price >= 2000 THEN 'Medium'
    ELSE 'Small'
END

Now an 8500 order matches >= 5000 first and becomes Large. A 3200 order fails the first condition and matches >= 2000, so it becomes Medium.

Key idea
When conditions overlap, arrange them from the most specific or restrictive condition to the broader condition. For numeric thresholds, that often means checking the highest threshold first.
04A common confusion

CASE does not filter rows

CASE and WHERE both use conditions, but they solve different problems. Consider:

Query
SELECT
    order_id,
    status,
    CASE
        WHEN status = 'paid' THEN 'Completed'
        ELSE 'Not completed'
    END AS payment_state
FROM orders;
Result
order_idstatuspayment_state
1001paidCompleted
1002pendingNot completed
1003paidCompleted
1004cancelledNot completed
1005paidCompleted
1006paidCompleted

Every order is still returned; CASE changes the value produced for each row. Compare that with SELECT order_id, status FROM orders WHERE status = 'paid';, where WHERE removes rows that do not satisfy the condition.

Key idea
WHERE decides whether a row remains in the result. CASE decides which value should be produced for a row.
05Everyday uses

Business-friendly labels

Raw database values are not always the values we want to display. Our status column contains paid, pending, and cancelled. Suppose a report should show Completed, Awaiting payment, and Cancelled:

Query
SELECT
    order_id,
    customer,
    status,
    CASE
        WHEN status = 'paid' THEN 'Completed'
        WHEN status = 'pending' THEN 'Awaiting payment'
        WHEN status = 'cancelled' THEN 'Cancelled'
        ELSE 'Other'
    END AS status_label
FROM orders
ORDER BY order_id;
Result
order_idstatusstatus_label
1001paidCompleted
1002pendingAwaiting payment
1003paidCompleted
1004cancelledCancelled
1005paidCompleted
1006paidCompleted

The stored value remains unchanged. CASE only creates a different representation in this query result.

The simple CASE form

When all conditions compare one expression with exact values, SQL also supports a shorter form:

Query
CASE status
    WHEN 'paid' THEN 'Completed'
    WHEN 'pending' THEN 'Awaiting payment'
    WHEN 'cancelled' THEN 'Cancelled'
    ELSE 'Other'
END

This works well for direct equality mappings. But it is not suitable for rules such as amount >= 5000 or status = 'paid' AND discount_amount > 100. For those cases, use the more flexible searched form with a full condition after each WHEN.

CASE can use multiple conditions

A WHEN condition can contain the same logical expressions used in a WHERE clause. Suppose the business wants to identify high-value paid orders: the order must be paid and worth at least 3,000.

Query
SELECT
    order_id,
    customer,
    status,
    quantity * unit_price AS order_amount,
    CASE
        WHEN status = 'paid'
         AND quantity * unit_price >= 3000
            THEN 'High-value paid order'
        ELSE 'Other'
    END AS order_type
FROM orders
ORDER BY order_id;
Result
order_idstatusorder_amountorder_type
1001paid1500Other
1002pending3200Other
1003paid3600High-value paid order
1004cancelled8500Other
1005paid750Other
1006paid3600High-value paid order

Order 1002 is worth more than 3,000, but it is pending. Order 1004 is worth 8,500, but it is cancelled. Both conditions must be true.

Using CASE for multiple categories

Business categorisation often contains more than two outcomes. Suppose we define order priority as: large paid order → Priority 1, other paid order → Priority 2, pending order → Priority 3, everything else → Review.

Query
CASE
    WHEN status = 'paid'
     AND quantity * unit_price >= 5000
        THEN 'Priority 1'
    WHEN status = 'paid'
        THEN 'Priority 2'
    WHEN status = 'pending'
        THEN 'Priority 3'
    ELSE 'Review'
END AS priority

Notice the order of conditions. The more specific rule (status = 'paid' AND order_amount >= 5000) comes before the broader rule (status = 'paid'). Otherwise, every paid order would match the broader condition first, and no paid order could ever become Priority 1.

06Defaults and edges

ELSE, NULL, gaps, and types

The ELSE value handles rows that match none of the previous conditions. For status = cancelled in a CASE that only tests paid and pending, neither WHEN matches, so the result is Other. This provides an explicit fallback.

ELSE is optional. Without it, a row that matches no WHEN makes the whole CASE return NULL. That may be exactly what you want. But if the business expects every row to have a category, omitting ELSE can quietly introduce missing values. Ask: what should happen when none of the conditions match? If there is a meaningful default, write it explicitly (ELSE 'Other'); if NULL genuinely represents the correct outcome, omitting ELSE may be appropriate.

CASE and NULL

The conditions inside CASE follow the same NULL rules covered earlier. This does not correctly detect missing values, because discount_amount = NULL evaluates to UNKNOWN, not TRUE:

Query
CASE
    WHEN discount_amount = NULL THEN 'No discount'
    ELSE 'Discount recorded'
END

Use IS NULL:

Query
CASE
    WHEN discount_amount IS NULL THEN 'No discount recorded'
    ELSE 'Discount recorded'
END

The same three-valued logic rules apply whether the condition appears inside WHERE or CASE.

Gotcha: categories can have gaps

Check the boundaries

Suppose the intended rules are: 5000 or more → Large, 2000 to below 5000 → Medium, below 2000 → Small. Someone might write a version whose Medium branch is >= 2000 AND order_amount < 4000. What happens to order_amount = 4500? It does not match any condition, and without ELSE the result becomes NULL. The SQL is valid, but the category definitions contain a gap.

When building ranges, check the boundaries carefully: what happens exactly at 2,000? Exactly at 5,000? Is there any value that matches no category? Can one value match several categories?

CASE result values should be compatible

Consider a CASE where one branch returns text ('Completed') and another returns a number (0). Database systems need to determine a common result type for a CASE expression, and mixing unrelated types can produce conversion problems or errors depending on the database and values involved. Prefer branches with compatible meanings and data types: all text, or all numbers. A CASE expression represents one output column, so its possible results should belong naturally to the same kind of value.

07Recognition cues

When CASE may be useful, and where it goes wrong

Look for these situations when CASE may be useful:

Raw values need more readable labels.
Numeric values need to be divided into categories, or ranges need names such as Low, Medium, High.
A report needs different output depending on a condition, or several columns together determine a classification.
A value should become Yes or No, or status codes need business-friendly descriptions.
A query needs a fallback when no specific condition matches.

Also look for these common mistakes:

Broader conditions appear before more specific ones.
Overlapping conditions produce the wrong category, or range definitions contain gaps.
ELSE is missing even though every row needs a value.
= NULL is used instead of IS NULL.
Different branches return unrelated data types.
08A practical mental model

Turn a business rule into CASE with four questions

1 · What value are we creating?

e.g. order_size.

2 · What are the possible outcomes?

Large, Medium, Small.

3 · What condition defines each?

Large → amount >= 5000, Medium → amount >= 2000, Small → everything else.

4 · Can the conditions overlap?

Yes: 8500 satisfies both >= 5000 and >= 2000, so the more restrictive rule must come first.

Query
CASE
    WHEN quantity * unit_price >= 5000 THEN 'Large'
    WHEN quantity * unit_price >= 2000 THEN 'Medium'
    ELSE 'Small'
END
Key idea
Thinking through the categories first makes the SQL much easier to verify.
09Check your understanding

One more prediction

Consider these order amounts. The business rule is: 5000 or more → Large, 2000 or more → Medium, below 2000 → Small. Which expression is correct?

orders
order_idorder_amount
10011500
10023200
10033600
10048500
1005750
10063600
ONE MORE PREDICTIONWhich expression is correct?
Query
-- Option A
CASE
    WHEN order_amount >= 2000 THEN 'Medium'
    WHEN order_amount >= 5000 THEN 'Large'
    ELSE 'Small'
END

-- Option B
CASE
    WHEN order_amount >= 5000 THEN 'Large'
    WHEN order_amount >= 2000 THEN 'Medium'
    ELSE 'Small'
END

-- Option C
CASE
    WHEN order_amount < 2000 THEN 'Small'
    WHEN order_amount >= 5000 THEN 'Large'
END

-- Option D
CASE
    WHEN order_amount = 5000 THEN 'Large'
    WHEN order_amount = 2000 THEN 'Medium'
    ELSE 'Small'
END
Your prediction
10Quick check

Lock these in

What does CASE do?

It produces a value based on one or more conditions.

Does CASE remove rows?

No. CASE controls values in the result; WHERE controls which rows remain.

What happens when several WHEN conditions are true?

The result from the first matching WHEN is returned.

Why does condition order matter?

Broader conditions can prevent more specific conditions from ever being reached.

What happens when no condition matches and there is no ELSE?

The CASE expression returns NULL.

How should NULL be checked inside CASE?

Use IS NULL, not = NULL.

When is simple CASE useful?

When one expression is being compared against several exact values.

11Summary

Summary

CASE allows SQL to create values based on conditions. It can turn raw values into business-friendly labels, create categories from numeric ranges, and combine several conditions. The most important rule is that CASE returns the result from the first WHEN condition that evaluates to TRUE. Because of that, condition order matters whenever rules overlap.

Key idea
WHEN amount >= 2000 THEN 'Medium' listed before WHEN amount >= 5000 THEN 'Large' will never classify an 8500 order as Large: the broader rule captures it first. Write your categories as business rules before translating them into SQL, check their boundaries, make sure every required value has an outcome, and arrange overlapping rules in the correct order.

Next, we will use CASE together with aggregate functions to calculate multiple business metrics from the same set of rows using conditional aggregation.

Go deeper

Ask Colearn about this pattern

Not sure why a category never shows up, or why your CASE returns blanks? Ask about the concept, or paste a simplified version of your expression.

Ask Colearn

3 of 3 free questions left

Instant answers, grounded in the same verified material the diagnostic grades against.

Why is my large order labelled Medium instead of Large?

CASE returns the result of the first WHEN that is true, then stops. If a broader threshold (>= 2000 → Medium) is listed before a more specific one (>= 5000 → Large), an 8500 order matches Medium first and never reaches Large.

Order the branches from most restrictive to broadest; for numeric ranges, that usually means the highest threshold first.

from the unit →
What happens when no WHEN matches and there is no ELSE?

The CASE expression returns NULL for that row. ELSE is optional, so an unmatched row silently becomes a missing value.

If every row should have a category, write an explicit ELSE; only omit it when NULL is genuinely the right outcome.

from the unit →
Why doesn't WHEN discount_amount = NULL work inside CASE?

A comparison to NULL is UNKNOWN, not TRUE, so that WHEN never matches; the same three-valued logic that applies inside WHERE.

Test for missing values with WHEN discount_amount IS NULL instead.

from the unit →
How do I combine CASE with aggregates to count by category?

Wrap a CASE inside an aggregate, for example SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END), to total or count only the rows that match a condition.

That technique is conditional aggregation; it lets one grouped query produce several business metrics at once.

from the unit →

That’s the free taste

Two ways to go deeper.

Don’t paste credentials, personal data, or confidential production records.

Related patterns

P04Pattern · 8 minAggregation & GROUP BYSummarise many rows into per-group metrics, the natural home for CASE.P02Pattern · 8 minNULL & UNKNOWNWhy = NULL never matches, inside CASE just as inside WHERE.P06Pattern · 7 minConditional aggregationPut CASE inside SUM or COUNT to compute several metrics at once.
Up next · Pattern 6
Conditional aggregation

Next, we put CASE inside aggregate functions to calculate several business metrics from one set of rows: conditional aggregation.

Continue to P6 →Browse all patterns