docs(modeling): расширены разделы 3NF и Звезда практическими примерами
- Зачем:
- разделы 3NF и Звезда были слишком краткими для учебного материала,
менти не видел разницу между моделями на практике
- Что:
- 3NF: добавлена mermaid-диаграмма с dim_city и SQL-запрос (3 JOIN)
- Звезда: добавлена явная связь с разделом 5, SQL-запрос (2 JOIN) для контраста
- оба примера отвечают на один вопрос: «сколько потратил клиент из Москвы?»
- Проверка:
- визуальная проверка diff
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+65
-4
@@ -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-ов уже три - и это для простого вопроса. В реальном ядре цепочка может быть длиннее: `Клиент → Город → Регион → Страна`.
|
||||||
|
|
||||||
**Плюсы:**
|
**Плюсы:**
|
||||||
|
|
||||||
* Удобно поддерживать **единую «карту бизнеса»**: где живут «клиент», «заказ», «договор» и как они связаны;
|
* Удобно поддерживать **единую «карту бизнеса»**: где живут «клиент», «заказ», «договор» и как они связаны;
|
||||||
@@ -322,19 +369,33 @@ AND (dim_customer.valid_to IS NULL OR fact_sales.order_date < dim_customer.valid
|
|||||||
### 2. Звезда (Star Schema)
|
### 2. Звезда (Star Schema)
|
||||||
*Источник: Ральф Кимболл (Ralph Kimball)*
|
*Источник: Ральф Кимболл (Ralph Kimball)*
|
||||||
|
|
||||||
Самый распространённый способ построения таблиц для слоя витрин.
|
Самый распространённый способ построения таблиц для слоя витрин. Именно эту модель мы использовали в [разделе 5](#5-базовые-понятия-факты-измерения-scd): схема `fact_sales` + `dim_date` / `dim_customer` / `dim_product` - это и есть Звезда.
|
||||||
|
|
||||||
Структура:
|
Структура:
|
||||||
|
|
||||||
* в центре - таблица фактов (события и метрики);
|
* в центре - таблица фактов (события и метрики);
|
||||||
* вокруг - измерения, обычно денормализованные («плоские») - широкие таблицы со всеми атрибутами сущности. Мы сознательно избегаем цепочек справочников ради простоты запросов.
|
* вокруг - измерения, обычно денормализованные («плоские») - широкие таблицы со всеми атрибутами сущности. Мы сознательно избегаем цепочек справочников ради простоты запросов.
|
||||||
|
|
||||||
Пример: `fact_sales` + `dim_date`, `dim_customer`, `dim_product`.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Методология Ральфа Кимбалла (Ralph Kimball) как раз делает упор на такие звёздные схемы: витрины, которые максимально просты для чтения и понятны аналитикам и BI-инструментам.
|
Методология Ральфа Кимбалла (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-инструментам удобно работать с такой моделью;
|
* проста для понимания: аналитикам и BI-инструментам удобно работать с такой моделью;
|
||||||
@@ -343,7 +404,7 @@ AND (dim_customer.valid_to IS NULL OR fact_sales.order_date < dim_customer.valid
|
|||||||
|
|
||||||
**Минусы:**
|
**Минусы:**
|
||||||
|
|
||||||
* измерения денормализованы, поэтому атрибуты дублируются (например, регион повторяется у всех клиентов региона);
|
* измерения денормализованы, поэтому атрибуты дублируются (например, название города повторяется у всех клиентов из этого города);
|
||||||
* изменения атрибутов могут требовать обновлять много строк в измерении.
|
* изменения атрибутов могут требовать обновлять много строк в измерении.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user