Пререработка структуры заголовков
This commit is contained in:
@@ -230,7 +230,7 @@ WHERE customer_id = 1 AND is_current = true
|
||||
|
||||
---
|
||||
|
||||
### А что, если СУБД не позволяет UPDATE? (Trino, Hive, ClickHouse в режиме append-only)
|
||||
#### А что, если СУБД не позволяет UPDATE? (Trino, Hive, ClickHouse в режиме append-only)
|
||||
|
||||
Некоторые аналитические системы (например, **Hive в формате ORC/Parquet**, **Trino**, **ClickHouse в режиме только вставки**) **не поддерживают UPDATE старых строк**. Как тогда реализовать SCD Type 2?
|
||||
|
||||
@@ -238,7 +238,7 @@ WHERE customer_id = 1 AND is_current = true
|
||||
|
||||
Алгоритм загрузки (ETL):
|
||||
1. Сравнить входящие данные с последней версией в таблице.
|
||||
2. Если есть изменения — **пишем новую строку** с новыми `valid_from` / `valid_to`.
|
||||
2. Если есть изменения — **пишем новую строку** с новыми `valid_from`.
|
||||
3. Старые строки остаются нетронутыми.
|
||||
|
||||
Пример в Trino/Hive-стиле (только INSERT):
|
||||
@@ -269,9 +269,9 @@ LEFT JOIN current_customers c ON n.customer_id = c.customer_id
|
||||
WHERE c.customer_id IS NULL OR c.category != n.category; -- условие верно, если какие-то из полей справочника поменялись
|
||||
```
|
||||
|
||||
### 🎯 Как работает этот запрос: пошаговое объяснение
|
||||
##### 🎯 Как работает этот запрос: пошаговое объяснение
|
||||
|
||||
#### Шаг 1: Подготовка данных (CTE current_customers)
|
||||
###### Шаг 1: Подготовка данных (CTE current_customers)
|
||||
CTE `current_customers` находит **последнюю версию** каждого клиента из таблицы `dim_customers_scd2`:
|
||||
|
||||
```sql
|
||||
@@ -283,7 +283,7 @@ FROM dim_customers_scd2
|
||||
- `ORDER BY valid_from DESC` — сортируем версии от самой новой к самой старой
|
||||
- `WHERE rn = 1` — выбираем только самую свежую версию
|
||||
|
||||
#### Шаг 2: Сравнение данных (LEFT JOIN + WHERE)
|
||||
###### Шаг 2: Сравнение данных (LEFT JOIN + WHERE)
|
||||
Теперь сравниваем новые данные с текущими:
|
||||
|
||||
```sql
|
||||
@@ -299,9 +299,10 @@ LEFT JOIN current_customers c ON n.customer_id = c.customer_id
|
||||
| Категория изменилась | 1 | 1 | ✅ `c.category != n.category` | Вставляется |
|
||||
| Без изменений | 3 | 3 | ❌ оба условия ложны | Пропускается |
|
||||
|
||||
#### Шаг 3: Вставка новых версий
|
||||
###### Шаг 3: Вставка новых версий
|
||||
Для подходящих записей создаём новую версию:
|
||||
- `uuid()` — генерируем уникальный ключ для новой версии (функция, скобки обязательны)
|
||||
- `uuid()` — генерируем уникальный ключ для новой версии
|
||||
- `current_date` - функция, возвращающая текущую даты
|
||||
- `COALESCE(n.effective_date, current_date)` — устанавливаем дату начала действия новой версии
|
||||
|
||||
> 💡 **Правильный подход к датам**: В реальных ETL-процессах важно использовать дату из исходных данных, когда она доступна. Мы используем `COALESCE(n.effective_date, current_date)`, что означает:
|
||||
@@ -320,7 +321,7 @@ LEFT JOIN current_customers c ON n.customer_id = c.customer_id
|
||||
> 1 | Иван Петров | VIP | 2025-04-15 ← дата реального изменения
|
||||
> ```
|
||||
|
||||
#### Практический пример
|
||||
##### Практический пример
|
||||
|
||||
**До выполнения запроса:**
|
||||
```
|
||||
@@ -348,11 +349,11 @@ customer_id | name | category | valid_from
|
||||
|
||||
> 💡 **Ключевой момент**: В append-only системах мы **не обновляем** старые записи, а только **добавляем новые**. История сохраняется автоматически!
|
||||
|
||||
**Главный вопрос после загрузки: как же читать эти данные?**
|
||||
##### Главный вопрос после загрузки: как же читать эти данные?
|
||||
|
||||
Поскольку мы не можем обновлять `valid_to` у предыдущей версии (у неё останется `NULL`), стандартный подход с `BETWEEN` не сработает. Вместо этого, для поиска нужной версии мы полагаемся на **оконные функции** или на логику «ближайшей даты, но не позже».
|
||||
Поскольку мы не можем обновлять `valid_to` у предыдущей версии, стандартный подход с `BETWEEN` не сработает. Вместо этого, для поиска нужной версии мы полагаемся на **оконные функции** или на логику «ближайшей даты, но не позже».
|
||||
|
||||
#### Паттерн 1: Найти последнюю (актуальную) версию на сегодня
|
||||
###### Паттерн 1: Найти последнюю (актуальную) версию на сегодня
|
||||
|
||||
Это самый частый запрос. Мы хотим видеть самую свежую информацию о клиенте.
|
||||
|
||||
@@ -372,7 +373,7 @@ WHERE rn = 1;
|
||||
|
||||
Этот запрос берёт строку с самой поздней датой начала действия для каждого клиента.
|
||||
|
||||
#### Паттерн 2: Найти версию, которая была актуальна на конкретную дату
|
||||
###### Паттерн 2: Найти версию, которая была актуальна на конкретную дату
|
||||
|
||||
Это основная цель SCD2. Например, «какой статус клиента был на дату заказа `2025-03-15`?».
|
||||
|
||||
@@ -434,10 +435,9 @@ WHERE rn = 1;
|
||||
|
||||
Медленно меняющиеся измерения — это не «магия», а **практический инструмент** для честной и точной аналитики во времени.
|
||||
|
||||
Начните с понимания разницы между Type 1 и Type 2. Попробуйте реализовать оба подхода в своём PostgreSQL. Задайте себе вопрос:
|
||||
Начните с понимания разницы между Type 1 и Type 2. Попробуйте реализовать оба подхода в своей БД. Задайте себе вопрос:
|
||||
> «Если бы я построил отчёт по данным на прошлый месяц — дал бы он правильный ответ после сегодняшнего изменения?»
|
||||
|
||||
Если нет — вам нужен SCD Type 2.
|
||||
|
||||
И помните: даже в системах без `UPDATE` вы можете хранить полную историю — достаточно понимать, как правильно читать данные с помощью оконных функций и временных границ.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user