Туториалы Данные

SQL для продакт-менеджеров: 7 когортных запросов, которые AI пишет лучше аналитика

Что такое когортный анализ в SQL?

Когортный анализ в SQL — набор шаблонных запросов, группирующих пользователей по общей дате события (обычно месяц регистрации или первого платежа) и отслеживающих поведенческие метрики — retention, выручку, активацию, отток — в последующие периоды. Запросы строят pivot-таблицы, где строки — когорты, столбцы — временные периоды, позволяя напрямую сравнивать качество пользователей из разных периодов привлечения. AI-модели генерируют такие запросы за секунды при наличии точной схемы данных и явного описания бизнес-логики, исключая стандартный 30–60-минутный цикл работы аналитика.

TL;DR

  • -7 запросов в статье покрывают 80% аналитических потребностей продакт-менеджера: retention, LTV, activation, churn risk, feature adoption, conversion time и NRR
  • -AI генерирует рабочий когортный SQL за 15 секунд при наличии схемы и бизнес-логики — против 30 минут у аналитика, пишущего с нуля
  • -Стабилизация retention на 3–4 месяце сигнализирует о формировании привычки; линейное падение до нуля означает, что продукт не удерживает
  • -Referral-пользователи в данных примера активируются в 1.7 раза чаще и конвертируются за 3 дня (медиана) против 12 дней у платного трафика — это разрыв в onboarding, а не проблема канала
  • -NRR выше 100% на 3-м месяце (когорта 2025-10: 105.3%) означает, что expansion revenue перекрывает отток — порог, разделяющий здоровую и проблемную unit economics SaaS

Большинство аналитических запросов в product-командах повторяются с минимальными вариациями: когортный анализ, retention, revenue breakdown. Одни и те же паттерны, разные фильтры.

AI-модели генерируют SQL быстрее человека не потому что умнее, а потому что когортные запросы имеют жёсткую структуру, которая хорошо ложится на pattern matching. Аналитик тратит 30 минут на уточнение требований и написание запроса. Claude или GPT-5.4 выдают рабочий SQL за 15 секунд при правильном промпте.

Ниже 7 SQL-запросов для когортного анализа, промпт к каждому и пример вывода. Все запросы написаны для PostgreSQL, но адаптируются под BigQuery, Snowflake и ClickHouse заменой 2-3 функций.

Схема данных для примеров

Все запросы работают с тремя таблицами. Если в продукте другая схема, AI адаптирует запрос за один промпт.

-- Пользователи
CREATE TABLE users (
  user_id       BIGINT PRIMARY KEY,
  created_at    TIMESTAMP NOT NULL,
  plan          VARCHAR(20),     -- free, pro, enterprise
  source        VARCHAR(50)      -- organic, paid, referral
);

-- События
CREATE TABLE events (
  event_id      BIGINT PRIMARY KEY,
  user_id       BIGINT REFERENCES users(user_id),
  event_name    VARCHAR(100),
  event_time    TIMESTAMP NOT NULL,
  properties    JSONB
);

-- Платежи
CREATE TABLE payments (
  payment_id    BIGINT PRIMARY KEY,
  user_id       BIGINT REFERENCES users(user_id),
  amount        DECIMAL(10,2),
  currency      VARCHAR(3),
  paid_at       TIMESTAMP NOT NULL
);

1. Классический retention по когортам регистрации

Retention показывает, сколько пользователей возвращаются через N дней/недель/месяцев после регистрации. Базовый запрос для любого продукта.

Промпт для AI:

Напиши SQL-запрос для PostgreSQL. Таблицы: users (user_id, created_at), events (user_id, event_time). Рассчитай месячный retention по когортам регистрации за последние 6 месяцев. Когорта = месяц created_at. Retention = процент пользователей когорты, совершивших хотя бы одно событие в month N после регистрации. Выведи pivot-таблицу: строки = когорты, столбцы = month_0 … month_5.

SQL:

