Домашка по моделированию
This commit is contained in:
@@ -57,6 +57,7 @@
|
||||
- [Базы данных. 1,2,3 нормальные формы. - Youtube](https://www.youtube.com/watch?v=zwQzL80U51c)
|
||||
- [Введение в структуру хранилища данных](dwh-modeling/README.md)
|
||||
- Теория про Slowly Changing Dimensions: [SCD](dwh-modeling/SCD.md)
|
||||
- Практика по моделированию статусов клиента: [домашка STG → ODS → DDS → DM](dwh-modeling/Homework_Customer_Status_DDS_DM.md)
|
||||
- Еще про Data Vault:
|
||||
- [Введение в Data Vault - Хабр](https://habr.com/ru/articles/348188/)
|
||||
- [Основы Data Vault - Хабр](https://habr.com/ru/articles/502968/)
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
# Домашка: статусы клиента от 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`
|
||||
|
||||
Структура файла:
|
||||
|
||||
```text
|
||||
customer_id,status,event_ts,_load_id,load_ts
|
||||
101,new,2024-01-01 09:00:00,batch_20250405_0800,2025-04-05 08: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-скрипта с подсказками и заготовками блоков.
|
||||
|
||||
Перед началом работы:
|
||||
|
||||
1. Поднимите demo‑Postgres по инструкции из корневого `README.md`.
|
||||
2. Выполните базовые скрипты DWH:
|
||||
- `01_ddl_stg-dds.sql`
|
||||
- `02_dml_stg-dds.sql`
|
||||
3. Выполните DDL для домашки:
|
||||
- `07_ddl_hw_customer_status.sql`
|
||||
|
||||
После этого схемы `stg`, `ods`, `dds` уже существуют, а дополнительные таблицы для статусов созданы.
|
||||
|
||||
---
|
||||
|
||||
## 3. Часть 1 — STG → ODS (обязательно)
|
||||
|
||||
**Задача:** загрузить CSV в STG и переложить данные в ODS с приведением типов.
|
||||
|
||||
### 3.1. STG: загрузка CSV
|
||||
|
||||
1. Откройте `psql` в контейнере (см. инструкцию в `postgres-bookings/README.md`).
|
||||
2. Загрузите файл `customer_status_events.csv` в таблицу `stg.customer_status_raw`:
|
||||
- можно использовать `\copy` из `psql`;
|
||||
- либо любой другой способ, к которому вы привыкли.
|
||||
3. Убедитесь, что данные загрузились:
|
||||
|
||||
```sql
|
||||
SELECT * FROM stg.customer_status_raw LIMIT 10;
|
||||
```
|
||||
|
||||
### 3.2. 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`.
|
||||
|
||||
Проверьте, что в `ods.customer_status` данные выглядят аккуратно:
|
||||
|
||||
```sql
|
||||
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` — период, когда статус был актуален;
|
||||
- `is_current` — флаг актуальной строки;
|
||||
- `created_at` / `updated_at` — технические поля.
|
||||
|
||||
### 4.1. Начальная загрузка SCD2
|
||||
|
||||
В шаблоне `08_dml_hw_customer_status_template.sql` допишите блок начальной загрузки:
|
||||
|
||||
1. Сформируйте промежуточный набор:
|
||||
|
||||
- `customer_bk`,
|
||||
- `status`,
|
||||
- `event_ts` (как «время начала действия статуса»),
|
||||
- `hashdiff` (например, `md5(status)` или `dds.customer_hash(status)` по аналогии с `dim_customer`).
|
||||
|
||||
2. Для каждого клиента отсортируйте события по `event_ts` и с помощью `LEAD()` посчитайте:
|
||||
|
||||
- `valid_from` — текущее `event_ts`,
|
||||
- `valid_to` — следующее `event_ts - 1 second` (или `9999-12-31`, если следующего нет).
|
||||
|
||||
3. Вставьте получившиеся строки в `dds.dim_customer_status`:
|
||||
|
||||
```sql
|
||||
INSERT INTO dds.dim_customer_status (
|
||||
customer_bk, status, hashdiff,
|
||||
valid_from, valid_to,
|
||||
is_current, created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
...
|
||||
```
|
||||
|
||||
Проверьте результат:
|
||||
|
||||
```sql
|
||||
SELECT *
|
||||
FROM dds.dim_customer_status
|
||||
ORDER BY customer_bk, valid_from;
|
||||
```
|
||||
|
||||
Ожидаемое поведение:
|
||||
|
||||
- у клиента 101 несколько строк с разными статусами и непересекающимися периодами;
|
||||
- `is_current = TRUE` только у самой свежей строки для каждого клиента.
|
||||
|
||||
### 4.2. Проверка себя
|
||||
|
||||
Примеры проверочных запросов (можно придумать свои):
|
||||
|
||||
- «Какой статус был у клиента 101 на дату `2024-06-01`?»
|
||||
→ одна строка с нужным статусом.
|
||||
- «Сколько клиентов были в статусе `active` на `2024-04-10`?»
|
||||
→ несколько строк, если статус *активен* для диапазона дат.
|
||||
|
||||
---
|
||||
|
||||
## 5. Часть 3 — инкрементальная загрузка (по желанию)
|
||||
|
||||
Если хочется потренироваться глубже:
|
||||
|
||||
1. Добавьте в CSV ещё несколько событий смены статуса (например, переход части клиентов из `churned` обратно в `active`).
|
||||
2. Загрузите новые строки только в `stg.customer_status_raw`.
|
||||
3. Напишите логику инкрементального обновления `dds.dim_customer_status`:
|
||||
|
||||
- ориентируйтесь на пример из `03_demo_increment.sql` для `dds.dim_customer`;
|
||||
- важно:
|
||||
- корректно «закрыть» старую актуальную строку (`valid_to`, `is_current = FALSE`);
|
||||
- вставить новую строку с `is_current = TRUE`.
|
||||
|
||||
Эта часть особенно полезна, если вы хотите почувствовать, как SCD2 живёт в реальном DWH.
|
||||
|
||||
---
|
||||
|
||||
## 6. Часть 4 — витрина в DM (по желанию)
|
||||
|
||||
Опциональное задание для закрепления: собрать небольшую витрину с количеством клиентов по статусам на каждую дату.
|
||||
|
||||
Пример целевой таблицы:
|
||||
|
||||
```sql
|
||||
CREATE TABLE dm.mart_customer_status_daily (
|
||||
date_actual DATE NOT NULL,
|
||||
status VARCHAR(20) NOT NULL,
|
||||
customers_cnt INT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
Идея:
|
||||
|
||||
- использовать `dds.dim_date` как календарь;
|
||||
- для каждой `date_actual` найти, какой статус был у клиента в этот день
|
||||
(через `JOIN` на `dds.dim_customer_status` по диапазону `valid_from/valid_to`);
|
||||
- агрегировать по `status`.
|
||||
|
||||
Пример запроса к витрине:
|
||||
|
||||
```sql
|
||||
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. Как вписать эту домашку в обучение
|
||||
|
||||
Рекомендуемое место в дорожке:
|
||||
|
||||
1. Пройти основную теорию по DWH и SCD:
|
||||
- `dwh-modeling/README.md`
|
||||
- `dwh-modeling/SCD.md`
|
||||
2. Разобрать базовый пример интернет‑магазина (скрипты `01_`–`06_`).
|
||||
3. Выполнить **эту домашку** как первую попытку «самостоятельного» моделирования и ETL:
|
||||
- познакомиться с ещё одним измерением с историей (`dim_customer_status`);
|
||||
- потренироваться аккуратно работать с датами и периодами;
|
||||
- при желании — собрать простую витрину в `dm`.
|
||||
|
||||
Если что‑то не получается — можно разбирать решения по шагам вместе с ментором: от простого `SELECT` из STG до полноценного SCD2 в DDS.
|
||||
|
||||
@@ -723,6 +723,12 @@ SELECT 'OK' WHERE EXISTS (
|
||||
|
||||
## Приложения
|
||||
|
||||
### Дополнительные материалы и практика
|
||||
|
||||
- [Домашка: статусы клиента от STG до DDS (и немного DM)](Homework_Customer_Status_DDS_DM.md)
|
||||
- [SCD: как хранить историю изменений](SCD.md)
|
||||
- [DataVault: как пережить бурную жизнь источников](DataVault.md)
|
||||
|
||||
### 📚 Мини-глоссарий (RU / EN)
|
||||
|
||||
| Термин | Пояснение |
|
||||
@@ -839,4 +845,3 @@ CREATE TABLE dds.fact_sales (
|
||||
```
|
||||
|
||||
> 💡 `date_key` — это `20240110`, а не `DATE`, чтобы не делать JOIN по диапазону в `fact → dim_date`.
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
customer_id,status,event_ts,_load_id,load_ts
|
||||
101,new,2024-01-01 09:00:00,batch_20250405_0800,2025-04-05 08:00:00
|
||||
101,active,2024-02-15 10:30:00,batch_20250405_0800,2025-04-05 08:00:00
|
||||
101,vip,2024-05-10 11:00:00,batch_20250405_1200,2025-04-05 12:00:00
|
||||
101,churned,2024-09-01 12:15:00,batch_20250405_1800,2025-04-05 18:00:00
|
||||
102,new,2024-03-05 14:00:00,batch_20250405_0800,2025-04-05 08:00:00
|
||||
102,active,2024-04-01 09:45:00,batch_20250405_1200,2025-04-05 12:00:00
|
||||
102,churned,2024-04-20 16:20:00,batch_20250405_1800,2025-04-05 18:00:00
|
||||
103,new,2024-03-10 10:10:00,batch_20250405_0800,2025-04-05 08:00:00
|
||||
|
||||
|
@@ -0,0 +1,43 @@
|
||||
-- ===============================================
|
||||
-- DDL: дополнительные таблицы для домашки
|
||||
-- Тема: статусы клиента (SCD2 поверх статуса)
|
||||
-- Скрипт можно запускать после 01_ddl_stg-dds.sql
|
||||
-- ===============================================
|
||||
|
||||
-- 1. STG: сырые события о статусе клиента из CRM
|
||||
DROP TABLE IF EXISTS stg.customer_status_raw;
|
||||
CREATE TABLE stg.customer_status_raw (
|
||||
customer_id TEXT,
|
||||
status TEXT,
|
||||
event_ts TEXT,
|
||||
_load_id TEXT,
|
||||
_load_ts TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 2. ODS: очищенные и типизированные статусы
|
||||
DROP TABLE IF EXISTS ods.customer_status;
|
||||
CREATE TABLE ods.customer_status (
|
||||
customer_id INT NOT NULL,
|
||||
status VARCHAR(20) NOT NULL,
|
||||
event_ts TIMESTAMP NOT NULL,
|
||||
_load_id TEXT NOT NULL,
|
||||
_load_ts TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE ods.customer_status
|
||||
ADD PRIMARY KEY (customer_id, event_ts);
|
||||
|
||||
-- 3. DDS: измерение статусов клиента с историей (SCD Type 2)
|
||||
DROP TABLE IF EXISTS dds.dim_customer_status;
|
||||
CREATE TABLE dds.dim_customer_status (
|
||||
customer_status_sk BIGSERIAL PRIMARY KEY,
|
||||
customer_bk INT NOT NULL,
|
||||
status VARCHAR(20) NOT NULL,
|
||||
hashdiff TEXT NOT NULL,
|
||||
valid_from TIMESTAMP NOT NULL,
|
||||
valid_to TIMESTAMP NOT NULL,
|
||||
is_current BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
-- ===============================================
|
||||
-- DML-шаблон для домашки
|
||||
-- Тема: статусы клиента (STG → ODS → DDS SCD2)
|
||||
-- Цель: по customer_status_events.csv построить историю статусов
|
||||
-- ===============================================
|
||||
|
||||
-- Подсказка:
|
||||
-- 1) Загрузите CSV в stg.customer_status_raw (через COPY или \copy в psql).
|
||||
-- См. пример структуры файла в dwh-modeling/data/customer_status_events.csv
|
||||
-- 2) Переложите данные в ods.customer_status с приведением типов.
|
||||
-- customer_id → INT, status → VARCHAR(20), event_ts / load_ts → TIMESTAMP.
|
||||
-- 3) Постройте из ods.customer_status измерение dds.dim_customer_status в стиле SCD2:
|
||||
-- - одна строка на период действия статуса (valid_from / valid_to);
|
||||
-- - is_current = TRUE только у актуальной строки для клиента;
|
||||
-- - hashdiff можно считать, например, от одного поля status.
|
||||
-- 4) При желании добавьте инкрементальную логику (как в 03_demo_increment.sql).
|
||||
-- 5) Опционально: соберите витрину dm.mart_customer_status_daily
|
||||
-- с количеством клиентов по статусам на каждую дату.
|
||||
|
||||
-- Ниже — ЗАГОТОВКИ блоков, которые можно дописать.
|
||||
-- Они намеренно оставлены пустыми, чтобы вы написали SQL сами.
|
||||
|
||||
-- 1. ODS: очистка и типизация
|
||||
-- TRUNCATE ods.customer_status;
|
||||
-- INSERT INTO ods.customer_status (...)
|
||||
-- SELECT ...
|
||||
-- FROM stg.customer_status_raw;
|
||||
|
||||
-- 2. DDS: начальная загрузка SCD2
|
||||
-- Примерный план:
|
||||
-- - рассчитать hashdiff по (status);
|
||||
-- - по каждому клиенту отсортировать события по времени;
|
||||
-- - построить для каждой строки valid_from и valid_to (LEAD() OVER ...);
|
||||
-- - вставить в dds.dim_customer_status.
|
||||
|
||||
-- 3. DDS: инкрементальная загрузка (по желанию)
|
||||
-- Можно ориентироваться на примеры в 03_demo_increment.sql.
|
||||
|
||||
-- 4. DM: витрина статусов клиентов по датам (по желанию)
|
||||
-- Пример целевой структуры:
|
||||
-- CREATE TABLE dm.mart_customer_status_daily (
|
||||
-- date_actual DATE NOT NULL,
|
||||
-- status VARCHAR(20) NOT NULL,
|
||||
-- customers_cnt INT NOT NULL
|
||||
-- );
|
||||
-- Идея: на каждую дату взять актуальный статус клиента
|
||||
-- через JOIN dds.dim_customer_status + dds.dim_date.
|
||||
|
||||
Reference in New Issue
Block a user