Все материалы
Продуктовая аналитикагайдстарт

Учебная база данных для SQL: пример базы продукта на 45 185 строк

Учебная база данных для практики SQL: пять таблиц SaaS-продукта, связи, словарь колонок, первые запросы с результатами, A/B-тест внутри и честный список того, чего в ней нет.

КПКейсПрактика23 сентября 2026 г.16 мин

Большинство учебных баз для SQL описывают магазин: заказы импортёра деликатесов в Northwind, прокат DVD в Sakila, производителя велосипедов в AdventureWorks. На них хорошо учить JOIN и GROUP BY, но вопросы, которые задают продуктовому аналитику, там не задать: нет событий в продукте, нет подписок и нет эксперимента. Ниже описана другая база — учебная база SQL-курса КейсПрактики. Это SaaS-сервис: пять таблиц, 45 185 строк, 90 дней регистраций, платежи с продлениями и A/B-тест, в котором средний результат скрывает сегмент. На ней посчитаны примеры новых SQL-статей блога, и любой запрос отсюда можно выполнить в песочнице курса без установки и регистрации. Статья — путеводитель: что лежит в каждой таблице, как таблицы связаны, какие вопросы им можно задать и чего в базе нет.

Коротко

База описывает один продукт за один квартал. Люди регистрируются, открывают приложение, создают рабочее пространство и отчёты, приглашают коллег и платят за тариф. Всё это разложено по пяти таблицам, которые связаны через user_id.

  • users — 4 613 регистраций с 1 июня по 29 августа 2026 года: канал, страна, устройство.
  • events — 35 341 событие пяти типов. Выгрузка закрыта 30 августа в 13:00, поэтому последний день неполный.
  • payments — 1 251 платёж от 912 человек на $30 639: первые оплаты и продления.
  • subscriptions — 912 подписок, ровно одна на плательщика, со статусом active, past_due или churned.
  • experiment_exposures — 3 068 участников теста чек-листа онбординга: +10 п. п. конверсии в среднем и ноль на мобильных.
  • Запросы выполняются в песочнице курса на DuckDB — том же движке, которым курс проверяет задания. Числа из этой статьи совпадают с песочницей до единицы.

Что внутри: пять таблиц и как они связаны

Схема устроена звездой вокруг users. У каждой из четырёх других таблиц есть колонка user_id, которая указывает на пользователя. Это внешний ключ: по нему платёж, событие или подписка находят своего владельца. Собственный идентификатор строки есть у каждой таблицы — event_id, payment_id и так далее.

Перед первым запросом к любой таблице стоит назвать её зерно — что описывает одна строка. У users одна строка — человек, у payments — платёж, и один человек может занимать в ней три строки. Почти все ошибки новичка в JOIN и COUNT происходят из-за того, что зерно двух таблиц не совпало, а запрос этого не заметил.

Ключи в базе чистые: у всех 35 341 события, 1 251 платежа, 912 подписок и 3 068 участников эксперимента есть пользователь в users, «сирот» ноль. Ограничения PRIMARY KEY и FOREIGN KEY при этом не объявлены — таблицы собраны через CREATE TABLE AS, как это часто бывает в аналитических хранилищах. Как проверить ключи самому, разобрано в статье про первичный и внешний ключ.

Таблицы учебной базы: зерно, объём и связь с пользователями
ТаблицаОдна строкаСтрокКлюч строкиСвязь с users
usersзарегистрированный пользователь4 613user_id
eventsдействие в продукте35 341event_idмного событий у одного человека
paymentsуспешный платёж1 251payment_idот одного до трёх платежей у плательщика
subscriptionsподписка плательщика912subscription_idне больше одной на человека
experiment_exposuresпопадание в вариант эксперимента3 068exposure_idне больше одного на человека
Сколько строк в каждой таблице
SELECT 'users' AS table_name, count(*) AS row_count FROM users
UNION ALL SELECT 'events', count(*) FROM events
UNION ALL SELECT 'payments', count(*) FROM payments
UNION ALL SELECT 'subscriptions', count(*) FROM subscriptions
UNION ALL SELECT 'experiment_exposures', count(*) FROM experiment_exposures;

