Learning Guides
Menu

Conditional Logic

8 min readLearning SQL

Conditional Logic

SQL's CASE expression adds if/then/else logic to your queries. You can transform data, create categories, handle special cases, and make decisions—all within a single query.

The CASE Expression

CASE comes in two forms: simple and searched.

Searched CASE (Most Common)

Evaluates conditions until one is true:

SQL
SELECT
    title,
    price,
    CASE
        WHEN price < 14 THEN 'Budget'
        WHEN price < 17 THEN 'Standard'
        ELSE 'Premium'
    END AS price_tier
FROM books;
titlepriceprice_tier
Animal Farm12.99Budget
Pride and Prejudice14.99Standard
198415.99Standard
Norwegian Wood16.99Standard
Kafka on the Shore17.99Premium
One Hundred Years of Solitude18.99Premium

Note

CASE evaluates conditions in order and returns the result for the first TRUE condition. Once a match is found, remaining conditions are skipped.

Simple CASE

Compares an expression to specific values:

SQL
SELECT
    order_id,
    status,
    CASE status
        WHEN 'pending' THEN 'Awaiting Processing'
        WHEN 'shipped' THEN 'On Its Way'
        WHEN 'completed' THEN 'Delivered'
        WHEN 'cancelled' THEN 'Cancelled'
        ELSE 'Unknown'
    END AS status_description
FROM orders;
order_idstatusstatus_description
1completedDelivered
2completedDelivered
3shippedOn Its Way

Without ELSE, unmatched cases return NULL:

SQL
SELECT
    title,
    CASE
        WHEN price > 17 THEN 'Expensive'
        WHEN price > 14 THEN 'Moderate'
        -- No ELSE: books under $14 get NULL
    END AS label
FROM books;

Warning

Always include ELSE to handle unexpected values. Silent NULLs can cause confusion downstream.

CASE in Different Clauses

In SELECT (Computed Columns)

SQL
SELECT
    title,
    stock_quantity,
    CASE
        WHEN stock_quantity = 0 THEN 'Out of Stock'
        WHEN stock_quantity < 10 THEN 'Critical'
        WHEN stock_quantity < 25 THEN 'Low'
        WHEN stock_quantity < 40 THEN 'Normal'
        ELSE 'Well Stocked'
    END AS stock_status
FROM books;

In ORDER BY

SQL
-- Custom sort order
SELECT title, status FROM orders
ORDER BY CASE status
    WHEN 'pending' THEN 1
    WHEN 'shipped' THEN 2
    WHEN 'completed' THEN 3
    WHEN 'cancelled' THEN 4
    ELSE 5
END;

In WHERE

SQL
-- Dynamic filtering based on a parameter
SET @include_expensive = TRUE;
 
SELECT * FROM books
WHERE
    CASE
        WHEN @include_expensive THEN TRUE
        ELSE price <= 15
    END;

In GROUP BY

SQL
SELECT
    CASE
        WHEN price < 15 THEN 'Under $15'
        WHEN price < 18 THEN '$15-$18'
        ELSE 'Over $18'
    END AS price_range,
    COUNT(*) AS book_count,
    AVG(price) AS avg_price
FROM books
GROUP BY CASE
    WHEN price < 15 THEN 'Under $15'
    WHEN price < 18 THEN '$15-$18'
    ELSE 'Over $18'
END;
price_rangebook_countavg_price
Under $15213.99
$15-$18316.99
Over $18118.99

In UPDATE

SQL
-- Bulk price adjustment by category
UPDATE books
SET price = CASE
    WHEN stock_quantity > 40 THEN price * 0.9   -- Discount overstocked
    WHEN stock_quantity < 10 THEN price * 1.1   -- Premium for low stock
    ELSE price
END;

Nested CASE Expressions

CASE can be nested for complex logic:

SQL
SELECT
    title,
    price,
    stock_quantity,
    CASE
        WHEN stock_quantity = 0 THEN 'Unavailable'
        ELSE CASE
            WHEN price > 17 THEN 'Premium - In Stock'
            WHEN price > 14 THEN 'Standard - In Stock'
            ELSE 'Budget - In Stock'
        END
    END AS availability
FROM books;

Note

Deeply nested CASE expressions become hard to read. Consider breaking complex logic into CTEs or computed columns if nesting gets too deep.

COALESCE: NULL Handling

COALESCE returns the first non-NULL value from a list:

SQL
SELECT
    first_name,
    COALESCE(phone, email, 'No contact') AS primary_contact
FROM customers;

Common COALESCE Patterns

SQL
-- Default value for NULL
SELECT COALESCE(discount_percent, 0) AS discount FROM products;
 
-- Fallback chain
SELECT COALESCE(nickname, first_name, email) AS display_name FROM users;
 
-- Division with NULL protection
SELECT total / COALESCE(NULLIF(count, 0), 1) AS average FROM stats;

NULLIF: Create NULL Conditionally

NULLIF returns NULL if two values are equal:

SQL
SELECT NULLIF(value, 0);  -- Returns NULL if value is 0

Primary use: preventing division by zero:

SQL
-- Without NULLIF: error if quantity is 0
SELECT total / quantity AS unit_price FROM order_items;
 
-- With NULLIF: returns NULL instead of error
SELECT total / NULLIF(quantity, 0) AS unit_price FROM order_items;

GREATEST and LEAST

Return the largest or smallest value from a list:

