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