Объясните принцип работы CASE WHEN в SQL. Приведите примеры использования.
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
CASE WHEN в SQL
CASE WHEN — это условный оператор в SQL, который позволяет выбирать значения на основе условий. Это одна из самых мощных и часто используемых конструкций в аналитических запросах.
Синтаксис
CASE
WHEN условие_1 THEN значение_1
WHEN условие_2 THEN значение_2
...
ELSE значение_по_умолчанию
END
Соответствия проверяются по порядку. Как только одно условие истинно, возвращается соответствующее значение и остальные не проверяются.
Простые примеры
Пример 1: Категоризация пользователей
SELECT
user_id,
revenue,
CASE
WHEN revenue >= 1000 THEN 'VIP'
WHEN revenue >= 500 THEN 'Premium'
WHEN revenue >= 100 THEN 'Regular'
ELSE 'Low-value'
END as user_segment
FROM user_revenue
ORDER BY revenue DESC;
Пример 2: Сегментирование по возрасту
SELECT
user_id,
age,
CASE
WHEN age < 18 THEN 'Teen'
WHEN age < 25 THEN 'Young Adult'
WHEN age < 35 THEN 'Adult'
WHEN age < 50 THEN 'Middle-aged'
ELSE 'Senior'
END as age_group
FROM users;
Использование CASE с агрегатными функциями
Это очень мощная техника для подсчета разных категорий в одном запросе.
Пример 3: Подсчет конверсии по типам устройств
SELECT
DATE_TRUNC('day', created_at) as day,
COUNT(*) as total_events,
SUM(CASE WHEN converted = true THEN 1 ELSE 0 END) as conversions,
COUNT(DISTINCT CASE WHEN device_type = 'mobile' THEN user_id END) as mobile_users,
COUNT(DISTINCT CASE WHEN device_type = 'desktop' THEN user_id END) as desktop_users,
ROUND(
SUM(CASE WHEN converted = true THEN 1 ELSE 0 END)::float / COUNT(*) * 100,
2
) as conversion_rate
FROM events
GROUP BY DATE_TRUNC('day', created_at)
ORDER BY day DESC;
Пример 4: Анализ воронки конверсии
SELECT
COUNT(DISTINCT CASE WHEN event_type = 'view' THEN user_id END) as viewed,
COUNT(DISTINCT CASE WHEN event_type = 'add_to_cart' THEN user_id END) as added_to_cart,
COUNT(DISTINCT CASE WHEN event_type = 'checkout' THEN user_id END) as started_checkout,
COUNT(DISTINCT CASE WHEN event_type = 'purchase' THEN user_id END) as purchased
FROM events
WHERE created_at >= DATE_TRUNC('month', NOW() - INTERVAL '1 month')
AND created_at < DATE_TRUNC('month', NOW());
CASE с математическими операциями
Пример 5: Динамический расчет с условиями
SELECT
order_id,
amount,
CASE
WHEN amount >= 1000 THEN amount * 0.9 -- 10% скидка
WHEN amount >= 500 THEN amount * 0.95 -- 5% скидка
ELSE amount -- без скидки
END as final_amount,
CASE
WHEN amount >= 1000 THEN amount * 0.1
WHEN amount >= 500 THEN amount * 0.05
ELSE 0
END as discount_amount
FROM orders;
Простая форма CASE (Simple CASE)
Так же есть более простая форма, когда сравниваем одно значение с несколькими вариантами.
-- Полная форма (Searched CASE)
SELECT
CASE
WHEN country = 'US' THEN 'North America'
WHEN country = 'CA' THEN 'North America'
WHEN country = 'RU' THEN 'Europe/Asia'
ELSE 'Other'
END as region
FROM users;
-- Простая форма (Simple CASE)
SELECT
CASE country
WHEN 'US' THEN 'North America'
WHEN 'CA' THEN 'North America'
WHEN 'RU' THEN 'Europe/Asia'
ELSE 'Other'
END as region
FROM users;
Обе формы эквивалентны в этом примере, но полная форма более универсальна.
Практические примеры для Product Analytics
Пример 6: RFM анализ (Recency, Frequency, Monetary)
WITH user_metrics AS (
SELECT
user_id,
MAX(created_at) as last_purchase,
COUNT(*) as purchase_count,
SUM(amount) as total_spent
FROM orders
WHERE created_at >= NOW() - INTERVAL '1 year'
GROUP BY user_id
)
SELECT
user_id,
CASE
WHEN (NOW() - last_purchase) <= INTERVAL '30 days' THEN 'Recency: High'
WHEN (NOW() - last_purchase) <= INTERVAL '90 days' THEN 'Recency: Medium'
ELSE 'Recency: Low'
END as recency_score,
CASE
WHEN purchase_count >= 10 THEN 'Frequency: High'
WHEN purchase_count >= 5 THEN 'Frequency: Medium'
ELSE 'Frequency: Low'
END as frequency_score,
CASE
WHEN total_spent >= 1000 THEN 'Monetary: High'
WHEN total_spent >= 500 THEN 'Monetary: Medium'
ELSE 'Monetary: Low'
END as monetary_score
FROM user_metrics
ORDER BY total_spent DESC;
Пример 7: Когортный анализ с условной классификацией
WITH cohorts AS (
SELECT
DATE_TRUNC('week', signup_date) as signup_cohort,
user_id,
(NOW() - signup_date)::int / 7 as weeks_since_signup,
CASE
WHEN last_active_date >= NOW() - INTERVAL '7 days' THEN 1
ELSE 0
END as is_active
FROM users
WHERE signup_date >= NOW() - INTERVAL '3 months'
)
SELECT
signup_cohort,
COUNT(DISTINCT user_id) as cohort_size,
SUM(CASE WHEN weeks_since_signup >= 1 AND is_active = 1 THEN 1 ELSE 0 END) as retained_week_1,
SUM(CASE WHEN weeks_since_signup >= 4 AND is_active = 1 THEN 1 ELSE 0 END) as retained_week_4,
ROUND(
SUM(CASE WHEN weeks_since_signup >= 4 AND is_active = 1 THEN 1 ELSE 0 END)::float /
COUNT(DISTINCT user_id) * 100, 2
) as retention_rate_week_4
FROM cohorts
GROUP BY signup_cohort
ORDER BY signup_cohort DESC;
Пример 8: Классификация по платформам и системам
SELECT
user_id,
CASE
WHEN user_agent ILIKE '%iPhone%' OR user_agent ILIKE '%iPad%' THEN 'iOS'
WHEN user_agent ILIKE '%Android%' THEN 'Android'
WHEN user_agent ILIKE '%Windows%' THEN 'Windows'
WHEN user_agent ILIKE '%Mac%' THEN 'macOS'
ELSE 'Other'
END as os,
CASE
WHEN user_agent ILIKE '%Chrome%' THEN 'Chrome'
WHEN user_agent ILIKE '%Safari%' THEN 'Safari'
WHEN user_agent ILIKE '%Firefox%' THEN 'Firefox'
WHEN user_agent ILIKE '%Edge%' THEN 'Edge'
ELSE 'Other'
END as browser,
COUNT(*) as sessions
FROM sessions
GROUP BY user_id, os, browser
ORDER BY sessions DESC;
Вложенные CASE
Можно вкладывать CASE друг в друга, хотя это может быть сложно для чтения.
SELECT
user_id,
revenue,
CASE
WHEN revenue >= 1000 THEN
CASE
WHEN created_at >= NOW() - INTERVAL '30 days' THEN 'High-value Active'
ELSE 'High-value Inactive'
END
WHEN revenue >= 500 THEN
CASE
WHEN created_at >= NOW() - INTERVAL '30 days' THEN 'Medium-value Active'
ELSE 'Medium-value Inactive'
END
ELSE
CASE
WHEN created_at >= NOW() - INTERVAL '30 days' THEN 'Low-value Active'
ELSE 'Low-value Inactive'
END
END as segment
FROM users;
Частые ошибки
Ошибка 1: Забыл ELSE
-- Неправильно: NULL для неопределённых случаев
SELECT
CASE
WHEN age < 18 THEN 'Minor'
WHEN age < 65 THEN 'Adult'
END as age_group
FROM users;
-- Люди 65+ получат NULL!
-- Правильно
SELECT
CASE
WHEN age < 18 THEN 'Minor'
WHEN age < 65 THEN 'Adult'
ELSE 'Senior'
END as age_group
FROM users;
Ошибка 2: CASE с NULL значениями
-- Осторожно с NULL!
SELECT
CASE
WHEN column_value = NULL THEN 'Empty' -- НИКОГДА не сработает!
ELSE 'Not empty'
END
FROM table;
-- Правильно
SELECT
CASE
WHEN column_value IS NULL THEN 'Empty'
ELSE 'Not empty'
END
FROM table;
Ошибка 3: Порядок условий
-- Неправильно: широкие условия до узких
SELECT
CASE
WHEN age >= 18 THEN 'Adult' -- Ловит всех 18+
WHEN age >= 65 THEN 'Senior' -- НИКОГДА не срабатывает!
END
FROM users;
-- Правильно
SELECT
CASE
WHEN age >= 65 THEN 'Senior' -- Узкое условие первым
WHEN age >= 18 THEN 'Adult' -- Потом более широкое
END
FROM users;
Performance Tips
- Используй CASE в SELECT, а не WHERE: WHERE может быть более сложным
- Избегай вложенного CASE: если много уровней, используй VIEW или CTE
- Порядок имеет значение: более частые условия первыми
- Используй CASE с индексами: CASE может замедлить использование индекса
Чек-лист использования CASE
- Все возможные случаи обработаны (есть ELSE)
- Нет условий с NULL без IS NULL
- Порядок условий логичен (узкие -> широкие)
- Не используется одно и то же вычисление дважды
- Понятны все метки категорий
- Нет вложенности > 2 уровней
CASE WHEN — это инструмент, который позволяет писать чистый и эффективный аналитический SQL.