diff --git a/dwh-modeling/README.md b/dwh-modeling/README.md index b269be5..fa80267 100644 --- a/dwh-modeling/README.md +++ b/dwh-modeling/README.md @@ -291,7 +291,7 @@ AND (dim_customer.valid_to IS NULL OR fact_sales.order_date < dim_customer.valid В DDS мы можем хранить данные по-разному. Это не «правильно/неправильно», а **выбор под задачу**. -### 1. 3NF (третья нормальная форма) +### 1. 3NF (третья нормальная форма) *Источник: Билл Инмон (Bill Inmon)* Если упростить, 3NF - это когда данные о разных бизнес-сущностях хранятся в отдельных таблицах и связываются ключами: клиент, заказ, город, регион, страна и т.д. Вместо одной большой таблицы с большим числом дублирующихся данных мы получаем цепочку таблиц, связанных ключами: `Заказ → Клиент → Город → Регион → Страна`. JOIN-ов становится больше, зато одно и то же свойство (например, название города) хранится в одном месте, а не дублируется в каждой строке заказа. @@ -303,6 +303,53 @@ AND (dim_customer.valid_to IS NULL OR fact_sales.order_date < dim_customer.valid * Данные о сущностях разнесены по отдельным таблицам: клиент, заказ, продукт. * Минимум дублирования: общие атрибуты хранятся в одном месте, таблицы связаны ключами. +#### Как выглядел бы наш магазин в 3NF + +В нашем примере `city` лежит прямо в `dim_customer`. В 3NF город стал бы отдельной таблицей, чтобы название хранилось в одном месте: + +```mermaid +erDiagram + dim_city ||--o{ dim_customer : "город" + dim_customer ||--o{ fact_sales : "клиент" + + dim_city { + int city_id PK + varchar city_name "Москва, СПб, ..." + } + + dim_customer { + bigint customer_sk PK + int customer_bk + varchar email + int city_id FK "ссылка на dim_city" + date valid_from + date valid_to + } + + fact_sales { + bigint sale_id PK + bigint customer_sk FK + int date_key FK + int quantity + decimal amount + } +``` + +Теперь, чтобы узнать **«сколько потратил клиент из Москвы за январь 2024?»**, нужно пройти по цепочке: + +```sql +-- 3NF: три JOIN, чтобы добраться до города +SELECT SUM(f.amount) +FROM dds.fact_sales f +JOIN dds.dim_customer c ON f.customer_sk = c.customer_sk +JOIN dds.dim_city ct ON c.city_id = ct.city_id +JOIN dds.dim_date d ON f.date_key = d.date_key +WHERE ct.city_name = 'Москва' + AND d.year = 2024 AND d.month = 1; +``` + +Запрос читаемый, но JOIN-ов уже три - и это для простого вопроса. В реальном ядре цепочка может быть длиннее: `Клиент → Город → Регион → Страна`. + **Плюсы:** * Удобно поддерживать **единую «карту бизнеса»**: где живут «клиент», «заказ», «договор» и как они связаны; @@ -319,22 +366,36 @@ AND (dim_customer.valid_to IS NULL OR fact_sales.order_date < dim_customer.valid --- -### 2. Звезда (Star Schema) +### 2. Звезда (Star Schema) *Источник: Ральф Кимболл (Ralph Kimball)* -Самый распространённый способ построения таблиц для слоя витрин. +Самый распространённый способ построения таблиц для слоя витрин. Именно эту модель мы использовали в [разделе 5](#5-базовые-понятия-факты-измерения-scd): схема `fact_sales` + `dim_date` / `dim_customer` / `dim_product` - это и есть Звезда. Структура: * в центре - таблица фактов (события и метрики); * вокруг - измерения, обычно денормализованные («плоские») - широкие таблицы со всеми атрибутами сущности. Мы сознательно избегаем цепочек справочников ради простоты запросов. -Пример: `fact_sales` + `dim_date`, `dim_customer`, `dim_product`. - ![Звёздная схема вокруг fact_sales](images/star-model-small.jpg) Методология Ральфа Кимбалла (Ralph Kimball) как раз делает упор на такие звёздные схемы: витрины, которые максимально просты для чтения и понятны аналитикам и BI-инструментам. +#### Тот же вопрос - в Звезде + +В Звезде `city` лежит прямо в `dim_customer` (денормализовано). Тот же отчёт выглядит проще: + +```sql +-- Звезда: два JOIN, город - прямо в измерении +SELECT SUM(f.amount) +FROM dds.fact_sales f +JOIN dds.dim_customer c ON f.customer_sk = c.customer_sk +JOIN dds.dim_date d ON f.date_key = d.date_key +WHERE c.city = 'Москва' + AND d.year = 2024 AND d.month = 1; +``` + +На один JOIN меньше, и не нужно знать, где именно хранится город: он лежит прямо в карточке клиента. Для аналитика или BI-инструмента это большая разница. + **Плюсы:** * проста для понимания: аналитикам и BI-инструментам удобно работать с такой моделью; @@ -343,7 +404,7 @@ AND (dim_customer.valid_to IS NULL OR fact_sales.order_date < dim_customer.valid **Минусы:** -* измерения денормализованы, поэтому атрибуты дублируются (например, регион повторяется у всех клиентов региона); +* измерения денормализованы, поэтому атрибуты дублируются (например, название города повторяется у всех клиентов из этого города); * изменения атрибутов могут требовать обновлять много строк в измерении.