Skip to main content

CASE: Conditional Columns

Level 2: Level 2: Aggregation & Joins (Combining Source Data)medium25 minCASE WHENsearched vs simple CASEconditional aggregation

Branch a value inside a query for bucketing and pivoting.

CASE is SQL's if/else

CASE is SQL's if/else. It lets you compute a different value per row based on conditions: bucketing a numeric measure into labels, mapping codes to names, or (the DE power move) pivoting rows into columns via conditional aggregation.

Searched CASE: the general form

Table
Each total_cents falls through the WHEN branches top-to-bottom; the first true branch sets size_bucket. 9900 is not >= 10000 so it lands in 'medium', while 10000 crosses the threshold into 'large'.
order_idtotal_centssize_bucket
100500small
1012000medium
1029900medium
10310000large
10415000large
Each total_cents falls through the WHEN branches top-to-bottom; the first true branch sets size_bucket. 9900 is not >= 10000 so it lands in 'medium', while 10000 crosses the threshold into 'large'.

Conditions can be anything:

SELECT
  order_id,
  CASE
    WHEN total_cents >= 10000 THEN 'large'
    WHEN total_cents >= 2000  THEN 'medium'
    ELSE 'small'
  END AS size_bucket
FROM orders;

Conditions are tested top-to-bottom; the first true branch wins, so order them from most to least specific. If none match and there's no ELSE, the result is NULL.

Simple CASE: shorthand for equality checks

Shorthand when you're comparing one expression to constants:

CASE status WHEN 'paid' THEN 1 WHEN 'cancelled' THEN 0 ELSE NULL END

Anatomy

CASE WHEN cond1 THEN val1     ← first matching branch wins
     WHEN cond2 THEN val2
     ELSE fallback            ← optional; without it, no-match → NULL
END AS alias

The conditional-aggregation trick: pivoting rows into columns

Wrap a CASE inside an aggregate and you turn categories into columns. To count paid vs cancelled side by side, per day:

SELECT
  order_date,
  SUM(CASE WHEN status = 'paid'      THEN 1 ELSE 0 END) AS paid_count,
  SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count
FROM orders
GROUP BY order_date;

Each CASE emits 1 for the rows it wants and 0 otherwise; the SUM counts them. This is how you build a classic reporting mart (one row per day, one column per status) without a dedicated PIVOT operator.

In the warehouse this differs: PIVOT. Snowflake, SQL Server, and others ship a dedicated PIVOT operator, but its syntax is warehouse-specific. Conditional aggregation with SUM(CASE …) is the fully portable equivalent (it runs on every engine, including SQLite), so we author pivots that way here.

Keep it readable / common pitfall

Every branch of a CASE should return the same type. Mixing 'small' (text) and 0 (number) across branches yields inconsistent typing that some engines coerce and others reject. Also: COUNT(CASE WHEN … THEN 1 END) works too (COUNT skips the NULL from the missing ELSE), but SUM(CASE … THEN 1 ELSE 0 END) is the clearer, more portable idiom.

Recap

CASE branches a value per row (first true WHEN wins) for bucketing; wrapped inside SUM/COUNT it becomes conditional aggregation: the portable way to pivot categories into side-by-side columns.

Sample data for this example
CREATE TABLE orders (
  order_id    INTEGER PRIMARY KEY,
  total_cents INTEGER
);
INSERT INTO orders VALUES
  (100,   500),
  (101,  2000),
  (102,  9900),
  (103, 10000),
  (104, 15000);
Worked example (SQL)
SELECT
  order_id,
  CASE
    WHEN total_cents >= 10000 THEN 'large'
    WHEN total_cents >= 2000  THEN 'medium'
    ELSE 'small'
  END AS size_bucket
FROM orders;

Apply

Your turn

The task this lesson builds to.

Bucket each order by total_cents into size_bucket: 'large' if >= 10000, 'medium' if >= 2000 (and < 10000), else 'small'. Return order_id and size_bucket, sorted by order_id.

3 hints and 1 automated check are waiting in the workspace.

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Build a daily status report using conditional aggregation: one row per order_date with three count columns: paid_count, shipped_count, cancelled_count. Return order_date, paid_count, shipped_count, cancelled_count, sorted by order_date. Every date present in the source must appear, and a status with zero occurrences that day must show 0 (not NULL).

4 hints and 1 automated check are waiting in the workspace.