Как посчитать DAU в SQL
Что такое DAU
DAU (Daily Active Users) — количество уникальных пользователей, совершивших целевое действие за день.
«Целевое действие» зависит от продукта:
- Для соцсети — открыл ленту.
- Для маркетплейса — просмотрел товар.
- Для игры — запустил сессию.
На собесе всегда уточняйте: «что считаем активностью?»
Формула DAU
DAU = COUNT(DISTINCT user_id) WHERE действие совершено в этот деньSQL:
SELECT created_at::DATE AS day,
COUNT(DISTINCT user_id) AS dau
FROM events
WHERE event_name = 'app_open'
GROUP BY 1
ORDER BY 1;Разрез по платформе
SELECT created_at::DATE AS day, platform,
COUNT(DISTINCT user_id) AS dau
FROM events
WHERE event_name = 'app_open'
GROUP BY 1, 2
ORDER BY 1, 2;DAU за последний день
SELECT COUNT(DISTINCT user_id) AS yesterday_dau
FROM events
WHERE event_name = 'app_open'
AND created_at >= CURRENT_DATE - INTERVAL '1 day'
AND created_at < CURRENT_DATE;Средний DAU за 7 дней
WITH daily AS (
SELECT created_at::DATE AS day,
COUNT(DISTINCT user_id) AS dau
FROM events
WHERE event_name = 'app_open'
GROUP BY 1
)
SELECT AVG(dau) AS avg_7d_dau
FROM daily
WHERE day >= CURRENT_DATE - INTERVAL '7 day';Больше таких примеров с разборами — в Telegram-тренажёре. Короткие сессии, прогресс по темам, объяснения после каждого ответа.
Скользящее среднее DAU
Сглаживает колебания выходных:
WITH daily AS (
SELECT created_at::DATE AS day,
COUNT(DISTINCT user_id) AS dau
FROM events
WHERE event_name = 'app_open'
GROUP BY 1
)
SELECT day, dau,
AVG(dau) OVER (
ORDER BY day
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS dau_7d_ma
FROM daily
ORDER BY day;DAU vs MAU
MAU (Monthly Active Users)
SELECT COUNT(DISTINCT user_id) AS mau
FROM events
WHERE event_name = 'app_open'
AND created_at >= CURRENT_DATE - INTERVAL '30 day';Rolling MAU по дням
SELECT day,
COUNT(DISTINCT user_id) OVER (
ORDER BY day
RANGE BETWEEN INTERVAL '29 day' PRECEDING AND CURRENT ROW
) AS mau_30d
FROM (SELECT created_at::DATE AS day, user_id FROM events) d;Stickiness = DAU / MAU
Показывает «липкость» продукта:
WITH dau AS (
SELECT created_at::DATE AS day,
COUNT(DISTINCT user_id) AS dau
FROM events GROUP BY 1
),
mau AS (
SELECT day,
COUNT(DISTINCT user_id) OVER (
ORDER BY day
RANGE BETWEEN INTERVAL '29 day' PRECEDING AND CURRENT ROW
) AS mau
FROM (SELECT DISTINCT created_at::DATE AS day, user_id FROM events) 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% — ежедневный продукт (месенджер, email).
- 20–40% — регулярный (соцсеть, e-commerce).
- <20% — редкий (страховка, госуслуги).
WAU (Weekly Active Users)
SELECT DATE_TRUNC('week', created_at) AS week,
COUNT(DISTINCT user_id) AS wau
FROM events
WHERE event_name = 'app_open'
GROUP BY 1
ORDER BY 1;New vs Returning DAU
WITH user_first AS (
SELECT user_id, MIN(created_at)::DATE AS first_seen
FROM events GROUP BY user_id
),
daily AS (
SELECT e.created_at::DATE AS day, e.user_id,
uf.first_seen
FROM events e JOIN user_first uf USING (user_id)
)
SELECT day,
COUNT(DISTINCT user_id) FILTER (WHERE day = first_seen) AS new_users,
COUNT(DISTINCT user_id) FILTER (WHERE day > first_seen) AS returning_users
FROM daily
GROUP BY day
ORDER BY day;DAU по событиям
Разные DAU для разных действий:
SELECT
created_at::DATE AS day,
COUNT(DISTINCT user_id) FILTER (WHERE event = 'view') AS view_dau,
COUNT(DISTINCT user_id) FILTER (WHERE event = 'purchase') AS buy_dau,
COUNT(DISTINCT user_id) AS total_dau
FROM events
GROUP BY 1
ORDER BY 1;Если готовишься к собесу — бот @kariernik_bot закрывает 80% технических вопросов. SQL, Python, A/B, продуктовые метрики — всё в одном месте.
DAU в pandas
import pandas as pd
events = pd.read_sql("SELECT created_at, user_id FROM events", conn)
events['date'] = pd.to_datetime(events['created_at']).dt.date
dau = events.groupby('date')['user_id'].nunique().reset_index(name='dau')Частые ошибки
1. Не указать event-фильтр
-- Все события, включая серверные, боты
COUNT(DISTINCT user_id)
-- Лучше — конкретное событие
COUNT(DISTINCT user_id) WHERE event = 'app_open'2. Timezone
-- Считаем в UTC, но пользователь в другой TZ — попадает не в свой день
GROUP BY created_at::DATE
-- Правильно
GROUP BY (created_at AT TIME ZONE 'Europe/Moscow')::DATE3. Считать события, не пользователей
COUNT(*) ≠ COUNT(DISTINCT user_id). Для DAU всегда DISTINCT.
4. Смешивать тестовых и реальных пользователей
Фильтруйте QA-аккаунты, ботов.
5. Игнорировать сессии
Если определение «активный» — по сессии, нужна логика sessions:
-- Сессия через 30-минутный таймаут
-- См. шпаргалку на оконные функцииВизуализация
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(dau['date'], dau['dau'])
ax.set_title('DAU trend')
ax.set_xlabel('Date')
ax.set_ylabel('DAU')
plt.show()Интерпретация
- DAU растёт — продукт здоровый.
- DAU плоский, MAU растёт — новые пользователи приходят, но не возвращаются. Retention issue.
- DAU падает, MAU стабильна — мало активности старых.
- DAU выше на выходных — consumer продукт.
- DAU выше в будни — B2B продукт.
Читайте также
FAQ
DAU считать по «open» или по «first event»?
Зависит от продукта. Для соцсети — просмотр ленты. Для маркетплейса — open_product_page. Уточняйте с PM.
Как фильтровать ботов?
По IP, user-agent, скорости событий. Либо через ML-модель детекции аномалий.
WAU или DAU/7?
Это разные вещи. WAU — уникальные пользователи за неделю. DAU/7 — среднее DAU по 7 дням. Разные цифры.
DAU = активные пользователи?
«Активный» — зависит от определения. Активный по engagement (посмотрел ленту) ≠ активный по действию (купил). Фиксируйте одно на проекте.