RFM-анализ: что это, как посчитать в SQL и что делать с сегментами
RFM-анализ делит клиентов по давности, частоте и сумме покупок. Как посчитать баллы в SQL, назвать сегменты и что делать, если бизнес работает по подписке.
Содержание статьи
CRM-маркетолог готовит рассылку «мы скучаем» и спрашивает аналитика, кому её отправлять: всем, кто давно не покупал, или только тем, кто раньше покупал часто. Отвечает на это RFM-анализ. RFM-анализ — это способ разделить клиентов на группы по трём признакам: Recency — сколько дней прошло с последней покупки, Frequency — сколько покупок было, Monetary — сколько денег клиент принёс. Каждый признак переводят в балл, по сочетанию баллов клиенту дают сегмент — «чемпионы», «лояльные», «уходящие», «спящие» — и для каждого сегмента готовят своё действие.
Коротко
RFM работает там, где клиенты покупают повторно и сами решают, когда вернуться.
- Recency считают от фиксированной опорной даты, а не от сегодняшнего дня, иначе результат меняется при каждом запуске.
- Баллы ставят по квантилям (
ntile) или по порогам из жизни бизнеса. Квантили ломаются, когда у признака мало разных значений. - Если средний чек у всех примерно одинаковый, Monetary повторяет Frequency и третий балл ничего не добавляет.
- Для подписки и SaaS покупки заменяют активностью: давностью последнего входа и числом активных дней.
- Сегмент без действия бесполезен. Для каждого нужно знать, что вы ему предложите и какую метрику будете смотреть.
Что такое Recency, Frequency и Monetary
Идея старая, из директ-маркетинга: клиент, который купил недавно, часто покупает и тратит много, скорее ответит на следующее предложение. Три признака описывают разные стороны поведения, и вместе они различают клиентов лучше, чем любой из них по отдельности.
| Признак | Вопрос | Как считать | Лучший балл |
|---|---|---|---|
| Recency | как давно клиент покупал | опорная дата − дата последней покупки, в днях | меньше дней |
| Frequency | как часто покупает | число покупок за период | больше покупок |
| Monetary | сколько приносит | сумма покупок за период | больше денег |
Как сделать RFM-анализ: пошагово
Порядок одинаковый в SQL, Excel и CRM. Большая часть работы — не формула, а решения до неё: что считать покупкой, за какой период и от какой даты.
- Определите покупку: оплаченный заказ, без возвратов и отмен. Для подписки решите, считается ли автопродление покупкой.
- Выберите период, например 12 месяцев, и опорную дату — день после последней транзакции в выгрузке.
- Посчитайте на клиента давность, число покупок и сумму. Одна строка — один клиент.
- Посмотрите распределение каждого признака и корреляцию частоты с суммой.
- Поставьте баллы: квантили, если значений много, пороги, если мало или у бизнеса есть естественный цикл.
- Сведите комбинации баллов в 5–8 сегментов с понятными названиями и для каждого запишите действие и метрику.
Как посчитать RFM в SQL: признаки
Возьмём учебную базу SQL-курса: подписочный сервис, 912 платящих клиентов, оплаты с 6 июня по 30 августа, всего $30 639. Опорная дата — день после последней оплаты в данных, max(paid_at) + 1, то есть 31 августа. Так давность у самого свежего клиента равна одному дню, и запрос даёт одинаковый результат завтра и через месяц.
Первым делом посмотрите, как распределены признаки. 622 клиента заплатили один раз, 241 — дважды, 49 — трижды. Две трети базы — одна покупка.
| Оплат | Клиентов | Средняя сумма | Средний платёж |
|---|---|---|---|
| 1 | 622 | $24,5 | $24,5 |
| 2 | 241 | $48,7 | $24,4 |
| 3 | 49 | $74,8 | $24,9 |
with rfm as (
select user_id,
date '2026-08-31' - max(paid_at) as recency_days, -- max(paid_at) + 1
count(*) as frequency,
sum(amount) as monetary
from payments
group by user_id
)
select frequency,
count(*) as clients,
round(avg(monetary), 1) as avg_monetary,
round(avg(monetary / frequency), 1) as avg_payment
from rfm
group by frequency
order by frequency;Monetary повторяет Frequency
Платёж в учебной базе бывает только $19, $29 или $39, и средний платёж почти не зависит от того, сколько раз клиент платил: $24,4–24,9. Поэтому сумма — это примерно частота, умноженная на $24,5. Корреляция между ними 0,81.
Что это значит на практике: балл M здесь не добавляет информации, а только удваивает вес частоты. Разница в сумме между клиентами с одинаковой частотой объясняется тарифом, а тариф — отдельный признак, его проще взять напрямую. В рознице, где чек у разных клиентов отличается в десятки раз, M работает; в подписке с тремя ценами — нет. Проверьте это до того, как строить куб 5 × 5 × 5.
corr(frequency, monetary) и средний чек по частотеЕсли корреляция высокая, а средний чек при разной частоте одинаковый, сегментируйте по R и F, а M показывайте как выручку сегмента.
Баллы: квантили через ntile или фиксированные пороги
Учебниковый рецепт — разбить каждый признак на пять равных по численности групп функцией ntile(5). Для давности с десятками разных значений это работает: границы получаются 1–8, 8–16, 16–25, 25–41 и 41–86 дней. Правда, значения на границах — 8, 16, 25 и 41 день — попали каждое в две группы сразу: ntile режет строки, а не значения.
С частотой хуже. Разных значений три, а групп пять, поэтому клиенты с одной оплатой занимают ранги с первого по четвёртый. Какой ранг получит конкретный человек, решает порядок сортировки. Из 125 возможных комбинаций R × F × M заполнено 55, и часть различий между ними создана сортировкой, а не поведением клиентов.
| Ранг F | Оплат | Клиентов |
|---|---|---|
| 1 | 1 | 183 |
| 2 | 1 | 183 |
| 3 | 1 | 182 |
| 4 | 1 | 74 |
| 4 | 2 | 108 |
| 5 | 2 | 133 |
| 5 | 3 | 49 |
with rfm as (
select user_id,
date '2026-08-31' - max(paid_at) as recency_days,
count(*) as frequency,
sum(amount) as monetary
from payments
group by user_id
),
scored as (
select *,
ntile(5) over (order by recency_days desc, user_id) as r,
ntile(5) over (order by frequency, user_id) as f,
ntile(5) over (order by monetary, user_id) as m
from rfm
)
select f, frequency, count(*) as clients
from scored
group by f, frequency
order by f, frequency;Когда у признака мало разных значений или когда у бизнеса есть естественный цикл. В подписке с ежемесячной оплатой давность до 30 дней значит «оплаченный месяц ещё идёт», 31–60 — пропущено одно продление, больше 60 — два. Такие пороги понятны маркетингу и не плывут при каждом пересчёте.
RFM-сегменты: пример на SQL
Ставим R по циклу оплаты: 3 — до 30 дней, 2 — 31–60, 1 — больше 60. F — число оплат, их три значения. M в сегментацию не входит, но выручку сегмента показываем. Названия сегментов — договорённость внутри команды; важно, чтобы каждый читался как действие.
| Сегмент | Правило | Клиентов | Выручка | Давность, дней |
|---|---|---|---|---|
| новички | R3, одна оплата | 410 (45,0%) | $10 090 (32,9%) | 15 |
| лояльные | R3, две оплаты | 183 (20,1%) | $8954 (29,2%) | 15 |
| чемпионы | R3, три оплаты | 49 (5,4%) | $3663 (12,0%) | 9 |
| под угрозой | R2, одна оплата | 144 (15,8%) | $3566 (11,6%) | 44 |
| уходящие | R1–2, две оплаты и больше | 58 (6,4%) | $2784 (9,1%) | 40 |
| спящие | R1, одна оплата | 68 (7,5%) | $1582 (5,2%) | 70 |
with rfm as (
select user_id,
date '2026-08-31' - max(paid_at) as recency_days,
count(*) as frequency,
sum(amount) as monetary
from payments
group by user_id
),
scored as (
select *,
case when recency_days <= 30 then 3
when recency_days <= 60 then 2
else 1 end as r,
least(frequency, 3) as f
from rfm
),
segmented as (
select *,
case
when r = 3 and f = 3 then 'чемпионы'
when r = 3 and f = 2 then 'лояльные'
when r = 3 and f = 1 then 'новички'
when r < 3 and f >= 2 then 'уходящие'
when r = 2 then 'под угрозой'
else 'спящие'
end as segment
from scored
)
select segment,
count(*) as clients,
round(100.0 * count(*) / sum(count(*)) over (), 1) as clients_pct,
sum(monetary) as revenue,
round(100.0 * sum(monetary) / sum(sum(monetary)) over (), 1) as revenue_pct,
round(avg(recency_days)) as avg_recency
from segmented
group by segment
order by revenue desc;Что делать с каждым RFM-сегментом
Самый большой сегмент — новички, 410 человек. У них одна оплата не потому, что они хуже остальных, а потому что с первой оплаты не прошло месяца и продлевать ещё рано. Сравнивать их с «под угрозой» можно только через месяц, по когортам первой оплаты.
| Сегмент | Что делать | Что смотреть |
|---|---|---|
| чемпионы | не мешать: ранний доступ к функциям, просьба об отзыве или рекомендации, без скидок | удержание, число приглашений |
| лояльные | предложить годовой тариф или переход на тариф выше | доля перешедших, выручка на клиента |
| новички | довести до ключевого действия до первого продления | доля продливших через 30 дней |
| под угрозой | напомнить о ценности до окончательного ухода, выяснить причину | доля вернувшихся за 30 дней |
| уходящие | личный контакт: эти люди уже продлевали, значит, продукт был им нужен | доля вернувшихся, причины ухода |
| спящие | дешёвая автоматическая рассылка или не трогать вовсе | стоимость контакта против возврата |
Часть «уходящих» вернётся и без письма. Оставьте случайные 10–20% сегмента без рассылки и сравнивайте возврат с ними, а не с прошлым месяцем.
RFM для подписки и SaaS: активность вместо покупок
В подписке оплата происходит автоматически, и давность платежа говорит о состоянии карты больше, чем о намерениях клиента. Ранний сигнал ухода — активность в продукте. Поэтому в SaaS R и F берут из событий: R — дни с последнего входа, F — число активных дней за последние 28 дней. M остаётся тарифом.
В учебной базе повторяется только app_open, поэтому активностью считаем его. Опорная дата — тоже 31 августа, окно — с 3 по 30 августа. Отдельно отмечаем тех, у кого последняя оплата не старше 30 дней, то есть оплаченный месяц идёт.
with activity as (
select user_id,
date '2026-08-31' - max(event_time::date) as recency_days,
count(distinct event_time::date)
filter (where event_time >= date '2026-08-03') as active_days_28
from events
group by user_id
),
paying_now as ( -- последняя оплата не старше 30 дней
select user_id
from payments
group by user_id
having date '2026-08-31' - max(paid_at) <= 30
),
scored as (
select a.*,
p.user_id is not null as paying_now,
case when recency_days <= 7 then 3
when recency_days <= 30 then 2
else 1 end as r,
case when active_days_28 >= 4 then 3
when active_days_28 >= 2 then 2
else 1 end as f
from activity a
left join paying_now p using (user_id)
)
select r, f,
count(*) as users,
count(*) filter (where paying_now) as paying_now
from scored
group by r, f
order by r desc, f desc;- Из 4613 пользователей 1115 заходили в последнюю неделю и не меньше четырёх дней за 28 — ядро продукта.
- Платят сейчас 642 человека. Из них 185, то есть 28,8%, не открывали продукт больше 14 дней, а 49 — больше 30. Это главный список для команды удержания: деньги пока идут, но следующее продление под вопросом.
- Комбинация R1 с F2 или F3 невозможна по построению: кто не заходил 30 дней, не имеет активных дней в последних 28.
Частые ошибки в RFM-анализе
Почти все ошибки делают сегменты случайными или устаревшими.
- Одинаковые значения и
ntile. Квантили режут строки, поэтому одинаковые клиенты получают разные баллы. Проверяйте распределение каждого признака и при малом числе значений ставьте пороги. - Большинство с одной покупкой. Если 68% клиентов купили один раз, F почти для всех одинаковый. Сегментация фактически идёт по R, и это нужно сказать заказчику.
- Возраст клиента вместо поведения. Новичок не успел купить второй раз. Считайте F за одинаковое окно от первой покупки или сравнивайте только клиентов одного возраста.
- Опорная дата «сегодня». При каждом запуске давность сдвигается, и сегменты меняются без изменения поведения. Фиксируйте дату и пишите её в отчёте.
- Сегменты посчитали один раз. Люди переходят между сегментами каждый месяц. Пересчитывайте регулярно и смотрите переходы: сколько лояльных стали уходящими.
- M без проверки. Если сумма почти равна частоте, третий балл удваивает вес частоты и ничего не объясняет.
Что читать дальше
RFM смотрит на клиента изнутри его истории. Если нужно понять, кто приносит основную долю денег, используйте ABC-анализ, а удержание по времени смотрите когортами. Попробуйте в песочнице поменять порог давности с 30 на 35 дней и посмотрите, сколько клиентов перейдёт из «под угрозой» в «новички».
Материалы по теме

Сегментация пользователей: что это, как делить и где она обманывает
Сегментация — деление пользователей на группы по признаку, чтобы увидеть различия, которые прячет среднее. Виды сегментов, отличие сегмента от когорты, RFM-анализ в SQL, маленькие сегменты, смена состава и сегменты в A/B-тестах — на учебной базе.

ABC и XYZ-анализ: что это, как сделать и пример расчёта
ABC-анализ делит клиентов или товары на группы по вкладу в выручку, XYZ — по стабильности продаж. Как сделать оба, матрица из 9 ячеек и пример на SQL.

Churn rate и retention rate: что это и как посчитать отток
Churn rate — доля клиентов, которые ушли за период, из тех, кто был в его начале. Как посчитать отток клиентов и retention rate в подписке и в бесплатном продукте, чем customer churn отличается от revenue churn, как перевести месячный отток в годовой и где ошибаются со знаменателем.