WITH cohorts AS (
  SELECT
    user_id,
    DATE_TRUNC('month', created_at) AS cohort_month
  FROM users
  WHERE created_at >= CURRENT_DATE - INTERVAL '6 months'
),
activity AS (
  SELECT DISTINCT
    e.user_id,
    c.cohort_month,
    DATE_TRUNC('month', e.event_time) AS activity_month
  FROM events e
  JOIN cohorts c ON e.user_id = c.user_id
),
retention_raw AS (
  SELECT
    cohort_month,
    EXTRACT(YEAR FROM AGE(activity_month, cohort_month)) * 12
      + EXTRACT(MONTH FROM AGE(activity_month, cohort_month)) AS month_number,
    COUNT(DISTINCT user_id) AS active_users
  FROM activity
  GROUP BY cohort_month, month_number
),
cohort_sizes AS (
  SELECT cohort_month, COUNT(DISTINCT user_id) AS cohort_size
  FROM cohorts
  GROUP BY cohort_month
)
SELECT
  TO_CHAR(r.cohort_month, 'YYYY-MM') AS cohort,
  s.cohort_size,
  ROUND(100.0 * MAX(CASE WHEN month_number = 0 THEN active_users END) / s.cohort_size, 1) AS month_0,
  ROUND(100.0 * MAX(CASE WHEN month_number = 1 THEN active_users END) / s.cohort_size, 1) AS month_1,
  ROUND(100.0 * MAX(CASE WHEN month_number = 2 THEN active_users END) / s.cohort_size, 1) AS month_2,
  ROUND(100.0 * MAX(CASE WHEN month_number = 3 THEN active_users END) / s.cohort_size, 1) AS month_3,
  ROUND(100.0 * MAX(CASE WHEN month_number = 4 THEN active_users END) / s.cohort_size, 1) AS month_4,
  ROUND(100.0 * MAX(CASE WHEN month_number = 5 THEN active_users END) / s.cohort_size, 1) AS month_5
FROM retention_raw r
JOIN cohort_sizes s ON r.cohort_month = s.cohort_month
GROUP BY r.cohort_month, s.cohort_size
ORDER BY r.cohort_month;

Пример вывода:

cohortcohort_sizemonth_0month_1month_2month_3month_4month_5
2025-101240100.042.331.124.821.218.9
2025-111385100.045.133.726.422.8
2025-121102100.038.928.523.1
2026-011467100.047.235.9
2026-021290100.044.6
2026-031158100.0

На что смотреть. Стабилизация retention на month_3-month_4 указывает на формирование привычки. Если retention падает линейно без плато, продукт не создаёт привычку. Разница между когортами (45.1% vs 38.9% на month_1) сигнализирует об изменениях в onboarding или качестве трафика.

2. Revenue cohorts: LTV по когортам

Revenue cohorts показывают, сколько денег приносит каждая когорта со временем. Критический запрос для unit economics.

Промпт для AI:

SQL для PostgreSQL. Таблицы: users (user_id, created_at), payments (user_id, amount, paid_at). Рассчитай кумулятивный revenue на пользователя по когортам регистрации (помесячно). Когорта = месяц created_at. Для каждого месяца после регистрации покажи cumulative revenue / cohort_size. Последние 6 месяцев.

SQL:

WITH cohorts AS (
  SELECT
    user_id,
    DATE_TRUNC('month', created_at) AS cohort_month
  FROM users
  WHERE created_at >= CURRENT_DATE - INTERVAL '6 months'
),
monthly_revenue AS (
  SELECT
    c.cohort_month,
    EXTRACT(YEAR FROM AGE(DATE_TRUNC('month', p.paid_at), c.cohort_month)) * 12
      + EXTRACT(MONTH FROM AGE(DATE_TRUNC('month', p.paid_at), c.cohort_month)) AS month_number,
    SUM(p.amount) AS revenue
  FROM payments p
  JOIN cohorts c ON p.user_id = c.user_id
  GROUP BY c.cohort_month, month_number
),
cumulative AS (
  SELECT
    cohort_month,
    month_number,
    SUM(revenue) OVER (
      PARTITION BY cohort_month ORDER BY month_number
    ) AS cum_revenue
  FROM monthly_revenue
),
cohort_sizes AS (
  SELECT cohort_month, COUNT(DISTINCT user_id) AS cohort_size
  FROM cohorts
  GROUP BY cohort_month
)
SELECT
  TO_CHAR(c.cohort_month, 'YYYY-MM') AS cohort,
  cs.cohort_size,
  ROUND(MAX(CASE WHEN month_number = 0 THEN cum_revenue END) / cs.cohort_size, 2) AS ltv_m0,
  ROUND(MAX(CASE WHEN month_number = 1 THEN cum_revenue END) / cs.cohort_size, 2) AS ltv_m1,
  ROUND(MAX(CASE WHEN month_number = 2 THEN cum_revenue END) / cs.cohort_size, 2) AS ltv_m2,
  ROUND(MAX(CASE WHEN month_number = 3 THEN cum_revenue END) / cs.cohort_size, 2) AS ltv_m3