SQL
SELECT
    GREATEST(10, 20, 15),  -- 20
    LEAST(10, 20, 15);     -- 10
 
-- Practical: ensure minimum/maximum values
SELECT
    product_name,
    GREATEST(price, minimum_price) AS actual_price,
    LEAST(discount, max_discount) AS applied_discount
FROM products;

IIF (SQL Server) / IF (MySQL)

Some databases have simpler conditional functions:

SQL
-- SQL Server IIF
SELECT IIF(price > 15, 'Expensive', 'Affordable') AS label FROM books;
 
-- MySQL IF
SELECT IF(price > 15, 'Expensive', 'Affordable') AS label FROM books;
 
-- Standard equivalent
SELECT CASE WHEN price > 15 THEN 'Expensive' ELSE 'Affordable' END FROM books;

Conditional Aggregation

Combine CASE with aggregate functions for powerful analysis:

SQL
-- Count orders by status in columns
SELECT
    customer_id,
    COUNT(*) AS total_orders,
    SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed,
    SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending,
    SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled
FROM orders
GROUP BY customer_id;
customer_idtotal_orderscompletedpendingcancelled
12100
21100

Conditional Sums

SQL
-- Revenue breakdown by price tier
SELECT
    SUM(CASE WHEN price < 15 THEN quantity * unit_price ELSE 0 END) AS budget_revenue,
    SUM(CASE WHEN price BETWEEN 15 AND 17 THEN quantity * unit_price ELSE 0 END) AS standard_revenue,
    SUM(CASE WHEN price > 17 THEN quantity * unit_price ELSE 0 END) AS premium_revenue
FROM order_items oi
JOIN books b ON oi.book_id = b.book_id;

Pivot Tables with CASE

SQL
-- Monthly sales pivot
SELECT
    author_id,
    SUM(CASE WHEN MONTH(o.order_date) = 1 THEN oi.quantity ELSE 0 END) AS jan,
    SUM(CASE WHEN MONTH(o.order_date) = 2 THEN oi.quantity ELSE 0 END) AS feb,
    SUM(CASE WHEN MONTH(o.order_date) = 3 THEN oi.quantity ELSE 0 END) AS mar
FROM order_items oi
JOIN orders o ON oi.order_id = o.order_id
JOIN books b ON oi.book_id = b.book_id
GROUP BY author_id;

Practical Examples

Example 1: Customer Segmentation

SQL
SELECT
    c.customer_id,
    c.first_name,
    c.last_name,
    COALESCE(SUM(o.total_amount), 0) AS total_spent,
    COUNT(o.order_id) AS order_count,
    CASE
        WHEN COALESCE(SUM(o.total_amount), 0) >= 100 THEN 'VIP'
        WHEN COALESCE(SUM(o.total_amount), 0) >= 50 THEN 'Regular'
        WHEN COUNT(o.order_id) > 0 THEN 'New'
        ELSE 'Prospect'
    END AS customer_tier
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.first_name, c.last_name;

Example 2: Dynamic Discounting

SQL
SELECT
    title,
    price AS original_price,
    CASE
        WHEN stock_quantity > 40 THEN price * 0.80  -- 20% off
        WHEN stock_quantity > 30 THEN price * 0.90  -- 10% off
        WHEN publication_year < 1950 THEN price * 0.85  -- Classic discount
        ELSE price
    END AS sale_price,
    CASE
        WHEN stock_quantity > 40 THEN '20% OFF - Clearance'
        WHEN stock_quantity > 30 THEN '10% OFF'
        WHEN publication_year < 1950 THEN '15% OFF - Classic'
        ELSE NULL
    END AS promotion
FROM books;

Example 3: Data Quality Report

SQL
SELECT
    'customers' AS table_name,
    COUNT(*) AS total_rows,
    SUM(CASE WHEN email IS NULL THEN 1 ELSE 0 END) AS missing_email,
    SUM(CASE WHEN phone IS NULL THEN 1 ELSE 0 END) AS missing_phone,
    ROUND(100.0 * SUM(CASE WHEN email IS NULL THEN 1 ELSE 0 END) / COUNT(*), 2) AS pct_missing_email
FROM customers
 
UNION ALL
 
SELECT
    'authors' AS table_name,
    COUNT(*) AS total_rows,
    SUM(CASE WHEN birth_date IS NULL THEN 1 ELSE 0 END) AS missing_birthdate,
    SUM(CASE WHEN nationality IS NULL THEN 1 ELSE 0 END) AS missing_nationality,
    ROUND(100.0 * SUM(CASE WHEN birth_date IS NULL THEN 1 ELSE 0 END) / COUNT(*), 2) AS pct_missing
FROM authors;

Example 4: Shipping Status Logic

SQL
SELECT
    o.order_id,
    o.order_date,
    o.status,
    CASE
        WHEN o.status = 'cancelled' THEN 'N/A'
        WHEN o.status = 'completed' THEN 'Delivered'
        WHEN o.status = 'shipped' AND DATEDIFF(CURRENT_DATE, o.order_date) > 7
            THEN 'Delayed'
        WHEN o.status = 'shipped' THEN 'In Transit'
        WHEN o.status = 'pending' AND DATEDIFF(CURRENT_DATE, o.order_date) > 2
            THEN 'Processing Delayed'
        ELSE 'Processing'
    END AS shipping_status
FROM orders o;

What's Next

The next chapter covers transactions—ensuring data integrity when multiple operations must succeed or fail together.