SQL Reference

CASE Statement in SQL

Here's the thing about CASE WHEN. You're going to write it more than almost any other SQL construct in your first year as a data engineer, and nobody ever sits you down and teaches it properly. You pick up the syntax in pieces, you get something working, and then an interviewer asks you to bucket revenue into five tiers under a three minute timer and your brain locks. That's what we're fixing today.

Across 1,042 verified interview rounds we analyzed, conditional logic questions show up in roughly 28% of SQL challenges. That's not a niche topic. That's a core fluency test. If you can read searched CASE and simple CASE at a glance, and you've written both inside GROUP BY and ORDER BY enough times to stop flinching, you'll clear that bar.

28%

Conditional logic qs

429

Verified SQL rounds

61%

Senior-level questions

2

CASE forms to know

Source: DataDriven analysis of 1,042 verified data engineering interview rounds.

Simple CASE Syntax

Think of simple CASE as the gentle introduction. You hand it one column, you list the values you care about, and you get back a label for each match. It's the form you'll reach for when you're renaming status codes or rewriting enum strings. You're not doing anything clever here, and that's fine: most production CASE expressions live at this level of complexity, and interviewers respect the candidate who knows when the simple form is enough.

-- Simple CASE: compare one expression to fixed values
SELECT
  order_id,
  status,
  CASE status
    WHEN 'pending'   THEN 'Awaiting Processing'
    WHEN 'shipped'   THEN 'In Transit'
    WHEN 'delivered' THEN 'Complete'
    WHEN 'returned'  THEN 'Returned to Warehouse'
    ELSE 'Unknown'
  END AS status_label
FROM orders;

Simple CASE only supports equality checks. You cannot write CASE status WHEN LIKE '%active%'. For anything beyond exact value matching, use the searched CASE syntax below.

When to Use Simple CASE

Mapping codes to labels: Converting status codes, country codes, or category IDs into readable names when a lookup table does not exist or when you need the mapping inline.

Rewriting enum values: Translating database enums into display values for reports or API responses.

Ordering by custom priority: Using CASE in ORDER BY to sort rows by business priority rather than alphabetical or numeric order.

-- Custom sort order using simple CASE
SELECT task_name, priority
FROM tasks
ORDER BY
  CASE priority
    WHEN 'critical' THEN 1
    WHEN 'high'     THEN 2
    WHEN 'medium'   THEN 3
    WHEN 'low'      THEN 4
    ELSE 5
  END;

Searched CASE Syntax

Searched CASE evaluates independent boolean expressions. Each WHEN clause can test a different column, use a different operator, or combine multiple conditions with AND/OR. This is the form you will use 90% of the time.

-- Searched CASE: each WHEN is an independent condition
SELECT
  employee_id,
  name,
  salary,
  CASE
    WHEN salary >= 150000 THEN 'Executive'
    WHEN salary >= 100000 THEN 'Senior'
    WHEN salary >= 70000  THEN 'Mid-Level'
    WHEN salary >= 40000  THEN 'Junior'
    ELSE 'Entry'
  END AS salary_band
FROM employees;

Order matters: CASE evaluates WHEN clauses top to bottom and stops at the first match. An employee earning $160,000 matches both the first and second conditions, but only 'Executive' is returned because it comes first. If you reversed the order, that employee would be labeled 'Senior' because $160,000 >= $100,000 matches before reaching the $150,000 check.

Multi-Column Conditions

Searched CASE can reference multiple columns in each WHEN clause. This is useful for classification logic that depends on more than one attribute.

-- Classify customers by both spend and tenure
SELECT
  customer_id,
  total_spend,
  months_active,
  CASE
    WHEN total_spend >= 10000 AND months_active >= 24
      THEN 'VIP'
    WHEN total_spend >= 5000 OR months_active >= 12
      THEN 'Established'
    WHEN months_active >= 3
      THEN 'Growing'
    ELSE 'New'
  END AS customer_tier
FROM customers;

NULL Handling in CASE

CASE does not treat NULL as equal to anything, including another NULL. If you need to check for NULL, use IS NULL explicitly.

