Как посчитать 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. Смотреть, какая часть упала. Далее — декомпозиция по каналам, платформам, гео.