-- 4 613, 35 341, 1 251, 912, 3 068 — всего 45 185 строк

Как выглядит один пользователь во всех пяти таблицах

Схему проще понять на одном человеке. Пользователь 8 зарегистрировался 1 июня из органики, с десктопа, из России. В тот же день в 11:56 он открыл приложение, через 35 минут создал рабочее пространство, в 13:56 — первый отчёт. На следующий день пригласил коллегу, через день выгрузил отчёт. Потом возвращался восемь раз, последний — 20 июля.

Платил он трижды: 10 июня, 10 июля и 9 августа, каждый раз $29 за тариф pro. Первая оплата помечена как first, две следующие — как renewal. В subscriptions у него одна строка со статусом active, а в эксперименте он попал в контрольную группу и дошёл до целевого действия.

Итого одна строка в users превращается в 13 строк в events и 3 в payments. Если соединить эти две таблицы по user_id, получится 13 × 3 = 39 строк на одного человека, и сумма его платежей в таком результате вырастет в 13 раз. Это главный риск работы с базой, и он виден уже на одном пользователе.

Пользователь 8 в каждой таблице
ТаблицаСтрокЧто в них
users12026-06-01, organic, RU, desktop
events13app_open ×9, workspace_created, report_created, invite_sent, export_completed
payments3pro по $29: first 10 июня, renewal 10 июля и 9 августа
subscriptions1pro, active, с 10 июня
experiment_exposures1onboarding_checklist, control, converted = true
Сколько строк у одного пользователя в каждой таблице
SELECT 'users' AS table_name, count(*) AS row_count FROM users WHERE user_id = 8
UNION ALL SELECT 'events', count(*) FROM events WHERE user_id = 8
UNION ALL SELECT 'payments', count(*) FROM payments WHERE user_id = 8
UNION ALL SELECT 'subscriptions', count(*) FROM subscriptions WHERE user_id = 8
UNION ALL SELECT 'experiment_exposures', count(*) FROM experiment_exposures WHERE user_id = 8;

-- 1, 13, 3, 1, 1

users: 4 613 регистраций за 90 дней

Колонок пять: user_id (INTEGER), signup_date (DATE), channel, country и device (все VARCHAR). Регистрации идут каждый день с 1 июня по 29 августа, 90 дней подряд. Поток растёт: в первую неделю июня 196 регистраций, в неделю с 17 августа — 481.

В данных есть три особенности, на которых строятся задания. Будни сильнее выходных: в будний день в среднем 61 регистрация, в выходной — 25. Сервисом пользуются на работе, и регистрируются в нём тоже с рабочего места. 15 и 16 июля идёт кампания — 99 и 100 регистраций, это два самых больших дня квартала. Канал paid_search появляется только 1 июля: в июне из него нет ни одного человека, и сравнивать каналы «за квартал» без поправки на это нельзя.

Каналы за квартал: organic — 1 747, referral — 1 037, paid_search — 1 015, partner — 814. Страны: RU — 2 715, KZ — 912, AM — 517, BY — 414, и у 55 человек страна не заполнена. Этот NULL ведёт себя как в рабочих данных: count(country) вернёт 4 558, а не 4 613, и фильтр country <> 'RU' этих 55 не покажет. Устройства делятся почти поровну: desktop — 2 384, mobile — 2 229.

Регистрации по месяцам и каналам
Месяцorganicreferralpartnerpaid_searchВсего
июнь4782922721 042
июль5973522454821 676
август (по 29-е)6723932975331 895
Регистрации по неделям

Неделя начинается с понедельника. Пик недели 13 июля — кампания 15–16 июля. В последней неделе шесть дней: выгрузка регистраций заканчивается субботой 29 августа.

Регистрации

events: 35 341 событие и пять действий в продукте

Колонки: event_id, user_id, event_time (TIMESTAMP) и event_name. Событий пять типов, и у них разное зерно. app_open повторяется: 28 693 открытия у всех 4 613 человек. Остальные четыре случаются не больше одного раза на пользователя — число строк у них равно числу людей. Это удобно для воронки и опасно для JOIN: соединение с app_open размножает строки, соединение с workspace_created — нет.