-- NULL-safe CASE
SELECT
  order_id,
  shipped_date,
  CASE
    WHEN shipped_date IS NULL THEN 'Not Shipped'
    WHEN shipped_date > CURRENT_DATE - INTERVAL '7 days'
      THEN 'Recently Shipped'
    ELSE 'Shipped'
  END AS shipping_status
FROM orders;

-- WRONG: this never matches NULL
-- CASE shipped_date WHEN NULL THEN 'Not Shipped' END
-- NULL = NULL evaluates to NULL (not TRUE), so it skips

CASE with Aggregation: Conditional Counting and Summing

The most powerful use of CASE in data engineering is inside aggregate functions. This pattern lets you count or sum different subsets of rows in a single pass without needing multiple queries or subqueries. Interviewers test this pattern constantly because it shows you can think about data at the group level.

-- Conditional count: orders by status in one query
SELECT
  DATE_TRUNC('month', order_date) AS order_month,
  COUNT(*) AS total_orders,
  COUNT(CASE WHEN status = 'delivered' THEN 1 END) AS delivered,
  COUNT(CASE WHEN status = 'returned' THEN 1 END) AS returned,
  COUNT(CASE WHEN status = 'pending' THEN 1 END) AS pending
FROM orders
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY order_month;

This works because COUNT ignores NULLs. When the CASE condition is false and there is no ELSE, the expression returns NULL. COUNT skips it. Only rows matching the condition contribute to the count.

-- Conditional sum: revenue by region in one query
SELECT
  product_category,
  SUM(CASE WHEN region = 'North' THEN revenue ELSE 0 END) AS north_revenue,
  SUM(CASE WHEN region = 'South' THEN revenue ELSE 0 END) AS south_revenue,
  SUM(CASE WHEN region = 'East' THEN revenue ELSE 0 END) AS east_revenue,
  SUM(CASE WHEN region = 'West' THEN revenue ELSE 0 END) AS west_revenue,
  SUM(revenue) AS total_revenue
FROM sales
GROUP BY product_category;

Interview note: This is called a pivot. When an interviewer says “pivot the data so each status is a column,” they want this pattern: SUM(CASE WHEN ... THEN value ELSE 0 END) or COUNT(CASE WHEN ... THEN 1 END) for each category.

CASE with AVG and Ratios

-- Delivery rate by month
SELECT
  DATE_TRUNC('month', order_date) AS month,
  ROUND(
    100.0
    * COUNT(CASE WHEN status = 'delivered' THEN 1 END)
    / NULLIF(COUNT(*), 0),
    1
  ) AS delivery_rate_pct
FROM orders
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY month;

NULLIF prevents division by zero. If a month has zero orders, NULLIF(COUNT(*), 0) returns NULL, and the division produces NULL instead of an error.

CASE with Window Functions

CASE works inside window function expressions. This combination lets you compute conditional running totals, conditional rankings, and row-level flags based on partition-level calculations.

-- Running total of delivered revenue only
SELECT
  order_id,
  order_date,
  status,
  revenue,
  SUM(CASE WHEN status = 'delivered' THEN revenue ELSE 0 END)
    OVER (ORDER BY order_date) AS running_delivered_revenue
FROM orders
ORDER BY order_date;
-- Flag rows where an employee's salary is above department average
SELECT
  employee_id,
  department,
  salary,
  AVG(salary) OVER (PARTITION BY department) AS dept_avg,
  CASE
    WHEN salary > AVG(salary) OVER (PARTITION BY department)
      THEN 'Above Average'
    WHEN salary = AVG(salary) OVER (PARTITION BY department)
      THEN 'At Average'
    ELSE 'Below Average'
  END AS salary_position
FROM employees;
-- Classify change direction from previous row
SELECT
  date,
  metric_value,
  LAG(metric_value) OVER (ORDER BY date) AS prev_value,
  CASE
    WHEN metric_value > LAG(metric_value) OVER (ORDER BY date)
      THEN 'Increase'
    WHEN metric_value < LAG(metric_value) OVER (ORDER BY date)
      THEN 'Decrease'
    WHEN metric_value = LAG(metric_value) OVER (ORDER BY date)
      THEN 'No Change'
    ELSE 'N/A'  -- first row has no previous value
  END AS direction
