Стилистические правки

This commit is contained in:
2025-11-08 19:21:43 +03:00
parent be1557716f
commit c52d0d8b31
3 changed files with 16 additions and 19 deletions
+1
View File
@@ -1 +1,2 @@
.env .env
.idea
+13 -18
View File
@@ -1,10 +1,3 @@
Отлично! Вот **связный, литературный и педагогически выстроенный** вариант статьи, адаптированный под новичков (студентов с базовым SQL), но без упрощения сути. Я убрал «рыхлые» места, усилил плавность повествования, добавил пояснения «на пальцах» — и сохранил все важные детали: архитектурные слои, модели данных, SCD, ключи, антипаттерны.
Статья оформлена как **обучающий материал** — с логикой «от боли к решению», с живым примером (интернет-магазин), с акцентом на *почему*, а не только *как*.
Готова к публикации на Habr, с приложенным GitHub-репозиторием.
---
# **Хранилище данных: как устроена аналитика «под капотом»** # **Хранилище данных: как устроена аналитика «под капотом»**
*Для тех, кто знает SQL, но хочет понять, как хранить данные не в Excel, а по-взрослому* *Для тех, кто знает SQL, но хочет понять, как хранить данные не в Excel, а по-взрослому*
@@ -132,12 +125,12 @@ flowchart TD
Давайте проследим, как превращается строка заказа. Давайте проследим, как превращается строка заказа.
### **STG (Staging / «Бронза»)** — «как пришло» ### **STG (Staging / Bronze)** — «как пришло»
- Таблицы: `stg.orders_raw`, `stg.customers_raw`; - Таблицы: `stg.orders_raw`, `stg.customers_raw`;
- Структура — *точно как в источнике* (может быть `VARCHAR` даже у дат); - Структура — *точно как в источнике* (может быть `VARCHAR` даже у дат);
- Добавлены технические поля: - Добавлены технические поля:
- `_ingest_id` — идентификатор загрузки; - `_load_id` — идентификатор загрузки;
- `_load_ts` — время получения данных; - `_load_ts` — время получения данных;
- Главное правило: **неизменяемость**. Если пришла новая порция — либо добавляем новые строки, либо *полностью перезагружаем* слой (идемпотентность). - Главное правило: **неизменяемость**. Если пришла новая порция — либо добавляем новые строки, либо *полностью перезагружаем* слой (идемпотентность).
- Дедупликация: если два раза пришёл один и тот же заказ — оставляем один (по `order_id + _load_ts`). - Дедупликация: если два раза пришёл один и тот же заказ — оставляем один (по `order_id + _load_ts`).
@@ -146,7 +139,7 @@ flowchart TD
--- ---
### **ODS (Operational Data Store / «Серебро»)** — «почистили, но не трогали смысл» ### **ODS (Operational Data Store / Silver)** — «почистили, но не трогали смысл»
- Таблицы: `ods.orders`, `ods.customers`; - Таблицы: `ods.orders`, `ods.customers`;
- Здесь: - Здесь:
@@ -161,7 +154,7 @@ flowchart TD
--- ---
### **DDS (Data Delivery Store / «Ядро», «Золото»)** — «интеграция + история» ### **DDS (Data Delivery Store / Core / Gold)** — «интеграция + история»
Здесь рождается *единая бизнес-модель*. Здесь рождается *единая бизнес-модель*.
Появляются понятия: **измерения**, **факты**, **суррогатные ключи**, **SCD**. Появляются понятия: **измерения**, **факты**, **суррогатные ключи**, **SCD**.
@@ -192,7 +185,7 @@ flowchart TD
- `dm.mart_daily_sales` — ежедневные продажи по товарам и сегментам; - `dm.mart_daily_sales` — ежедневные продажи по товарам и сегментам;
- `dm.mart_customer_360` — полный портрет клиента: сколько потратил, когда заходил, какие товары любит. - `dm.mart_customer_360` — полный портрет клиента: сколько потратил, когда заходил, какие товары любит.
Они построены по модели **Звезда (Star Schema)** — потому что BI-инструментам так удобнее всего. Они часто построены по модели **Звезда (Star Schema)** — потому что BI-инструментам так удобнее всего.
--- ---
@@ -257,7 +250,7 @@ fact_sales.order_date BETWEEN dim_customer.valid_from AND dim_customer.valid_to
``` ```
и получаем актуальный на тот день email и город. и получаем актуальный на тот день email и город.
> 🔍 Подробнее про SCD — в отдельной статье [SQC](SCD.md) (сравнение Type 1/2/3, паттерны обновления). > 🔍 Подробнее про SCD — в отдельной статье [Slow Changing Dimensions](SCD.md) (сравнение Type 1/2/3, паттерны обновления).
--- ---
@@ -269,16 +262,18 @@ fact_sales.order_date BETWEEN dim_customer.valid_from AND dim_customer.valid_to
*Источник: Билл Инмон (Bill Inmon)* *Источник: Билл Инмон (Bill Inmon)*
**Плюсы**: **Плюсы**:
- Максимальная **целостность** — дубликатов нет (все атрибуты — там, где должны быть); - Минимум избыточности при строгих ключах и правилах дедупликации.
- Легко **расширять** — добавили новый источник? Расширили связанный справочник. - Проще поддерживать единую терминологию и НСИ (reference data).
- Атрибуты и справочники легко расширять.
**Минусы**: **Минусы**:
- Запросы сложные: много JOIN’ов даже для простого отчёта; - Много JOIN даже для простых отчётов.
- Тяжело новичкам: «а где город клиента?» → нужно пройти `customer → address → city`. - Историчность (SCD2) усложняет таблицы.
- Новые источники дороже гармонизировать (привести к канону).
📌 **Когда выбирать**: 📌 **Когда выбирать**:
→ Корпоративные DWH, где важна *единая терминология* и *долгосрочная поддержка*. → Корпоративные DWH, где важна *единая терминология* и *долгосрочная поддержка*.
Когда источников — десятки, и нужно гарантировать консистентность. Стабильные домены (финансы, НСИ, договоры) и умеренная динамика изменений.
--- ---
+2 -1
View File
@@ -56,7 +56,8 @@ WITH src AS (
s._load_id, s._load_id,
s._load_ts, s._load_ts,
COALESCE(NULLIF(s.event_ts, '')::timestamp, s._load_ts, COALESCE(NULLIF(s.event_ts, '')::timestamp, s._load_ts,
to_timestamp(regexp_replace(s._load_id,'^batch_',''),'YYYYMMDD_HH24MI')) AS eff_ts to_timestamp(regexp_replace(s._load_id,'^batch_',''),'YYYYMMDD_HH24MI'))
AS eff_ts
FROM stg.customers_raw s FROM stg.customers_raw s
WHERE s.customer_id ~ '^\d+$' WHERE s.customer_id ~ '^\d+$'
), ),