События идут с 1 июня 08:38 до 30 августа 12:59. У каждого пользователя окно наблюдения — до двух месяцев после регистрации, но не дальше конца выгрузки. Последний день выгрузки неполный: 30 августа — 226 событий, накануне — 588. Если построить DAU по дням и не отрезать этот день, график покажет обвал, которого не было.

Время записано без часового пояса. В описании базы это UTC+3, как в Москве, и граница дня в расчётах проходит по этому времени.

События по типам
event_nameСобытийПользователейЧто означает
app_open28 6934 613открыл приложение, в том числе при возврате
workspace_created2 7502 750создал рабочее пространство — активация
report_created1 7751 775собрал первый отчёт
invite_sent1 1351 135пригласил коллегу
export_completed988988выгрузил отчёт
События и люди по типу события
SELECT event_name,
       count(*)                AS events,
       count(DISTINCT user_id) AS users
FROM events
GROUP BY event_name
ORDER BY events DESC;

-- app_open 28 693 / 4 613, workspace_created 2 750 / 2 750, …

payments и subscriptions: деньги и статусы

В payments шесть колонок: payment_id, user_id, paid_at (DATE), amount (DOUBLE, в долларах), plan и payment_type. Тарифов три: basic за $19, pro за $29 и team за $39. Первый платёж помечен first, продления — renewal, они идут каждые 30 дней. Всего 1 251 платёж на $30 639: 912 первых на $22 328 и 339 продлений на $8 311.

Из 912 плательщиков 622 заплатили один раз, 241 — дважды, 49 — трижды. Четвёртого платежа нет ни у кого: окно выгрузки закрывается раньше. Отсюда разные «средние» из одной таблицы: средний платёж $24,49, средняя выручка на плательщика $33,60, выручка на зарегистрированного пользователя $6,64.

В subscriptions одна строка на плательщика: 912 подписок у 912 разных людей. started_at совпадает с датой первого платежа, plan — тариф первого платежа, monthly_price — его месячная цена. Статусы: active — 515 подписок на $12 595 в месяц, past_due — 228 на $5 632, churned — 169 на $4 101. Какой MRR показывать, только active или вместе с past_due, — решение, которое аналитик обязан проговорить, и база даёт его принять на числах.

Платежи по тарифам
planЦенаПлатежейПлательщиковВыручка
basic$19706514$13 414
pro$29403296$11 687
team$39142102$5 538
всего1 251912$30 639

experiment_exposures: A/B-тест, который можно разобрать до конца

Таблица описывает один эксперимент — onboarding_checklist: новым пользователям показывали чек-лист первых шагов. В тест попали 3 068 человек из 4 613: 1 527 в контрольную группу и 1 541 в вариант с чек-листом. Колонка converted (BOOLEAN) — дошёл ли участник до целевого действия.

В среднем чек-лист выигрывает: 30,3% против 20,0%. Разрез по устройству из users меняет вывод. На десктопе конверсия выросла с 20,0% до 39,8%, на мобильных осталась прежней — 20,1% против 19,9%. Весь прирост дал десктоп, и решение «раскатить на всех» опиралось бы на среднее, которое не описывает ни одну из двух групп.

Этот сюжет можно пройти до конца на одной базе: проверить, что группы разделились поровну, посчитать конверсию, разложить её по сегментам, проверить значимость и сформулировать решение. Поэтому таблица и лежит в базе целиком, а не готовым итогом.

Конверсия в эксперименте по устройствам, %

Контроль: 789 человек на десктопе и 738 на мобильных. Чек-лист: 807 и 734. Посчитано на учебной базе SQL-курса.

controlchecklist
Конверсия по варианту и устройству
SELECT u.device,
       x.variant,
       count(*)                                  AS participants,
       round(100.0 * avg(x.converted::INT), 1)   AS conversion_pct
FROM experiment_exposures x
JOIN users u ON u.user_id = x.user_id
GROUP BY u.device, x.variant
ORDER BY u.device, x.variant;