FROM daily_metrics;

CASE in Data Engineering Pipelines

In production pipelines, CASE is used for data cleaning, standardization, and transformation. Here are the patterns that show up in real dbt models and ETL jobs.

Data Cleaning: Standardize Messy Input

-- Standardize country names from raw data
SELECT
  user_id,
  CASE
    WHEN UPPER(TRIM(country)) IN ('US', 'USA', 'UNITED STATES', 'U.S.A.')
      THEN 'United States'
    WHEN UPPER(TRIM(country)) IN ('UK', 'GB', 'UNITED KINGDOM', 'GREAT BRITAIN')
      THEN 'United Kingdom'
    WHEN UPPER(TRIM(country)) IN ('CA', 'CAN', 'CANADA')
      THEN 'Canada'
    ELSE INITCAP(TRIM(country))
  END AS country_standardized
FROM raw_users;

SCD Type 2 Flag

-- Mark current vs historical records
SELECT
  customer_id,
  name,
  email,
  valid_from,
  valid_to,
  CASE
    WHEN valid_to IS NULL OR valid_to > CURRENT_TIMESTAMP
      THEN TRUE
    ELSE FALSE
  END AS is_current
FROM customer_history;

Data Quality Scoring

-- Score row completeness for a data quality dashboard
SELECT
  record_id,
  (
    CASE WHEN email IS NOT NULL AND email != '' THEN 1 ELSE 0 END
    + CASE WHEN phone IS NOT NULL AND phone != '' THEN 1 ELSE 0 END
    + CASE WHEN address IS NOT NULL AND address != '' THEN 1 ELSE 0 END
    + CASE WHEN name IS NOT NULL AND name != '' THEN 1 ELSE 0 END
  ) AS completeness_score,
  CASE
    WHEN (
      CASE WHEN email IS NOT NULL AND email != '' THEN 1 ELSE 0 END
      + CASE WHEN phone IS NOT NULL AND phone != '' THEN 1 ELSE 0 END
      + CASE WHEN address IS NOT NULL AND address != '' THEN 1 ELSE 0 END
      + CASE WHEN name IS NOT NULL AND name != '' THEN 1 ELSE 0 END
    ) = 4 THEN 'Complete'
    WHEN (
      CASE WHEN email IS NOT NULL AND email != '' THEN 1 ELSE 0 END
      + CASE WHEN phone IS NOT NULL AND phone != '' THEN 1 ELSE 0 END
      + CASE WHEN address IS NOT NULL AND address != '' THEN 1 ELSE 0 END
      + CASE WHEN name IS NOT NULL AND name != '' THEN 1 ELSE 0 END
    ) >= 2 THEN 'Partial'
    ELSE 'Poor'
  END AS quality_tier
FROM contacts;

CASE Statement Patterns for Interviews

These are the CASE patterns that come up most in interview SQL questions. Each one solves a specific category of problem.

Pattern 1: Bucketing Continuous Values

“Group users by age bracket” or “categorize orders by amount.” Use searched CASE with range conditions. List ranges from largest to smallest (or smallest to largest) to avoid overlaps.

SELECT
  CASE
    WHEN age >= 65 THEN '65+'
    WHEN age >= 45 THEN '45-64'
    WHEN age >= 25 THEN '25-44'
    WHEN age >= 18 THEN '18-24'
    ELSE 'Under 18'
  END AS age_group,
  COUNT(*) AS user_count
FROM users
GROUP BY 1
ORDER BY MIN(age);

Pattern 2: Conditional Aggregation (Pivot)

“Show each department's headcount broken out by gender.” Use CASE inside COUNT or SUM to create columns from row values.

SELECT
  department,
  COUNT(CASE WHEN gender = 'M' THEN 1 END) AS male_count,
  COUNT(CASE WHEN gender = 'F' THEN 1 END) AS female_count,
  COUNT(CASE WHEN gender NOT IN ('M', 'F') THEN 1 END) AS other_count,
  COUNT(*) AS total
FROM employees
GROUP BY department;

Pattern 3: Decode Boolean Flags

Converting 0/1, true/false, or Y/N into readable labels for reporting.

