Как посчитать time to value в SQL
Содержание:
Что такое TTV
Time to Value (TTV) — время от регистрации до первого получения ценности от продукта. Чем короче TTV, тем сильнее retention. Это одна из главных метрик активации.
В Slack — время до первого отправленного сообщения в команду. В Notion — до первой созданной страницы. В банковском приложении — до первого перевода.
Не путать с активацией: активация — доля юзеров, дошедших до value. TTV — время, за которое они доходят.
Выбор события «value»
Это самая важная часть. Хорошее value-событие:
- Связано с core ценностью продукта, не вторичным действием
- Сильно коррелирует с D7-retention (как и активация)
- Происходит у большинства активных юзеров (иначе нет данных для медианы)
Например, для маркетплейса доставки:
- Плохой выбор: «открыл каталог» (происходит у всех, не отражает ценности)
- Хороший: «получил первый заказ»
- Отличный: «получил первый заказ и оценил его на 4-5 звёзд»
SQL: общий TTV
Допустим, у вас есть users(user_id, signup_at) и events(user_id, event_type, created_at):
WITH first_value AS (
SELECT
u.user_id,
u.signup_at,
MIN(e.created_at) AS first_value_at
FROM users u
LEFT JOIN events e
ON e.user_id = u.user_id
AND e.event_type = 'first_order_completed'
AND e.created_at >= u.signup_at
WHERE u.signup_at >= '2026-01-01'
GROUP BY u.user_id, u.signup_at
)
SELECT
COUNT(*) AS total_users,
COUNT(first_value_at) AS reached_value,
ROUND(COUNT(first_value_at)::NUMERIC / COUNT(*), 3) AS share_reached,
EXTRACT(EPOCH FROM AVG(first_value_at - signup_at)) / 3600 AS avg_ttv_hours,
EXTRACT(EPOCH FROM PERCENTILE_CONT(0.5)
WITHIN GROUP (ORDER BY first_value_at - signup_at)) / 3600 AS median_ttv_hours
FROM first_value
WHERE first_value_at IS NOT NULL;Медиана почти всегда полезнее среднего — TTV имеет длинный хвост (некоторые юзеры активируются через месяц, тянут среднее).
SQL: TTV по когортам
Чтобы видеть, как TTV меняется со временем (после релизов, изменений onboarding):
WITH first_value AS (
SELECT
u.user_id,
DATE_TRUNC('week', u.signup_at)::DATE AS cohort_week,
u.signup_at,
MIN(e.created_at) AS first_value_at
FROM users u
LEFT JOIN events e
ON e.user_id = u.user_id
AND e.event_type = 'first_order_completed'
AND e.created_at >= u.signup_at
GROUP BY u.user_id, cohort_week, u.signup_at
)
SELECT
cohort_week,
COUNT(*) AS users,
ROUND(EXTRACT(EPOCH FROM PERCENTILE_CONT(0.5)
WITHIN GROUP (ORDER BY first_value_at - signup_at)) / 3600, 1) AS median_ttv_hours,
ROUND(EXTRACT(EPOCH FROM PERCENTILE_CONT(0.9)
WITHIN GROUP (ORDER BY first_value_at - signup_at)) / 3600, 1) AS p90_ttv_hours
FROM first_value
WHERE first_value_at IS NOT NULL
GROUP BY cohort_week
ORDER BY cohort_week;Связь с retention
Чем короче TTV, тем выше retention. Это можно проверить SQL'ем:
WITH ttv AS (
SELECT
u.user_id,
EXTRACT(EPOCH FROM (MIN(e.created_at) - u.signup_at)) / 3600 AS ttv_hours
FROM users u
JOIN events e
ON e.user_id = u.user_id
AND e.event_type = 'first_order_completed'
GROUP BY u.user_id, u.signup_at
),
ttv_buckets AS (
SELECT
user_id,
CASE
WHEN ttv_hours <= 1 THEN '<1 hour'
WHEN ttv_hours <= 24 THEN '1-24 hours'
WHEN ttv_hours <= 168 THEN '1-7 days'
ELSE '>7 days'
END AS ttv_bucket
FROM ttv
),
d30_retention AS (
SELECT
user_id,
EXISTS (
SELECT 1 FROM events
WHERE user_id = user_id
AND created_at >= NOW() - INTERVAL '30 days'
) AS retained_d30
FROM users
)
SELECT
tb.ttv_bucket,
COUNT(*) AS users,
ROUND(AVG(CASE WHEN dr.retained_d30 THEN 1.0 ELSE 0 END), 3) AS d30_retention
FROM ttv_buckets tb
JOIN d30_retention dr USING (user_id)
GROUP BY tb.ttv_bucket
ORDER BY tb.ttv_bucket;Ожидаем: чем меньше TTV, тем выше D30-retention. Если связь обратная — value-событие выбрано неправильно.
Частые ошибки
Среднее вместо медианы. TTV имеет длинный хвост. Один юзер с TTV 30 дней тянет среднее. Используйте медиану.
Игнорировать тех, кто не достиг value. Если 70% не активировались — медиана TTV считается только по 30%. Это смещение. Считайте отдельно median TTV и share who reached value.
Использовать timezone сервера. Юзер из UTC+3 регистрируется в 23:00 UTC, value совершает в 02:00 UTC — TTV считается «3 часа», но фактически было 23:30 локального → 5:30 локального = 6 часов. Используйте локальное время.
Не учитывать новые когорты. Юзер, зарегистрированный 2 дня назад, не имел шанса достичь value за 7 дней. Считайте только когорты, где окно прошло.
Брать «слабое» value-событие. «Открыл приложение второй раз» — слабое value. Не предсказывает retention. Берите событие, которое реально отражает core ценность.
FAQ
Какой TTV считается хорошим?
Зависит от продукта. Соцсеть — минуты. Маркетплейс доставки — часы. SaaS B2B — дни. Главное — снижать TTV для своей категории.
Как использовать TTV в работе?
A/B-тесты onboarding меняют TTV. Если новый онбординг сократил median TTV с 24 до 6 часов — это сильный сигнал, что и retention вырастет.
Это официальная информация?
Нет. Статья основана на индустриальных практиках.