feat(site): добавлен MkDocs Material сайт с CI/CD на GitHub Pages
- Зачем: - роадмап нуждается в презентабельном виде с навигацией и поиском, а не только GitHub README. - Что: - создан mkdocs.yml (Material, docs_dir: ., поиск на русском, тёмная/светлая тема). - создан .github/workflows/deploy-site.yml (push в main → сборка → GitHub Pages). - адаптирован Markdown для dual compatibility (GitHub + MkDocs): пустые строки перед списками, отступы 2sp→4sp, заголовки README #→## для корректного TOC. - заменены em-dash на запятые в 4 заголовках dwh-modeling/README.md (фикс расхождения якорей). - добавлены правила Markdown Style и команды MkDocs/Playwright в AGENTS.md. - Проверка: - uv run --with 'mkdocs-material==9.6.14' --with 'mkdocs-same-dir==0.1.3' mkdocs build --strict
This commit is contained in:
@@ -52,9 +52,9 @@ customer_id,status,event_ts,_load_id,_load_ts
|
||||
Чтобы не тратить время на DDL, структуры таблиц для домашки уже подготовлены в `dwh-modeling/sql`:
|
||||
|
||||
- `07_ddl_hw_customer_status.sql` — создаёт дополнительные таблицы:
|
||||
- `stg.customer_status_raw` — сырые события о статусе клиента;
|
||||
- `ods.customer_status` — очищенные и типизированные события;
|
||||
- `dds.dim_customer_status` — измерение статусов клиента в формате **SCD Type 2**.
|
||||
- `stg.customer_status_raw` — сырые события о статусе клиента;
|
||||
- `ods.customer_status` — очищенные и типизированные события;
|
||||
- `dds.dim_customer_status` — измерение статусов клиента в формате **SCD Type 2**.
|
||||
- `08_dml_hw_customer_status_template.sql` — шаблон DML-скрипта с подсказками и заготовками блоков.
|
||||
|
||||
Перед началом работы:
|
||||
@@ -127,9 +127,9 @@ SELECT * FROM stg.customer_status_raw LIMIT 10;
|
||||
В файле `08_dml_hw_customer_status_template.sql` найдите заготовку блока ODS и допишите SQL:
|
||||
|
||||
- привести:
|
||||
- `customer_id` → `INT`,
|
||||
- `status` → `VARCHAR(20)` (можно оставить как есть),
|
||||
- `event_ts` и `_load_ts` → `TIMESTAMP`;
|
||||
- `customer_id` → `INT`,
|
||||
- `status` → `VARCHAR(20)` (можно оставить как есть),
|
||||
- `event_ts` и `_load_ts` → `TIMESTAMP`;
|
||||
- аккуратно обработать возможные пустые значения (если бы они были);
|
||||
- заполнить `_load_id` и `_load_ts` в `ods.customer_status`.
|
||||
|
||||
@@ -234,8 +234,8 @@ cat dwh-modeling/data/customer_status_events_increment.csv | ./postgres-bookings
|
||||
|
||||
- ориентируйтесь на пример из `03_demo_increment.sql` для `dds.dim_customer`;
|
||||
- важно:
|
||||
- корректно «закрыть» старую актуальную строку (заполнить `valid_to` датой начала новой версии);
|
||||
- вставить новую строку с `valid_to = NULL`.
|
||||
- корректно «закрыть» старую актуальную строку (заполнить `valid_to` датой начала новой версии);
|
||||
- вставить новую строку с `valid_to = NULL`.
|
||||
|
||||
Эта часть особенно полезна, если вы хотите почувствовать, как SCD2 живёт в реальном DWH.
|
||||
|
||||
|
||||
+26
-17
@@ -3,28 +3,30 @@
|
||||
|
||||
## Оглавление
|
||||
|
||||
- [Что вы уже умеете — и что узнаете здесь](#что-вы-уже-умеете-и-что-узнаете-здесь)
|
||||
- [Что вы уже умеете, и что узнаете здесь](#что-вы-уже-умеете-и-что-узнаете-здесь)
|
||||
- [1. Введение: почему нельзя просто SELECT из базы заказов?](#1-введение-почему-нельзя-просто-select-из-базы-заказов)
|
||||
- [2. Учебный пример: интернет-магазин](#2-учебный-пример-интернет-магазин)
|
||||
- [3. Зачем делить DWH на слои?](#3-зачем-делить-dwh-на-слои)
|
||||
- [4. Путешествие данных: от STG до DM](#4-путешествие-данных-от-stg-до-dm)
|
||||
- [5. Базовые понятия: факты, измерения, SCD](#5-базовые-понятия-факты-измерения-scd)
|
||||
- [6. Модели данных для слоя DDS: 4 подхода — и когда какой выбрать](#6-модели-данных-для-слоя-dds-4-подхода-и-когда-какой-выбрать)
|
||||
- [6. Модели данных для слоя DDS: 4 подхода, и когда какой выбрать](#6-модели-данных-для-слоя-dds-4-подхода-и-когда-какой-выбрать)
|
||||
- [7. Практикум: как собрать первую витрину](#7-практикум-как-собрать-первую-витрину)
|
||||
- [8. Как выбрать модель данных? Советы от практиков](#8-как-выбрать-модель-данных-советы-от-практиков)
|
||||
- [9. Эксплуатация: качество данных — это не «опция»](#9-эксплуатация-качество-данных-это-не-опция)
|
||||
- [10. Заключение: главное — понимать «почему»](#10-заключение-главное-понимать-почему)
|
||||
- [9. Эксплуатация: качество данных, это не «опция»](#9-эксплуатация-качество-данных-это-не-опция)
|
||||
- [10. Заключение: главное, понимать «почему»](#10-заключение-главное-понимать-почему)
|
||||
- [Приложения](#приложения)
|
||||
|
||||
---
|
||||
|
||||
## Что вы уже умеете — и что узнаете здесь
|
||||
## Что вы уже умеете, и что узнаете здесь
|
||||
|
||||
✅ Уже знаете:
|
||||
|
||||
- `SELECT`, `JOIN`, `GROUP BY`;
|
||||
- как посчитать сумму/среднее/количество по таблице.
|
||||
|
||||
🆕 Узнаете в этой статье:
|
||||
|
||||
- **слои хранилища** (STG → ODS → DDS → DM) и *зачем они нужны*;
|
||||
- **факты и измерения** — основные кирпичики аналитики;
|
||||
- **SCD Type 2** — как хранить историю изменений клиента (например, смену email или города);
|
||||
@@ -32,6 +34,7 @@
|
||||
- **четыре модели данных**: 3NF, Звезда (Star), Data Vault, Anchor Modeling — и когда какую использовать.
|
||||
|
||||
⛔ **Не будем говорить** здесь о:
|
||||
|
||||
- физическом хранении (партиции, индексы, ClickHouse-движки);
|
||||
- распределённых кластерах (Kafka, Spark, Airflow — это отдельный курс);
|
||||
- настройке производительности (`EXPLAIN`, кэши и т.п.).
|
||||
@@ -47,6 +50,7 @@
|
||||
Вы идёте в базу заказов — и… не находите email. Он в CRM. Идёте в CRM — там нет сумм заказов. Возвращаетесь в заказы — сумма есть, но *только текущая цена товара*. А в 2023 году цена была другой!
|
||||
|
||||
Знакомо? Это — **проблема OLTP-систем** (оперативного учёта):
|
||||
|
||||
- **CRM**, **склад**, **платёжка** — это разные базы;
|
||||
- каждая оптимизирована под *быструю запись операций* («добавить заказ», «списать товар»);
|
||||
- историю там не хранят — email меняется «в лоб»: старое значение перезаписывается.
|
||||
@@ -75,6 +79,7 @@
|
||||
| `promos` | Маркетинг | Акции: `promo_id`, `code` |
|
||||
|
||||
⚠️ Обратите внимание:
|
||||
|
||||
- `customer_id = 101` в одном месяце — `a@ex.com`, в другом — `b@ex.com`;
|
||||
- цена на товар `9001` (Phone) в январе — 100 ₽, в феврале — 110 ₽;
|
||||
- `order_items` содержит `price_at_sale` — *цену в момент покупки*, а не текущую.
|
||||
@@ -145,8 +150,8 @@ flowchart TD
|
||||
- Таблицы: `stg.orders_raw`, `stg.customers_raw`;
|
||||
- Структура — *точно как в источнике* (может быть `VARCHAR` даже у дат);
|
||||
- Добавлены технические поля:
|
||||
- `_load_id` — идентификатор загрузки;
|
||||
- `_load_ts` — время получения данных;
|
||||
- `_load_id` — идентификатор загрузки;
|
||||
- `_load_ts` — время получения данных;
|
||||
- Главное правило: **неизменяемость**. Если пришла новая порция — либо добавляем новые строки, либо *полностью перезагружаем* слой (идемпотентность).
|
||||
|
||||
> 💡 *Пример:* `stg.orders_raw` содержит `"2024-01-10"` как строку — это нормально. Главное — не потерять оригинал.
|
||||
@@ -157,10 +162,10 @@ flowchart TD
|
||||
|
||||
- Таблицы: `ods.orders`, `ods.customers`;
|
||||
- Здесь:
|
||||
- привели `order_date` к типу `DATE`;
|
||||
- убрали заказы без клиента (`customer_id IS NULL` → ошибка или флаг);
|
||||
- привели телефоны к формату `79991112233`;
|
||||
- проверили email на валидность (регуляркой или простой проверкой).
|
||||
- привели `order_date` к типу `DATE`;
|
||||
- убрали заказы без клиента (`customer_id IS NULL` → ошибка или флаг);
|
||||
- привели телефоны к формату `79991112233`;
|
||||
- проверили email на валидность (регуляркой или простой проверкой).
|
||||
- **Но!** Не объединяем клиента из CRM и клиента из заказов — это будет позже.
|
||||
- Пока — никакой бизнес-логики. Только *техническая* очистка.
|
||||
- Дедупликация: если два раза пришёл один и тот же заказ — оставляем один (по `order_id + _load_ts`).
|
||||
@@ -197,6 +202,7 @@ flowchart TD
|
||||
### **DM (Data Mart / Gold/ «Витрины»)** — «готово к употреблению»
|
||||
|
||||
Здесь — таблицы и представления для конкретных задач:
|
||||
|
||||
- `dm.mart_daily_sales` — ежедневные продажи по товарам и сегментам;
|
||||
- `dm.mart_customer_360` — полный портрет клиента: сколько потратил, когда заходил, какие товары любит.
|
||||
|
||||
@@ -210,12 +216,13 @@ flowchart TD
|
||||
> *«10 января 2024 года клиент из Москвы (сегмент Premium) купил Phone за 100 ₽»*.
|
||||
|
||||
В DWH это разложится на:
|
||||
|
||||
- **Факт (Fact)** — событие, которое можно измерить: *покупка*.
|
||||
Хранится в `fact_sales`: `quantity = 1`, `amount = 100`.
|
||||
- **Измерения (Dimensions)** — *контекст* факта:
|
||||
- `dim_date` → 10 января 2024;
|
||||
- `dim_customer` → Москва, Premium;
|
||||
- `dim_product` → Phone.
|
||||
- `dim_date` → 10 января 2024;
|
||||
- `dim_customer` → Москва, Premium;
|
||||
- `dim_product` → Phone.
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
@@ -262,6 +269,7 @@ erDiagram
|
||||
### SCD Type 2 — как хранить историю
|
||||
|
||||
Клиент №101:
|
||||
|
||||
- с 1 янв по 15 мая — `email = a@ex.com`, `city = Москва`;
|
||||
- с 16 мая — `email = b@ex.com`, `city = Москва`;
|
||||
- с 1 окт — `email = b@ex.com`, `city = Санкт-Петербург`.
|
||||
@@ -287,7 +295,7 @@ AND (dim_customer.valid_to IS NULL OR fact_sales.order_date < dim_customer.valid
|
||||
|
||||
---
|
||||
|
||||
## 6. Модели данных для слоя DDS: 4 подхода — и когда какой выбрать
|
||||
## 6. Модели данных для слоя DDS: 4 подхода, и когда какой выбрать
|
||||
|
||||
В DDS мы можем хранить данные по-разному. Это не «правильно/неправильно», а **выбор под задачу**.
|
||||
|
||||
@@ -431,6 +439,7 @@ WHERE c.city = 'Москва'
|
||||
|
||||
💡 **Главная мысль:**
|
||||
идентичность, связи и атрибуты живут **в разных таблицах**, поэтому:
|
||||
|
||||
- историю проще хранить;
|
||||
- новые источники проще прикручивать;
|
||||
- меньше шансов «сломать» старые отчёты.
|
||||
@@ -663,7 +672,7 @@ GROUP BY d.date_actual, p.product_name,
|
||||
|
||||
---
|
||||
|
||||
## 9. Эксплуатация: качество данных — это не «опция»
|
||||
## 9. Эксплуатация: качество данных, это не «опция»
|
||||
|
||||
Самая красивая архитектура бессмысленна, если в `mart_daily_sales` — нули.
|
||||
Поэтому в каждом слое — **контроль качества (DQ, Data Quality)**.
|
||||
@@ -706,7 +715,7 @@ SELECT 'OK' WHERE EXISTS (
|
||||
|
||||
---
|
||||
|
||||
## 10. Заключение: главное — понимать «почему»
|
||||
## 10. Заключение: главное, понимать «почему»
|
||||
|
||||
Хранилище данных — это не про «крутые технологии», а про **мышление**:
|
||||
|
||||
|
||||
@@ -283,6 +283,7 @@ LEFT JOIN current_customers c ON n.customer_id = c.customer_id
|
||||
|
||||
###### Шаг 3: Вставка новых версий
|
||||
Для подходящих записей создаём новую версию:
|
||||
|
||||
- `uuid()` — генерируем уникальный ключ для новой версии
|
||||
- `current_date` - функция, возвращающая текущую даты
|
||||
- `COALESCE(n.effective_date, current_date)` — устанавливаем дату начала действия новой версии
|
||||
|
||||
Reference in New Issue
Block a user