Как посчитать D1, D7, D30 retention в SQL
Содержание:
Что такое D1, D7, D30
D1 retention — доля юзеров, вернувшихся на 1-й день после регистрации. D7 — на 7-й, D30 — на 30-й. Базовая метрика любого продукта — без неё не понятно, нравится ли продукт пользователям.
Что считать «днём 0»: дата регистрации. Что считать «днём N»: ровно через N дней (classic) или в окне до N (rolling).
Бенчмарки для b2c-приложений (грубо):
- D1 — 30-50%
- D7 — 15-30%
- D30 — 8-15%
Игры обычно выше, b2b-приложения ниже.
Classic vs rolling retention
Classic retention (день в день): пользователь должен вернуться именно на N-й день.
Rolling retention (диапазон): пользователь должен вернуться в любой день от N до N+window.
День 0 День 1 День 7 День 30
Юзер A signup open open — → classic D1, D7; не D30
Юзер B signup — open open → classic D7, D30; не D1
Юзер C signup — — — → не активенКакой вариант использовать:
- Classic — для строгих метрик, для сравнения с бенчмарками
- Rolling — для b2b и редко используемых продуктов (юзер мог пропустить именно D7, но активен)
SQL: classic D1 retention
Допустим, есть таблица events:
WITH cohort AS (
SELECT DISTINCT user_id, signup_date::DATE AS day_0
FROM users
WHERE signup_date BETWEEN '2026-04-01' AND '2026-04-30'
),
returns AS (
SELECT
c.user_id,
EXISTS (
SELECT 1 FROM events e
WHERE e.user_id = c.user_id
AND e.created_at::DATE = c.day_0 + 1
) AS d1_retained
FROM cohort c
)
SELECT
COUNT(*) AS cohort_size,
SUM(CASE WHEN d1_retained THEN 1 ELSE 0 END) AS retained_d1,
ROUND(SUM(CASE WHEN d1_retained THEN 1.0 ELSE 0 END) / NULLIF(COUNT(*), 0), 3) AS d1_retention
FROM returns;SQL: D7 и D30
Универсальная функция через INTERVAL:
WITH cohort AS (
SELECT DISTINCT user_id, signup_date::DATE AS day_0
FROM users
WHERE signup_date BETWEEN '2026-04-01' AND '2026-04-30'
),
retention AS (
SELECT
c.user_id,
EXISTS (SELECT 1 FROM events e WHERE e.user_id = c.user_id
AND e.created_at::DATE = c.day_0 + 1) AS d1,
EXISTS (SELECT 1 FROM events e WHERE e.user_id = c.user_id
AND e.created_at::DATE = c.day_0 + 7) AS d7,
EXISTS (SELECT 1 FROM events e WHERE e.user_id = c.user_id
AND e.created_at::DATE = c.day_0 + 30) AS d30
FROM cohort c
)
SELECT
COUNT(*) AS cohort_size,
ROUND(SUM(CASE WHEN d1 THEN 1.0 ELSE 0 END) / NULLIF(COUNT(*), 0), 3) AS d1,
ROUND(SUM(CASE WHEN d7 THEN 1.0 ELSE 0 END) / NULLIF(COUNT(*), 0), 3) AS d7,
ROUND(SUM(CASE WHEN d30 THEN 1.0 ELSE 0 END) / NULLIF(COUNT(*), 0), 3) AS d30
FROM retention;SQL: rolling retention
Тот же запрос с диапазоном вместо точной даты:
-- Rolling D7 — вернулся в окне D7-D13
EXISTS (
SELECT 1 FROM events e
WHERE e.user_id = c.user_id
AND e.created_at::DATE BETWEEN c.day_0 + 7 AND c.day_0 + 13
) AS d7_rollingRolling D7 всегда выше classic D7 — потому что включает больший интервал.
Частые ошибки
Не учитывать timezone. Юзер регистрируется в 23:30 UTC, открывает приложение в 00:30 UTC следующего дня — это разные дни. Проблема: 30-минутный gap считается как D1. Решение: использовать локальное время юзера или достаточно широкий day boundary.
Считать D7 retention сегодняшних юзеров. Юзер, зарегистрированный 3 дня назад, не имеет шанса быть retained на D7 — у него window ещё не закрыто. Включайте только когорты, где наблюдательное окно полностью прошло.
Использовать DAU вместо event of return. «DAU = retained» — если ваш DAU включает push-открытия без активного действия, retention завышен. Используйте конкретное событие (session_start, content_view, action).
Путать classic и rolling. На собесе уточните, какой формат от вас ждут. Бенчмарки обычно по classic.
Считать retention на накладных когортах. Юзеры, которые регистрируются и сразу делают action — это органически активная когорта. Если вы рекламировали → пришла другая когорта. Раскладывайте retention по acquisition channel.
FAQ
D7 = 30% — это хорошо или плохо?
Зависит от бенчмарка категории. Для соцсетей плохо (норма 50%+). Для b2b SaaS — нормально. Для приложения раз в месяц — отлично.
Что важнее — D1 или D7?
D1 показывает onboarding. D7 показывает habit formation. Оба важны, но если выбирать один — D7, потому что D1 можно «починить» лёгкими twiks, а D7 показывает реальную ценность.
Это официальная информация?
Нет. Статья основана на индустриальных практиках.