-- desktop: checklist 39.8, control 20.0
-- mobile:  checklist 19.9, control 20.1

С каких запросов начать

Первый запрос к незнакомой базе — всегда SELECT * FROM таблица LIMIT 5 для каждой таблицы: посмотреть колонки, типы и то, как выглядят значения. Второй — подсчёт строк и различных ключей, как в разделе выше. Третий — первый вопрос, ради которого база нужна.

Хороший третий запрос здесь — активация по каналам: какая доля зарегистрировавшихся создала рабочее пространство. Он требует соединить две таблицы, выбрать условие соединения и не потерять людей без события. Ответ сразу даёт продуктовый вывод: referral активируется в 74,7% случаев, paid_search — в 37,0%, вдвое хуже.

Обратите внимание, где стоит условие на тип события. Если перенести его из ON в WHERE, LEFT JOIN превратится во внутреннее соединение: люди без workspace_created выпадут из знаменателя, и активация у всех каналов станет 100%.

Активация по каналам: доля создавших рабочее пространство
SELECT u.channel,
       count(*)          AS users,
       count(e.user_id)  AS activated,
       round(100.0 * count(e.user_id) / count(*), 1) AS activation_pct
FROM users u
LEFT JOIN events e
  ON e.user_id = u.user_id
 AND e.event_name = 'workspace_created'
GROUP BY u.channel
ORDER BY activation_pct DESC;

-- referral 74.7, organic 66.7, partner 53.2, paid_search 37.0

Какие вопросы можно задать этой базе

Ниже — карта: рабочий вопрос, таблицы, которые для него нужны, и где он разобран подробно. Главы курса с пометкой «полная база» работают на тех же 45 185 строках, что и песочница. Остальные главы учат синтаксису на срезе того же продукта — 18 пользователей первой недели июня, которых видно целиком и можно пересчитать руками.

Вопрос → таблицы → где разобран
ВопросТаблицыСтатьяГлава курса
сколько людей пользуются продуктом каждый деньeventsDAU6, полная база
какая доля новых дошла до ценностиusers, eventsactivation rate7, полная база
где пользователи теряются по путиeventsворонка в SQL8, полная база
возвращаются ли на следующий деньusers, eventsкогорты и retention9 и 16, полная база
что приносит paid_search от регистрации до выручкиusers, events, paymentsARPU и ARPPU13, полная база
сколько людей платят и сколько приносятusers, paymentsJOIN без дублей3–4
сработал ли экспериментexperiment_exposures, usersA/B-тест в SQL17
можно ли доверять цифрамвсепроверки качества данных14–15

Чем она отличается от Northwind, Sakila и AdventureWorks

Классические учебные базы делали для разработчиков и администраторов, и это видно по схемам: много нормализованных справочников, заказы, склад, сотрудники. На них хорошо тренировать соединение пяти таблиц подряд. Хуже — вопросы про поведение во времени: у заказа есть дата, но нет пути пользователя до него.

Учебная база курса устроена наоборот: справочников нет, таблиц мало, а время и поведение — главное. Поэтому на ней неудобно учить длинные цепочки JOIN, зато удобно учить метрики продукта, окна наблюдения и сегменты.

Учебные базы для SQL: что в них можно потренировать
БазаЧто описываетСильная сторонаЧего нет для продуктового аналитика
Northwindторговец деликатесами: заказы, поставщики, сотрудникиклассический JOIN по многим таблицамсобытий, подписок, экспериментов
Sakila (и Pagila для PostgreSQL)прокат DVD: фильмы, копии, аренды, платежисвязи многие-ко-многим, несколько путей между таблицамипути пользователя в продукте
AdventureWorksпроизводитель велосипедовбольшая корпоративная схема, продажи и производствоповеденческих данных
Chinookцифровой музыкальный магазинкомпактная схема, есть для многих СУБДсобытий и экспериментов
базы sql-exкомпьютерная фирма, корабли, авиарейсысотни упражнений с проверкойпродуктовых метрик
учебная база SQL-курсаSaaS-сервис: регистрации, события, платежи, подписки, A/Bметрики, когорты, окна, сегментысправочников и длинных цепочек JOIN