FROM cumulative c
JOIN cohort_sizes cs ON c.cohort_month = cs.cohort_month
GROUP BY c.cohort_month, cs.cohort_size
ORDER BY c.cohort_month;

Пример вывода:

cohortcohort_sizeltv_m0ltv_m1ltv_m2ltv_m3
2025-1012402.155.879.4212.31
2025-1113852.436.9110.7814.05
2025-1211021.894.627.95
2026-0114673.017.84

На что смотреть. Кумулятивный LTV должен расти от когорты к когорте. Если ltv_m1 когорты 2026-01 ($7.84) выше, чем у 2025-10 ($5.87), монетизация улучшается. Сравнение ltv_m0 показывает, насколько быстро новые пользователи совершают первую покупку.

3. Activation rate по когортам и источникам

Activation rate измеряет долю пользователей, выполнивших ключевое действие (aha-moment). Разбивка по источникам трафика показывает, откуда приходят самые качественные пользователи.

Промпт для AI:

SQL для PostgreSQL. Таблицы: users (user_id, created_at, source), events (user_id, event_name, event_time). Activation event = ‘project_created’. Рассчитай activation rate по когортам (месяц регистрации) и источникам трафика. Activation = пользователь совершил event ‘project_created’ в первые 7 дней после регистрации. Покажи cohort, source, cohort_size, activated_count, activation_rate.

SQL:

WITH cohort_source AS (
  SELECT
    user_id,
    DATE_TRUNC('month', created_at) AS cohort_month,
    created_at,
    source
  FROM users
  WHERE created_at >= CURRENT_DATE - INTERVAL '6 months'
),
activated AS (
  SELECT DISTINCT cs.user_id, cs.cohort_month, cs.source
  FROM cohort_source cs
  JOIN events e ON e.user_id = cs.user_id
  WHERE e.event_name = 'project_created'
    AND e.event_time <= cs.created_at + INTERVAL '7 days'
)
SELECT
  TO_CHAR(cs.cohort_month, 'YYYY-MM') AS cohort,
  cs.source,
  COUNT(DISTINCT cs.user_id) AS cohort_size,
  COUNT(DISTINCT a.user_id) AS activated,
  ROUND(100.0 * COUNT(DISTINCT a.user_id) / COUNT(DISTINCT cs.user_id), 1) AS activation_rate
FROM cohort_source cs
LEFT JOIN activated a
  ON cs.user_id = a.user_id
  AND cs.cohort_month = a.cohort_month
GROUP BY cs.cohort_month, cs.source
HAVING COUNT(DISTINCT cs.user_id) >= 30
ORDER BY cs.cohort_month, activation_rate DESC;

Пример вывода:

cohortsourcecohort_sizeactivatedactivation_rate
2026-01referral31219863.5
2026-01organic84542350.1
2026-01paid31011236.1
2026-02referral28719066.2
2026-02organic76139952.4
2026-02paid2429438.8

На что смотреть. Referral-пользователи активируются в 1.7 раза чаще paid-пользователей. Это не повод отключать платный трафик, а повод проверить, получают ли paid-пользователи тот же onboarding, и инвестировать в реферальную программу.

4. Churn prediction: пользователи в зоне риска

Запрос находит пользователей, которые были активны, но перестали совершать действия. Ранний сигнал оттока до того, как пользователь уйдёт окончательно.

Промпт для AI:

SQL для PostgreSQL. Таблицы: users (user_id, created_at, plan), events (user_id, event_time). Найди пользователей в зоне риска оттока: были активны (3+ событий за предыдущие 30 дней), но за последние 14 дней не совершили ни одного события. Выведи user_id, plan, days_since_last_event, events_in_prior_period, created_at. Отсортируй по events_in_prior_period DESC (самые активные в прошлом = самые ценные потери).

SQL:

WITH recent_activity AS (
  SELECT
    user_id,
    MAX(event_time) AS last_event,
    COUNT(*) FILTER (
      WHERE event_time >= CURRENT_DATE - INTERVAL '44 days'
        AND event_time < CURRENT_DATE - INTERVAL '14 days'
    ) AS events_prior_30d,
    COUNT(*) FILTER (
      WHERE event_time >= CURRENT_DATE - INTERVAL '14 days'
    ) AS events_last_14d
  FROM events
  WHERE event_time >= CURRENT_DATE - INTERVAL '90 days'
  GROUP BY user_id
)
SELECT
  u.user_id,
  u.plan,
  CURRENT_DATE - ra.last_event::date AS days_since_last_event,
  ra.events_prior_30d,
  TO_CHAR(u.created_at, 'YYYY-MM-DD') AS registered
