SQLRFMсегментацияпродуктовая аналитикаCRM

RFM-сегментация клиентов в SQL: пошаговый разбор 2026

2026-04-25 12 мин

RFM — самый практичный метод сегментации клиентов в e-commerce / SaaS. Делишь юзеров на 3-5 групп по каждой из трёх метрик: Recency (как давно покупал), Frequency (как часто), Monetary (сколько денег принёс). Получаешь матрицу 3×3×3 = 27 сегментов, и для каждого — свою маркетинговую стратегию.

Этот гайд — пошаговый разбор как посчитать RFM в чистом SQL. Без Python, без ML — только агрегации и оконные функции. Готовый запрос можно копировать.


Зачем RFM-сегментация

«Лояльные клиенты» и «неактивные» — слишком грубо. RFM даёт operational segments:

Каждому сегменту — свой email/push/скидка. Эффективность маркетинга растёт в 2-3 раза.


Шаг 1: подготовь данные

Тебе нужна таблица заказов с user_id, order_date, amount.

-- Пример таблицы
CREATE TABLE orders (
    order_id INT,
    user_id INT,
    order_date DATE,
    amount NUMERIC(10,2)
);

Шаг 2: посчитай R, F, M для каждого юзера

WITH user_rfm AS (
    SELECT
        user_id,
        MAX(order_date)              AS last_order_date,
        CURRENT_DATE - MAX(order_date) AS recency_days,
        COUNT(*)                     AS frequency,
        SUM(amount)                  AS monetary
    FROM orders
    WHERE order_date >= CURRENT_DATE - INTERVAL '365 days'
    GROUP BY user_id
)
SELECT * FROM user_rfm LIMIT 5;

Получишь таблицу:

user_idlast_order_daterecency_daysfrequencymonetary
12026-04-2051245000
22026-02-156938200
32026-04-22312500
Период анализа
Стандартно — 12 месяцев. Для быстро меняющихся продуктов (мобильные игры) — 3 месяца. Для luxury / B2B — 24 месяца. Юзеры покупающие реже периода в R = 5 не попадут вообще.


Шаг 3: разбей на квинтили (5 групп)

Используй NTILE(5) чтобы каждый юзер получил оценку 1-5 по каждой метрике:

WITH user_rfm AS (...),
scored AS (
    SELECT
        user_id,
        recency_days,
        frequency,
        monetary,
        -- R: меньше дней = лучше → инвертируем (5 = свежий, 1 = давно)
        6 - NTILE(5) OVER (ORDER BY recency_days ASC) AS r_score,
        NTILE(5) OVER (ORDER BY frequency ASC)        AS f_score,
        NTILE(5) OVER (ORDER BY monetary ASC)         AS m_score
    FROM user_rfm
)
SELECT * FROM scored LIMIT 5;
user_idr_scoref_scorem_score
1555
2233
3512

Шаг 4: собери RFM-код и сегмент

WITH user_rfm AS (...),
scored AS (...)
SELECT
    user_id,
    r_score, f_score, m_score,
    CONCAT(r_score::text, f_score::text, m_score::text) AS rfm_code,
    CASE
        -- Champions: топ-сегмент
        WHEN r_score >= 4 AND f_score >= 4 AND m_score >= 4 THEN 'Champions'
        WHEN r_score >= 4 AND f_score >= 3 THEN 'Loyal'
        WHEN r_score >= 4 AND f_score <= 2 THEN 'New'
        WHEN r_score <= 2 AND f_score >= 4 AND m_score >= 4 THEN 'At Risk'
        WHEN r_score <= 2 AND f_score >= 3 AND m_score >= 3 THEN 'Sleeping'
        WHEN r_score = 3 AND f_score = 3 THEN 'Need Attention'
        WHEN r_score <= 2 AND f_score <= 2 THEN 'Lost'
        ELSE 'Other'
    END AS segment
FROM scored;

Готовый полный запрос