Где выполнить запросы без установки

В песочнице SQL-курса. Она открыта без регистрации, в ней уже загружены все пять таблиц, рядом — схема с колонками и типами. Запрос выполняется на сервере в DuckDB — том же движке и на той же базе, что проверяют задания курса. Поэтому числа из статей, посчитанных на учебной базе, совпадают с тем, что вы увидите.

Песочница работает только на чтение. Разрешён один запрос за запуск, и он должен начинаться с SELECT или WITH. Создать таблицу, вставить строки или удалить их нельзя: база общая, и каждый запуск начинается с одинакового состояния. Промежуточные расчёты пишите через CTE.

Диалект DuckDB близок к PostgreSQL: те же date_trunc, ::INT, FILTER, оконные функции. Отличия есть, и они мелкие. Например, DuckDB разрешает сослаться на псевдоним колонки из SELECT прямо в WHERE, а PostgreSQL на тот же запрос ответит column "doubled" does not exist. Если переносите запрос в рабочую базу, такие места стоит проверить.

Ограничения: чего в базе нет

Данные синтетические. Они сгенерированы детерминированно, без случайных чисел, поэтому база одинакова при каждой пересборке и эталонные ответы не разъезжаются. При этом у них есть форма, как у живого продукта: рост, недельная сезонность, запуск канала посреди периода, затухающий возврат и неполный последний день. Выводы о реальных SaaS-продуктах из этих чисел делать нельзя, учиться на них — можно.

  • Нет затрат на привлечение. CAC и окупаемость канала не посчитать: денег на рекламу в базе нет.
  • Нет сессий, страниц и свойств событий. Событие — это имя и время, без параметров и JSON.
  • Нет возвратов, неудачных списаний и смены тарифа. Статус подписки — готовое поле, и past_due здесь зависит от активности человека, а не от просроченного платежа.
  • Флаг converted в эксперименте хранится готовым. Из events его не восстановить: целевое действие в базе не расшифровано.
  • Время попадания в эксперимент условное — 00:05 в день регистрации, раньше первого открытия. Проверить порядок «попал в тест → совершил действие» по времени не получится.
  • Когорта 29 августа не дозрела: у неё нет полного следующего дня. Для D1 и более длинных окон её нужно отбрасывать.
  • Ограничения PRIMARY KEY и FOREIGN KEY не объявлены. Ключи чистые, но это проверяется запросом, а не гарантируется базой.

Частые вопросы

Нужно ли ставить PostgreSQL, чтобы повторить примеры? Нет. Запрос пишется в браузере, а выполняет его сервер песочницы. Ставить базу, импортировать дамп и настраивать подключение не нужно.

Почему в статьях доллары, а не рубли? Так устроен продукт в базе: тарифы basic, pro и team стоят $19, $29 и $39 в месяц. Для задач это ничего не меняет.

Подходит ли база для подготовки к собеседованию? Да, для той части, где просят посчитать метрику: DAU, активацию, воронку, retention, ARPPU, конверсию в A/B-тесте. Задачи на длинные цепочки JOIN по справочникам лучше тренировать на Northwind или Sakila.

Можно ли в песочнице создать свою таблицу? Нет, она только на чтение. Промежуточную таблицу заменяет CTE: WITH per_user AS (…) SELECT … FROM per_user.

Итог

Учебная база SQL-курса — это один продукт, на котором можно пройти путь аналитика от SELECT * до решения по эксперименту. Пять таблиц, 45 185 строк, связи через user_id и несколько встроенных ловушек: неполный последний день, канал, появившийся в середине периода, NULL в стране, повторяющиеся платежи и средний uplift, который держится на одном сегменте.

Начните с подсчёта строк и одного пользователя во всех таблицах, затем повторите активацию по каналам. Дальше — задачи с решениями на этой же базе и главы курса. Первые главы — устройство базы, SELECT и WHERE — открыты бесплатно.

Продолжить чтение
Вся библиотека