FROM recent_activity ra
JOIN users u ON u.user_id = ra.user_id
WHERE ra.events_prior_30d >= 3
  AND ra.events_last_14d = 0
ORDER BY ra.events_prior_30d DESC
LIMIT 100;

Пример вывода:

user_idplandays_since_last_eventevents_prior_30dregistered
48201enterprise16892025-04-12
33107pro18672025-08-03
51422pro15542025-11-21
29834free21412025-06-15

На что смотреть. Enterprise-пользователь с 89 событиями за предыдущий месяц и 16 днями молчания — тревожный сигнал. Передавайте этот список в CRM или в triggered email-кампанию. Порог в 14 дней и 3 события можно адаптировать: попросите AI подставить параметры, соответствующие вашему продукту.

5. Feature adoption по когортам

Запрос показывает, растёт ли adoption новых функций и какие когорты их игнорируют.

Промпт для AI:

SQL для PostgreSQL. Таблицы: users (user_id, created_at), events (user_id, event_name, event_time). Рассчитай feature adoption rate по когортам регистрации. Features = список event_name: ‘export_report’, ‘invite_member’, ‘api_key_created’, ‘dashboard_created’. Для каждой когорты (месяц) и каждой фичи покажи процент пользователей когорты, использовавших фичу хотя бы раз в первые 30 дней.

SQL:

WITH cohorts AS (
  SELECT
    user_id,
    created_at,
    DATE_TRUNC('month', created_at) AS cohort_month
  FROM users
  WHERE created_at >= CURRENT_DATE - INTERVAL '4 months'
),
feature_usage AS (
  SELECT DISTINCT
    c.user_id,
    c.cohort_month,
    e.event_name
  FROM cohorts c
  JOIN events e ON e.user_id = c.user_id
  WHERE e.event_name IN ('export_report', 'invite_member', 'api_key_created', 'dashboard_created')
    AND e.event_time <= c.created_at + INTERVAL '30 days'
),
cohort_sizes AS (
  SELECT cohort_month, COUNT(DISTINCT user_id) AS cohort_size
  FROM cohorts
  GROUP BY cohort_month
)
SELECT
  TO_CHAR(cs.cohort_month, 'YYYY-MM') AS cohort,
  cs.cohort_size,
  ROUND(100.0 * COUNT(DISTINCT CASE WHEN f.event_name = 'dashboard_created' THEN f.user_id END) / cs.cohort_size, 1) AS dashboard_created,
  ROUND(100.0 * COUNT(DISTINCT CASE WHEN f.event_name = 'invite_member' THEN f.user_id END) / cs.cohort_size, 1) AS invite_member,
  ROUND(100.0 * COUNT(DISTINCT CASE WHEN f.event_name = 'export_report' THEN f.user_id END) / cs.cohort_size, 1) AS export_report,
  ROUND(100.0 * COUNT(DISTINCT CASE WHEN f.event_name = 'api_key_created' THEN f.user_id END) / cs.cohort_size, 1) AS api_key_created
FROM cohort_sizes cs
LEFT JOIN feature_usage f ON f.cohort_month = cs.cohort_month
GROUP BY cs.cohort_month, cs.cohort_size
ORDER BY cs.cohort_month;

Пример вывода:

cohortcohort_sizedashboard_createdinvite_memberexport_reportapi_key_created
2025-12110234.218.712.45.1
2026-01146741.822.314.17.9
2026-02129045.125.915.89.2
2026-03115847.328.116.510.4

На что смотреть. Рост adoption от когорты к когорте (dashboard_created: 34.2% → 47.3%) означает, что улучшения onboarding работают. Низкий adoption api_key_created (5-10%) нормален для technical feature. Если invite_member стагнирует, виральный цикл продукта не ускоряется.

6. Conversion time: время от регистрации до оплаты

Сколько дней проходит от регистрации до первой покупки. Запрос критичен для планирования payback period и настройки trial-to-paid воронки.

Промпт для AI:

