Уникальные пользователи за неделю в SQL: почему нельзя сложить DAU
В понедельник продуктом пользовались 100 человек, во вторник 120. Недельная аудитория не обязательно равна 220: часть людей пришла в оба дня. Для недельного охвата нужно объединить множества пользователей, а дневные значения сохранить как отдельный показатель активности.
Зафиксируйте неделю и идентичность
В примере неделя начинается в понедельник 2 ноября и заканчивается перед понедельником 9 ноября. Используем явные границы вместо номера недели без года. Время событий приведено к согласованному часовому поясу бизнеса.
Пользователь определяется устойчивым user_id. Несколько событий одного человека в один день не увеличивают дневную аудиторию. Неавторизованные события с NULL не включаем в этот расчёт; для них потребуется отдельное правило идентификации и собственный показатель.
Постройте пользователя и число активных дней
Сначала ограничьте события неделей. Затем для каждого пользователя посчитайте разные календарные дни. Число полученных строк — недельный охват. Сумма active_days — число пользовательских дней, которое равно сумме DAU по дням.
Запрос ниже самодостаточен и выполняется в SQLite. date(at) извлекает дату из времени, которое уже приведено к нужной зоне. Не используйте эту функцию как замену предварительному согласованию часового пояса: событие около полуночи может попасть в другой рабочий день.
WITH events(user_id,at) AS (
VALUES ('u1','2026-11-02 09:00:00'),('u1','2026-11-02 12:00:00'),
('u1','2026-11-03 09:00:00'),('u2','2026-11-03 10:00:00'),
('u3','2026-11-04 10:00:00'),(NULL,'2026-11-04 11:00:00'),
('u4','2026-11-09 00:00:00')
), user_days AS (
SELECT user_id,COUNT(DISTINCT date(at)) AS active_days FROM events
WHERE at>='2026-11-02' AND at<'2026-11-09' AND user_id IS NOT NULL
GROUP BY user_id
)
SELECT '2026-11-02' AS week_start,COUNT(*) AS weekly_users,
COALESCE(SUM(active_days),0) AS user_days,
COALESCE(SUM(CASE WHEN active_days>=2 THEN 1 ELSE 0 END),0) AS returned_users
FROM user_days;
Прочитайте разницу метрик
У u1 есть события в понедельник и вторник, у u2 во вторник, у u3 в среду. Недельная аудитория — три человека. Пользовательских дней — четыре. Повтор события u1 в понедельник не меняет ни охват, ни число активных дней.
Показатель returned_users здесь означает людей, активных хотя бы в два разных дня этой же недели. Это не удержание относительно прошлой недели и не доля вернувшихся клиентов за всю историю. В результат включено точное определение, чтобы похожие названия не подменяли метрику.
Не складывайте охваты разных разрезов
Тот же человек может прийти из двух каналов или использовать два устройства. Сумма уникальных пользователей каналов может быть больше общей аудитории, как сумма DAU больше WAU. Для общего охвата считайте DISTINCT на нужном полном периметре, а не складывайте готовые агрегаты.
Если нужно объяснить пересечение, соберите по пользователю признаки каналов или устройств и посчитайте группы. Это отдельный результат, который показывает, почему сумма частей не равна общему множеству. Разница не обязательно означает ошибку данных.
Для приёмки нужны один человек в двух днях, два человека в одном дне, повторное событие, NULL user_id и событие ровно на правой границе. Для пустой недели внешний SELECT должен вернуть одну строку с нулевыми показателями, чтобы период не исчез из регулярного отчёта.
При поздней загрузке событий недельный охват может измениться. Сохраняйте дату среза и правило закрытия периода. Тогда сравнение двух версий отчёта можно объяснить появившимися пользователями, а повторный расчёт даст тот же результат на том же наборе данных.
От запроса к рабочей витрине
В курсе вы поработаете с временными рядами, календарём и клиентскими метриками, а запросы проверите на дублях и пустых выборках. Это помогает точно задавать периметр аудитории и отличать охват от суммы активности. Дальше эти приёмы используются в когортных расчётах и аналитической витрине.
Откройте программу платного курса «Продвинутый SQL: сложные запросы и задачи для собеседований».
Продолжить разбор: Поздние события в SQL: почему вчерашний отчёт меняется сегодня.
Продолжить на практике
- Продвинутый SQL: сложные запросы и задачи для собеседований ↗
Продолжение базового SQL: сложные JOIN, оконные рамки, временные ряды, retention, воронки и рекурсивные CTE. Разбирайте примеры и пишите запросы с автоматической проверкой — от отдельных расчётов до витрины интернет-магазина.