WITH user_rfm AS (
    SELECT
        user_id,
        CURRENT_DATE - MAX(order_date) AS recency_days,
        COUNT(*) AS frequency,
        SUM(amount) AS monetary
    FROM orders
    WHERE order_date >= CURRENT_DATE - INTERVAL '365 days'
    GROUP BY user_id
),
scored AS (
    SELECT
        user_id,
        recency_days, frequency, monetary,
        6 - NTILE(5) OVER (ORDER BY recency_days ASC) AS r_score,
        NTILE(5) OVER (ORDER BY frequency ASC)        AS f_score,
        NTILE(5) OVER (ORDER BY monetary ASC)         AS m_score
    FROM user_rfm
)
SELECT
    user_id,
    recency_days,
    frequency,
    monetary,
    CONCAT(r_score::text, f_score::text, m_score::text) AS rfm_code,
    CASE
        WHEN r_score >= 4 AND f_score >= 4 AND m_score >= 4 THEN 'Champions'
        WHEN r_score >= 4 AND f_score >= 3 THEN 'Loyal'
        WHEN r_score >= 4 AND f_score <= 2 THEN 'New'
        WHEN r_score <= 2 AND f_score >= 4 AND m_score >= 4 THEN 'At Risk'
        WHEN r_score <= 2 AND f_score >= 3 AND m_score >= 3 THEN 'Sleeping'
        WHEN r_score = 3 AND f_score = 3 THEN 'Need Attention'
        WHEN r_score <= 2 AND f_score <= 2 THEN 'Lost'
        ELSE 'Other'
    END AS segment
FROM scored
ORDER BY rfm_code DESC;

ClickHouse-вариант

В CH NTILE есть, но нет INTERVAL. Замени:

WHERE order_date >= today() - 365

И r_score:

6 - ntile(5) OVER (ORDER BY recency_days ASC) AS r_score

Остальное идентично.


Стратегии работы с сегментами

Сегмент% юзеровСтратегия
Champions (~5%)топ-юзерыПрограмма лояльности, эксклюзивные офферы, ранний доступ к новинкам
Loyal (~10%)стабильныеUp-selling (более дорогие товары), cross-sell
New (~15%)недавно пришлиWelcome-серия, second-purchase incentive
At Risk (~10%)падает RСрочно реактивировать: персональная скидка, опрос «что не так»
Sleeping (~15%)давно нетРеактивационная кампания: «мы скучали», большая скидка
Need Attention (~10%)средниеEngagement-контент, образовательные емейлы
Lost (~25%)в прошломОдин последний оффер, потом — исключить из активного маркетинга (CAC > LTV)
Other (~10%)смешанныеПо ситуации

Подводные камни

Малая выборка

Если у тебя 200 юзеров — NTILE(5) даст по 40 в каждом квинтиле. Для статистически значимых сегментов нужно минимум 5000 юзеров на сегмент.

Bot-traffic / тестовые юзеры

Часто Champions — это тестовый юзер компании или бот-сборщик данных. Фильтруй до RFM:

WHERE user_id NOT IN (SELECT user_id FROM internal_users)

Сильно skewed monetary

1% юзеров приносят 50% выручки → NTILE даст странную картину. Используй log(monetary) вместо raw, или вместо квинтилей — фиксированные пороги (50K / 200K / 1M).

RFM не измеряет engagement

Если юзер каждый день заходит, но ничего не покупает — RFM покажет его как «Lost». А он — твой лучший pre-buyer. Дополняй RFM behavioural метриками.


Связанные материалы

Открой SQL-тренажёр, найди задачу с тегом «сегментация» или «retention» — попробуй применить RFM. Через 2 часа практики научишься делать это в любой схеме данных.

Реши задачи на сегментацию
325 SQL-задач, многие на RFM, когорты, retention. С автопроверкой кода. Первые 5 бесплатно.
Открыть SQL-тренажёр →