SQL для PostgreSQL. Таблицы: users (user_id, created_at, source), payments (user_id, paid_at). Рассчитай время от регистрации до первого платежа. Для каждого источника трафика покажи: количество конвертировавшихся пользователей, медианное время конверсии в днях, среднее время, 90-й перцентиль. Только пользователи, зарегистрированные за последние 6 месяцев.

SQL:

WITH first_payment AS (
  SELECT
    user_id,
    MIN(paid_at) AS first_paid_at
  FROM payments
  GROUP BY user_id
),
conversion_data AS (
  SELECT
    u.source,
    EXTRACT(DAY FROM fp.first_paid_at - u.created_at) AS days_to_convert
  FROM users u
  JOIN first_payment fp ON fp.user_id = u.user_id
  WHERE u.created_at >= CURRENT_DATE - INTERVAL '6 months'
)
SELECT
  source,
  COUNT(*) AS converted_users,
  ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY days_to_convert)) AS median_days,
  ROUND(AVG(days_to_convert), 1) AS avg_days,
  ROUND(PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY days_to_convert)) AS p90_days
FROM conversion_data
GROUP BY source
ORDER BY median_days;

Пример вывода:

sourceconverted_usersmedian_daysavg_daysp90_days
referral31235.214
organic587711.828
paid1981218.442

На что смотреть. Referral-пользователи конвертируются за 3 дня (медиана) против 12 у paid. Реферальный трафик приходит с более сформированным intent. P90 в 42 дня для paid-трафика говорит о том, что trial длиной 14 дней может быть слишком коротким для этого сегмента.

7. Net revenue retention по когортам

NRR показывает, как меняется выручка от существующих когорт: upsell, downgrade, churn. NRR выше 100% означает, что рост выручки от существующих клиентов компенсирует отток.

Промпт для AI:

SQL для PostgreSQL. Таблицы: users (user_id, created_at), payments (user_id, amount, paid_at). Рассчитай net revenue retention по когортам. Когорта = месяц первого платежа. Для каждого последующего месяца покажи: revenue когорты в этом месяце / revenue когорты в month_0 * 100. Последние 6 месяцев.

SQL:

WITH first_payment_month AS (
  SELECT
    user_id,
    DATE_TRUNC('month', MIN(paid_at)) AS cohort_month
  FROM payments
  GROUP BY user_id
),
monthly_revenue AS (
  SELECT
    fpm.cohort_month,
    DATE_TRUNC('month', p.paid_at) AS revenue_month,
    SUM(p.amount) AS revenue
  FROM payments p
  JOIN first_payment_month fpm ON p.user_id = fpm.user_id
  WHERE fpm.cohort_month >= CURRENT_DATE - INTERVAL '6 months'
  GROUP BY fpm.cohort_month, DATE_TRUNC('month', p.paid_at)
),
base_revenue AS (
  SELECT cohort_month, revenue AS base
  FROM monthly_revenue
  WHERE revenue_month = cohort_month
)
SELECT
  TO_CHAR(mr.cohort_month, 'YYYY-MM') AS cohort,
  ROUND(br.base, 0) AS base_revenue,
  ROUND(100.0 * MAX(CASE
    WHEN EXTRACT(YEAR FROM AGE(revenue_month, mr.cohort_month)) * 12
       + EXTRACT(MONTH FROM AGE(revenue_month, mr.cohort_month)) = 0
    THEN revenue END) / br.base, 1) AS nrr_m0,
  ROUND(100.0 * MAX(CASE
    WHEN EXTRACT(YEAR FROM AGE(revenue_month, mr.cohort_month)) * 12
       + EXTRACT(MONTH FROM AGE(revenue_month, mr.cohort_month)) = 1
    THEN revenue END) / br.base, 1) AS nrr_m1,
  ROUND(100.0 * MAX(CASE
    WHEN EXTRACT(YEAR FROM AGE(revenue_month, mr.cohort_month)) * 12
       + EXTRACT(MONTH FROM AGE(revenue_month, mr.cohort_month)) = 2
    THEN revenue END) / br.base, 1) AS nrr_m2,
  ROUND(100.0 * MAX(CASE
    WHEN EXTRACT(YEAR FROM AGE(revenue_month, mr.cohort_month)) * 12
       + EXTRACT(MONTH FROM AGE(revenue_month, mr.cohort_month)) = 3
    THEN revenue END) / br.base, 1) AS nrr_m3
FROM monthly_revenue mr
JOIN base_revenue br ON mr.cohort_month = br.cohort_month
GROUP BY mr.cohort_month, br.base
ORDER BY mr.cohort_month;

