Как посчитать MAU в SQL

Что такое MAU

MAU (Monthly Active Users) — количество уникальных пользователей, совершивших целевое действие хотя бы раз за последние 30 дней.

«Целевое действие» зависит от продукта. Чаще всего — запуск приложения, просмотр контента, значимая активность.

Два способа считать MAU

1. Calendar MAU — по календарному месяцу

Простейший:

SELECT DATE_TRUNC('month', created_at) AS month,
    COUNT(DISTINCT user_id) AS mau
FROM events
WHERE event_name = 'app_open'
GROUP BY 1
ORDER BY 1;

Минус: в разных месяцах разное количество дней. MAU за февраль и март не сравнимы напрямую.

2. Rolling MAU (28-day MAU)

Каждый день — MAU за последние 28/30 дней:

SELECT day,
    COUNT(DISTINCT user_id) OVER (
        ORDER BY day
        RANGE BETWEEN INTERVAL '29 day' PRECEDING AND CURRENT ROW
    ) AS mau_30d
FROM (
    SELECT DISTINCT created_at::DATE AS day, user_id
    FROM events
    WHERE event_name = 'app_open'
) daily;

Чаще используют 28-day MAU (4 × 7) — устраняет недельные паттерны:

WHERE ... INTERVAL '27 day' PRECEDING ...

Плюс: сравнимо каждый день, сглаженный тренд.

MAU за конкретный период

-- MAU за последние 30 дней
SELECT COUNT(DISTINCT user_id) AS mau
FROM events
WHERE event_name = 'app_open'
  AND created_at >= CURRENT_DATE - INTERVAL '30 day';

MAU с фильтрами

По платформе

SELECT platform, COUNT(DISTINCT user_id) AS mau
FROM events
WHERE event_name = 'app_open'
  AND created_at >= CURRENT_DATE - INTERVAL '30 day'
GROUP BY platform;

По каналу

SELECT u.channel, COUNT(DISTINCT e.user_id) AS mau
FROM events e
JOIN users u USING (user_id)
WHERE e.created_at >= CURRENT_DATE - INTERVAL '30 day'
GROUP BY u.channel
ORDER BY mau DESC;

Больше таких примеров с разборами — в Telegram-тренажёре. Короткие сессии, прогресс по темам, объяснения после каждого ответа.

DAU / MAU = Stickiness

Показывает, как часто MAU-пользователь заходит:

WITH daily AS (
    SELECT DATE_TRUNC('day', created_at)::DATE AS day, user_id
    FROM events
),
dau AS (SELECT day, COUNT(DISTINCT user_id) AS dau FROM daily GROUP BY day),
mau AS (
    SELECT day,
        COUNT(DISTINCT user_id) OVER (
            ORDER BY day
            RANGE BETWEEN INTERVAL '27 day' PRECEDING AND CURRENT ROW
        ) AS mau
    FROM (SELECT DISTINCT day, user_id FROM daily) d
)
SELECT d.day, d.dau, m.mau,
    ROUND(d.dau * 1.0 / m.mau, 3) AS stickiness
FROM dau d JOIN mau m USING (day)
ORDER BY d.day;

Интерпретация:

  • >50% — daily продукт (мессенджер).
  • 20–40% — обычный (соцсеть).
  • <20% — редкий (страховка).

New vs Returning MAU

WITH user_first AS (
    SELECT user_id, MIN(created_at)::DATE AS first_seen
    FROM events GROUP BY user_id
),
last_30 AS (
    SELECT DISTINCT user_id
    FROM events
    WHERE created_at >= CURRENT_DATE - INTERVAL '30 day'
)
SELECT
    COUNT(*) FILTER (
        WHERE first_seen >= CURRENT_DATE - INTERVAL '30 day'
    ) AS new_mau,
    COUNT(*) FILTER (
        WHERE first_seen < CURRENT_DATE - INTERVAL '30 day'
    ) AS returning_mau
FROM last_30 JOIN user_first USING (user_id);

MAU в pandas

import pandas as pd

events['date'] = pd.to_datetime(events['created_at']).dt.date
events = events.sort_values('date')

# Rolling MAU
mau = (
    events.drop_duplicates(['date', 'user_id'])
    .set_index('date')
    ['user_id']
    .groupby(pd.Grouper(freq='D'))
    .apply(lambda x: x.nunique())
    .rolling(30).sum()
    # Это не настоящий MAU — простое суммирование
)

Правильный rolling MAU в pandas сложнее — обычно проще в SQL.

Частые ошибки

1. Month-to-date MAU vs full-month

-- ❌ MAU за текущий месяц (неполный)
WHERE DATE_TRUNC('month', created_at) = DATE_TRUNC('month', CURRENT_DATE)

-- ✅ Rolling MAU за 30 дней
WHERE created_at >= CURRENT_DATE - INTERVAL '30 day'

Calendar MAU текущего месяца неинформативен до конца месяца.

2. Разные определения «active»

На одном продукте 3 команды считают MAU по-разному:

  • Backend: «открыл приложение».
  • Маркетинг: «совершил любое действие».
  • Продукт: «досмотрел контент».

Фиксируйте одно определение.

3. Ботовый трафик

Фильтруйте ботов через user_agent / логику поведения.

4. Тестовые аккаунты

QA-аккаунты добавляют шума — исключайте.

Если готовишься к собесу — бот @kariernik_bot закрывает 80% технических вопросов. SQL, Python, A/B, продуктовые метрики — всё в одном месте.

Нормы MAU по индустриям

  • Месенджеры (WhatsApp, Telegram): DAU/MAU > 80%.
  • Соцсети (VK, Instagram): DAU/MAU 40–60%.
  • E-commerce: DAU/MAU 10–20%.
  • Страховка, госуслуги: DAU/MAU < 5%.

Сравнивайте свой показатель с индустрией.

MAU vs WAU vs DAU

  • DAU — за день.
  • WAU — за неделю (7 дней).
  • MAU — за месяц (28–30 дней).

Для B2B-продуктов WAU часто важнее MAU. Для consumer — MAU.

Читайте также

FAQ

Calendar или rolling MAU?

Rolling (28-day) — лучше для мониторинга. Calendar — для отчётности.

Почему 28 дней, а не 30?

28 = 4 × 7. Убирает разные недельные паттерны в разных месяцах. Для сравнимости — лучше.

MAU выше DAU в 5 раз — это норма?

Зависит от продукта. Для daily-продуктов DAU/MAU должно быть > 40%, то есть MAU/DAU < 2.5x.

MAU падает — что делать?

Разбить на new и returning. Смотреть, какая часть упала. Далее — декомпозиция по каналам, платформам, гео.