Домашка: статусы клиента от STG до DDS (и немного DM)¶
Небольшое практическое задание на 1–2 вечера: по данным о смене статусов клиента (CRM) построить цепочку слоёв STG → ODS → DDS (SCD2) и, по желанию, небольшую витрину в dm.
Цель — потренировать руками:
- работу со слоями DWH (stg / ods / dds / dm);
- проектирование и загрузку измерения с историей (SCD Type 2);
- аккуратную работу со временем (
event_ts,valid_from,valid_to).
Исходим из того, что вы уже прошли основную статью dwh-modeling/README.md и познакомились с примером интернет‑магазина.
1. Данные: события смены статуса клиента¶
Представьте, что в CRM для каждого клиента хранится история статусов:
new— только что зарегистрировался;active— делал покупки недавно;vip— часто покупает и много тратит;churned— давно ничего не делал, считаем «отвалившимся».
Эта информация приходит в DWH в виде событий (events): «у клиента X в момент времени Y статус стал Z».
В репозитории в каталоге dwh-modeling/data лежит файл:
customer_status_events.csv
Структура файла:
customer_id,status,event_ts,_load_id,_load_ts
101,new,2024-01-01 09:00:00,batch_20240101_1000,2024-01-01 10:00:00
...
Колонки:
customer_id— бизнес-ключ клиента (тот же, что и в основном примере — 101, 102, 103);status— статус клиента в CRM (new,active,vip,churned);event_ts— момент, когда статус сменился в CRM;_load_id— идентификатор батча загрузки;_load_ts— момент, когда данные попали в DWH.
Файл содержит несколько клиентов и несколько смен статуса по каждому — этого достаточно, чтобы отработать SCD2.
2. Целевая схема: какие таблицы уже есть¶
Чтобы не тратить время на DDL, структуры таблиц для домашки уже подготовлены в dwh-modeling/sql:
07_ddl_hw_customer_status.sql— создаёт дополнительные таблицы:stg.customer_status_raw— сырые события о статусе клиента;ods.customer_status— очищенные и типизированные события;dds.dim_customer_status— измерение статусов клиента в формате SCD Type 2.
08_dml_hw_customer_status_template.sql— шаблон DML-скрипта с подсказками и заготовками блоков.
Перед началом работы:
- Поднимите demo‑Postgres по инструкции из корневого
README.md. - Выполните базовые скрипты DWH:
01_ddl_stg-dds.sql02_dml_stg-dds.sql(нужен как минимум дляdds.dim_date)05_ddl_dm.sql(создаёт схемуdmдля витрин)- Выполните DDL для домашки:
07_ddl_hw_customer_status.sql
После этого схемы stg, ods, dds, dm уже существуют, а дополнительные таблицы для статусов созданы.
3. Часть 1 — STG → ODS (обязательно)¶
Задача: загрузить CSV в STG и переложить данные в ODS с приведением типов.
3.1. STG: загрузка CSV¶
Есть два варианта — выберите любой. Для первого прохождения рекомендуем вариант A (самый простой).
Вариант A (рекомендуемый): вставить данные в STG через INSERT¶
Откройте SQL‑клиент (DBeaver или psql) и вставьте данные текстом:
💡 Если вы уже загружали данные в
stg.customer_status_rawи делаете повторный запуск — начните сTRUNCATE stg.customer_status_raw;.💡 В примере ниже показан минимальный набор для клиента 101. Чтобы получить несколько клиентов и больше событий — используйте вариант B (CSV) или добавьте строки из файла
dwh-modeling/data/customer_status_events.csv.
INSERT INTO stg.customer_status_raw (customer_id, status, event_ts, _load_id, _load_ts) VALUES
('101','new','2024-01-01 09:00:00','batch_20240101_1000','2024-01-01 10:00:00'),
('101','active','2024-02-15 10:30:00','batch_20240215_1100','2024-02-15 11:00:00'),
('101','vip','2024-05-10 11:00:00','batch_20240510_1200','2024-05-10 12:00:00'),
('101','churned','2024-09-01 12:15:00','batch_20240901_1300','2024-09-01 13:00:00');
💡 Здесь
_load_ts— это время загрузки.
Вариант B: загрузить CSV¶
Можно загрузить файл dwh-modeling/data/customer_status_events.csv в таблицу stg.customer_status_raw:
- Через DBeaver: Import Data → CSV →
stg.customer_status_raw. - Через
psqlв контейнере (./psql_sh): без установкиpsqlна хост.
Способ: передайте CSV в psql через STDIN и выполните \copy ... FROM STDIN:
./postgres-bookings/psql_sh -c "TRUNCATE stg.customer_status_raw;"
cat dwh-modeling/data/customer_status_events.csv | ./postgres-bookings/psql_sh -c \
"\\copy stg.customer_status_raw(customer_id,status,event_ts,_load_id,_load_ts) FROM STDIN WITH (FORMAT csv, HEADER true)"
💡 Для части 5 (инкрементальная загрузка)
TRUNCATE stg.customer_status_rawделать не нужно — загружайте только новые строки.
После загрузки убедитесь, что данные на месте:
SELECT * FROM stg.customer_status_raw LIMIT 10;
3.2. ODS: очистка и типизация¶
💡 Обратите внимание: в основном примере
ods.customersхранит снимок (одна строка на клиента, PK =customer_id), а здесьods.customer_statusхранит все события (PK =customer_id + event_ts). Это не ошибка, а сознательный выбор: источник данных о статусах - поток событий, и ODS сохраняет эту природу. Подробнее - в комментариях к решению.
В файле 08_dml_hw_customer_status_template.sql найдите заготовку блока ODS и допишите SQL:
- привести:
customer_id→INT,status→VARCHAR(20)(можно оставить как есть),event_tsи_load_ts→TIMESTAMP;
- аккуратно обработать возможные пустые значения (если бы они были);
- заполнить
_load_idи_load_tsвods.customer_status.
💡 Для простоты сделайте загрузку идемпотентной через
TRUNCATE ods.customer_status;передINSERT(таблица в ODS имеет первичный ключ(customer_id, event_ts)).
Проверьте, что в ods.customer_status данные выглядят аккуратно:
SELECT *
FROM ods.customer_status
ORDER BY customer_id, event_ts;
4. Часть 2 — ODS → DDS (SCD Type 2, обязательно)¶
Задача: по событиям в ods.customer_status построить измерение dds.dim_customer_status, где каждая строка — период действия статуса.
Целевая таблица уже создана (см. 07_ddl_hw_customer_status.sql):
customer_bk— бизнес-ключ клиента (тот же, чтоcustomer_idв ODS);status— статус клиента;hashdiff— хэш от атрибутов (здесь достаточно самогоstatus);valid_from/valid_to— период, когда статус был актуален;created_at/updated_at— технические поля.
4.1. Начальная загрузка SCD2¶
В шаблоне 08_dml_hw_customer_status_template.sql допишите блок начальной загрузки:
-
Сформируйте промежуточный набор:
-
customer_bk, status,event_ts(как «время начала действия статуса»),-
hashdiff(например,md5(status); можно вынести расчёт в отдельную функцию по аналогии сdds.customer_hashдля клиентов). -
Для каждого клиента отсортируйте события по
event_tsи с помощьюLEAD()посчитайте (в учебном варианте считаем, что обновление DWH идёт раз в день, поэтому используемDATE): -
valid_from—event_ts::date, valid_to— следующийevent_ts::date(а у последней версииvalid_to = NULL).
💡 Упрощение для домашки: считаем, что у клиента не бывает двух разных смен статуса в один и тот же день.
Если такое бывает — удобнее строить периоды вTIMESTAMP(или вводить дополнительное правило сортировки), но это уже усложнение.💡 Если в событиях встречаются повторы одного и того же статуса подряд, можно отфильтровать “не-изменения” через
LAG(status)(илиLAG(hashdiff)) перед расчётомLEAD().
- Вставьте получившиеся строки в
dds.dim_customer_status. Актуальная строка для клиента — та, гдеvalid_to IS NULL.
💡 Для первой версии решения можно сделать full refresh: перед вставкой очистить таблицу (
TRUNCATE dds.dim_customer_status;), как в основном примере сdds.dim_customer.
INSERT INTO dds.dim_customer_status (
customer_bk, status, hashdiff,
valid_from, valid_to,
created_at, updated_at
)
SELECT
...
Проверьте результат:
SELECT *
FROM dds.dim_customer_status
ORDER BY customer_bk, valid_from;
Ожидаемое поведение:
- у клиента 101 несколько строк с разными статусами и непересекающимися периодами;
valid_to IS NULLтолько у самой свежей строки для каждого клиента.
4.2. Проверка себя¶
Примеры проверочных запросов (можно придумать свои):
- «Какой статус был у клиента 101 на дату
2024-06-01?»
→ одна строка с нужным статусом. - «Сколько клиентов были в статусе
activeна2024-04-10?»
→ несколько строк, если статус активен для диапазона дат.
5. Часть 3 — инкрементальная загрузка (по желанию)¶
Если хочется потренироваться глубже:
- Добавьте ещё несколько событий смены статуса (например, переход части клиентов из
churnedобратно вactive). - Можно дописать в исходный CSV самостоятельно.
- Либо взять готовую порцию “для инкремента” из файла
dwh-modeling/data/customer_status_events_increment.csv. - Загрузите только новые строки в
stg.customer_status_raw(не делайтеTRUNCATE):
cat dwh-modeling/data/customer_status_events_increment.csv | ./postgres-bookings/psql_sh -c \
"\\copy stg.customer_status_raw(customer_id,status,event_ts,_load_id,_load_ts) FROM STDIN WITH (FORMAT csv, HEADER true)"
-
Напишите логику инкрементального обновления
dds.dim_customer_status: -
ориентируйтесь на пример из
03_demo_increment.sqlдляdds.dim_customer; - важно:
- корректно «закрыть» старую актуальную строку (заполнить
valid_toдатой начала новой версии); - вставить новую строку с
valid_to = NULL.
- корректно «закрыть» старую актуальную строку (заполнить
Эта часть особенно полезна, если вы хотите почувствовать, как SCD2 живёт в реальном DWH.
6. Часть 4 — витрина в DM (по желанию)¶
Опциональное задание для закрепления: собрать небольшую витрину с количеством клиентов по статусам на каждую дату.
DDL витрины уже создан в 07_ddl_hw_customer_status.sql (таблица dm.mart_customer_status_daily).
Идея:
- использовать
dds.dim_dateкак календарь; - для каждой
date_actualнайти, какой статус был у клиента в этот день (черезJOINнаdds.dim_customer_statusпо диапазонуvalid_from/valid_to); - агрегировать по
status.
Пример запроса к витрине:
SELECT
date_actual,
status,
customers_cnt
FROM dm.mart_customer_status_daily
WHERE date_actual BETWEEN '2024-04-01' AND '2024-04-30'
ORDER BY date_actual, status;
7. Как вписать эту домашку в обучение¶
Рекомендуемое место в дорожке:
- Пройти основную теорию по DWH и SCD:
dwh-modeling/README.mddwh-modeling/SCD.md- Разобрать базовый пример интернет‑магазина (скрипты
01_–06_). - Выполнить эту домашку как первую попытку «самостоятельного» моделирования и ETL:
- познакомиться с ещё одним измерением с историей (
dim_customer_status); - потренироваться аккуратно работать с датами и периодами;
- при желании — собрать простую витрину в
dm.
Если что‑то не получается — можно разбирать решения по шагам вместе с ментором: от простого SELECT из STG до полноценного SCD2 в DDS.
8. Эталонное решение¶
Когда выполните домашку и захотите сверить результат — готовое решение лежит в файле 09_dml_hw_customer_status_solution.sql.
Постарайтесь не подглядывать до того, как напишете свой вариант — основная ценность задания именно в самостоятельном разборе.