Пререработка структуры заголовков

This commit is contained in:
2025-11-04 18:00:40 +03:00
parent 197ef1b0d2
commit e9b50406e3
+14 -14
View File
@@ -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` вы можете хранить полную историю — достаточно понимать, как правильно читать данные с помощью оконных функций и временных границ.