Пример вывода:

cohortbase_revenuenrr_m0nrr_m1nrr_m2nrr_m3
2025-1018500100.094.291.8105.3
2025-1122100100.097.1103.6
2025-1216800100.088.4
2026-0127300100.0101.2

На что смотреть. Когорта 2025-10 показывает NRR 105.3% на month_3 — upsell компенсировал отток. Когорта 2025-12 теряет 11.6% revenue к month_1 — возможная проблема с качеством пользователей или holiday churn. NRR выше 100% на ранних месяцах (когорта 2026-01: 101.2%) указывает на успешную стратегию expansion revenue.

Как писать промпты для SQL-генерации

1. Указывай схему. AI не знает структуру базы данных. Перечисли таблицы, столбцы и типы. Чем точнее схема, тем точнее запрос.

2. Описывай бизнес-логику явно. “Активный пользователь” может означать “залогинился” или “совершил ключевое действие”. AI не угадает. Определяй термины: “activation = выполнил event ‘project_created’ в первые 7 дней”.

3. Указывай СУБД. PostgreSQL, BigQuery и ClickHouse используют разный синтаксис для дат, window functions и percentile-функций. Одно слово в промпте экономит цикл исправлений.

4. Проси pivot-формат. Когортные данные в длинном формате (cohort, month_number, value) неудобны для анализа. Pivot-таблица (строки = когорты, столбцы = месяцы) читается значительно быстрее.

5. Добавляй ограничения. “Последние 6 месяцев”, “минимум 30 пользователей в когорте”, “только платящие”. Без ограничений AI вернёт запрос, который сканирует всю таблицу или включает нерелевантные данные.

Связь с observability

Когортный анализ показывает, что происходит с пользователями. LLM observability показывает, что происходит с AI-системой. Если продукт использует LLM-функции, когортные метрики без трейсинга LLM-вызовов дают неполную картину. Рост churn в когорте может коррелировать с деградацией качества модели, ростом latency или увеличением cost per query. Связка cohort SQL + Langfuse трейсинг закрывает обе стороны.

Итого

Семь запросов покрывают 80% аналитических потребностей product-менеджера: retention, LTV, activation, churn risk, feature adoption, conversion time, NRR. Каждый запрос генерируется через AI за 15-30 секунд с правильным промптом. Ключевое условие — точная схема данных и явное описание бизнес-логики.

Аналитик всё ещё нужен для интерпретации результатов, построения data pipelines и работы с edge cases. Но написание стандартных когортных запросов больше не требует ожидания в очереди тикетов.


Нужна помощь с настройкой продуктовой аналитики и когортного анализа? Я помогаю стартапам внедрять AI-решения и строить продукты — belov.works.

FAQ

Как когортные запросы нужно адаптировать при переходе с PostgreSQL на BigQuery?

Логика остаётся идентичной — меняется лишь несколько синтаксических деталей. Замените DATE_TRUNC('month', ts) на DATE_TRUNC(ts, MONTH), EXTRACT(YEAR FROM AGE(...)) на DATE_DIFF(d1, d2, YEAR), PERCENTILE_CONT на APPROX_QUANTILES. Попросите AI напрямую: вставьте PostgreSQL-запрос и укажите «адаптируй для BigQuery» — модель выполнит синтаксический перевод за один шаг и отметит различия в агрегатных функциях.

Какой минимальный размер когорты статистически значим для retention-анализа?

30 пользователей на когорту — практический минимум для принятия направленных решений. При меньшем количестве один выброс может сдвинуть retention на 3–5 процентных пункта. Запрос из раздела 3 уже содержит HAVING COUNT(DISTINCT cs.user_id) >= 30 — распространите это условие на retention-запрос при низких объёмах. Для A/B-тестового уровня уверенности при сравнении когорт нужно 100+ пользователей, чтобы зафиксировать 5-процентный пункт разницы с мощностью 80%.

Почему NRR может превышать 100% в первые месяцы, если явного upsell не было?

Expansion revenue может приходить от usage-based компонентов биллинга, апгрейдов плана при достижении лимитов, активации дополнений или добавления мест. Если в продукте есть любой переменный элемент тарификации, NRR > 100% в ранние месяцы ожидаем и является здоровым сигналом. Если планы исключительно фиксированные, а NRR всё равно > 100% — проверьте данные платежей на дубликаты или возвраты, учитываемые как выручка.