From e9b50406e348f3cf067cc703a6a71920e9391cf4 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Tue, 4 Nov 2025 18:00:40 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=B5=D1=80=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20=D1=81=D1=82=D1=80=D1=83=D0=BA?= =?UTF-8?q?=D1=82=D1=83=D1=80=D1=8B=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SCD.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/SCD.md b/SCD.md index 48ecdd4..8d674dc 100644 --- a/SCD.md +++ b/SCD.md @@ -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` вы можете хранить полную историю — достаточно понимать, как правильно читать данные с помощью оконных функций и временных границ. -