Complete SQL CASE Expression Technical Guide
1. Fundamental Syntax
-- Simple CASE (equality comparison)
SELECT product_name,
CASE category_id
WHEN 1 THEN 'Electronics'
WHEN 2 THEN 'Clothing'
WHEN 3 THEN 'Home Goods'
ELSE 'Other'
END AS category_name
FROM products;
-- Searched CASE (conditional logic)
SELECT order_id, total_amount,
CASE
WHEN total_amount > 1000 THEN 'Premium'
WHEN total_amount > 500 THEN 'Standard'
WHEN total_amount > 0 THEN 'Basic'
ELSE 'Invalid'
END AS order_tier
FROM orders;
-
Two Forms: Simple (value comparison) and
Searched (conditional expressions)
-
Evaluation Order: Conditions are checked
sequentially, first match wins
-
ELSE Clause: Optional but recommended,
defaults to NULL if omitted
-
Type Consistency: All result expressions must
return compatible data types
2. Practical Applications
Data Categorization
-- Customer segmentation
SELECT customer_id,
CASE
WHEN purchase_count > 50 THEN 'Gold'
WHEN purchase_count > 20 THEN 'Silver'
WHEN purchase_count > 0 THEN 'Bronze'
ELSE 'Prospect'
END AS customer_tier,
CASE
WHEN last_purchase_date > CURRENT_DATE - INTERVAL '30 days' THEN 'Active'
WHEN last_purchase_date > CURRENT_DATE - INTERVAL '90 days' THEN 'Lapsing'
ELSE 'Inactive'
END AS engagement_status
FROM customers;
NULL Handling
-- Special NULL handling
SELECT employee_id,
CASE
WHEN termination_date IS NULL THEN 'Active'
WHEN termination_date > CURRENT_DATE THEN 'Notice Period'
ELSE 'Terminated'
END AS employment_status,
CASE
WHEN COALESCE(bonus_eligibility, FALSE) THEN 'Eligible'
ELSE 'Not Eligible'
END AS bonus_status
FROM employees;
3. Advanced Patterns
Nested CASE Expressions
-- Complex business logic
SELECT product_id,
CASE
WHEN discontinued THEN 'Discontinued'
WHEN stock_quantity = 0 THEN
CASE
WHEN restock_date IS NULL THEN 'Out of Stock - No Restock'
WHEN restock_date > CURRENT_DATE + 30 THEN 'Out of Stock - Delayed'
ELSE 'Out of Stock - Restocking Soon'
END
WHEN stock_quantity < 10 THEN 'Low Stock'
ELSE 'In Stock'
END AS inventory_status
FROM products;
CASE in Aggregate Functions
-- Conditional aggregation
SELECT department_id,
COUNT(*) AS total_employees,
SUM(CASE WHEN gender = 'M' THEN 1 ELSE 0 END) AS male_count,
SUM(CASE WHEN gender = 'F' THEN 1 ELSE 0 END) AS female_count,
AVG(CASE WHEN salary > 0 THEN salary ELSE NULL END) AS avg_salary
FROM employees
GROUP BY department_id;
4. Performance Optimization
Index Usage with CASE
-- Index-friendly CASE expressions
SELECT * FROM orders
WHERE
CASE
WHEN status = 'Shipped' THEN shipped_date
WHEN status = 'Delivered' THEN delivered_date
ELSE order_date
END > '2023-01-01';
-- Better approach (rewrite for index usage)
SELECT * FROM orders
WHERE (status = 'Shipped' AND shipped_date > '2023-01-01')
OR (status = 'Delivered' AND delivered_date > '2023-01-01')
OR (status NOT IN ('Shipped','Delivered') AND order_date > '2023-01-01');
Materialized CASE Results
-- Store computed values for frequent queries
CREATE TABLE customer_segments AS
SELECT customer_id,
CASE
WHEN lifetime_value > 5000 THEN 'High Value'
WHEN lifetime_value > 1000 THEN 'Medium Value'
ELSE 'Low Value'
END AS value_segment
FROM customers;
CREATE INDEX idx_segment ON customer_segments(value_segment);
5. Platform-Specific Extensions
-
PostgreSQL:
CASE
with
FILTER
clause for aggregates
-
Oracle:
DECODE
function as
CASE
alternative
-
SQL Server:
IIF
and
CHOOSE
shorthand functions
-
MySQL:
IF()
function for simple
conditions
Vendor-Specific Examples
-- PostgreSQL FILTER with CASE
SELECT
COUNT(*) FILTER (WHERE status = 'Active') AS active_count,
COUNT(*) FILTER (WHERE status = 'Inactive') AS inactive_count
FROM users;
-- SQL Server IIF
SELECT product_name,
IIF(discontinued = 1, 'Discontinued', 'Available') AS availability
FROM products;
-- Oracle DECODE
SELECT employee_name,
DECODE(department_id, 10, 'Finance', 20, 'HR', 'Other') AS dept_name
FROM employees;
Alternatives to CASE
-- COALESCE/NULLIF for NULL handling
SELECT COALESCE(discount, 0) AS effective_discount FROM orders;
-- UNION ALL for complex conditional logic
SELECT 'High' AS priority, * FROM tasks WHERE urgency > 8
UNION ALL SELECT 'Medium', * FROM tasks WHERE urgency BETWEEN 5 AND 8
UNION ALL SELECT 'Low', * FROM tasks WHERE urgency < 5;
Dynamic SQL with CASE
-- Building conditional queries
SET @sql = CONCAT('SELECT * FROM products WHERE ',
CASE
WHEN @category IS NOT NULL THEN CONCAT('category_id = ', @category)
ELSE '1=1'
END);
PREPARE stmt FROM @sql;
EXECUTE stmt;