SELECT
  user_id,
  CASE is_verified
    WHEN TRUE THEN 'Verified'
    WHEN FALSE THEN 'Unverified'
  END AS verification_status,
  CASE has_subscription
    WHEN TRUE THEN 'Subscriber'
    WHEN FALSE THEN 'Free Tier'
  END AS subscription_status
FROM users;

Pattern 4: Safe Division

Prevent division-by-zero errors in ratio calculations.

SELECT
  campaign_id,
  impressions,
  clicks,
  CASE
    WHEN impressions = 0 THEN 0
    ELSE ROUND(100.0 * clicks / impressions, 2)
  END AS click_through_rate
FROM ad_campaigns;

Common CASE Statement Mistakes

These mistakes appear in interviews and production code. Knowing them prevents debugging headaches.

Overlapping Conditions

Because CASE stops at the first match, overlapping ranges silently produce wrong results.

-- BUG: salary of 120000 matches first condition
CASE
  WHEN salary > 50000 THEN 'Mid'     -- 120000 > 50000 is TRUE
  WHEN salary > 100000 THEN 'Senior' -- never reached for 120000
END

-- FIX: order from most restrictive to least
CASE
  WHEN salary > 100000 THEN 'Senior'
  WHEN salary > 50000 THEN 'Mid'
  ELSE 'Junior'
END

Missing ELSE Returns NULL

Forgetting ELSE when you do not want NULL results.

-- BUG: status 'canceled' returns NULL
SELECT CASE status
  WHEN 'active' THEN 'Active'
  WHEN 'inactive' THEN 'Inactive'
END AS label  -- 'canceled' -> NULL

-- FIX: always handle the default
SELECT CASE status
  WHEN 'active' THEN 'Active'
  WHEN 'inactive' THEN 'Inactive'
  ELSE 'Other'
END AS label

Mixing Data Types in THEN Clauses

All THEN and ELSE values must be the same data type (or implicitly castable). Mixing strings and numbers causes errors in strict-type databases.

-- BUG in strict-type engines: mixing int and text
CASE WHEN quantity > 0 THEN quantity ELSE 'N/A' END

-- FIX: cast to consistent type
CASE WHEN quantity > 0 THEN CAST(quantity AS TEXT) ELSE 'N/A' END

CASE Statement in SQL FAQ

What is the CASE statement in SQL?+
The CASE statement is SQL's way of writing if/then/else logic inside a query. It evaluates conditions in order and returns a value when the first condition is true. If no condition matches and there is no ELSE clause, it returns NULL. CASE works in SELECT, WHERE, ORDER BY, GROUP BY, and HAVING clauses. It does not change the data in the table. It only changes what the query returns.
What is the difference between simple CASE and searched CASE?+
Simple CASE compares one expression to a list of values: CASE status WHEN 'active' THEN 1 WHEN 'inactive' THEN 0 END. It works like a switch statement. Searched CASE evaluates independent boolean conditions: CASE WHEN salary > 100000 THEN 'high' WHEN salary > 50000 THEN 'mid' ELSE 'low' END. Searched CASE is more flexible because each WHEN can test a different column or a different comparison operator. In practice, searched CASE covers every scenario that simple CASE covers, plus more. Most SQL developers default to searched CASE.
Can you use CASE in a WHERE clause?+
Yes, but it is rarely the best approach. You can write WHERE CASE WHEN ... THEN 1 ELSE 0 END = 1, but this is harder to read than using AND/OR logic directly. The main use case for CASE in WHERE is when you need conditional filtering based on a parameter, such as a stored procedure argument that determines which filter to apply. For standard queries, rewrite the logic using AND/OR instead.
What happens if no CASE condition matches and there is no ELSE?+
The CASE expression returns NULL. This catches many people off guard because there is no error or warning. If you use the CASE result in a SUM or COUNT, NULL values are silently skipped. If you use it in a string concatenation, the entire result becomes NULL in most databases. Always include an ELSE clause unless you specifically want NULL for unmatched cases.

You'll be fluent by next Tuesday

Pick twenty CASE problems, work through them over a week, and you'll stop second-guessing WHEN ordering forever. We'll track which patterns you're solid on and which ones still need reps.