Раскрыт детальнее update для trino
This commit is contained in:
@@ -243,6 +243,8 @@ WHERE customer_id = 1 AND is_current = true
|
|||||||
|
|
||||||
Пример в Trino/Hive-стиле (только INSERT):
|
Пример в Trino/Hive-стиле (только INSERT):
|
||||||
|
|
||||||
|
Давайте разберём этот запрос по шагам, чтобы понять его логику:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- new_customers — staging-таблица с обновлёнными данными
|
-- new_customers — staging-таблица с обновлёнными данными
|
||||||
-- dim_customers_scd2 — основная таблица (append-only)
|
-- dim_customers_scd2 — основная таблица (append-only)
|
||||||
@@ -261,12 +263,109 @@ SELECT
|
|||||||
n.customer_id, -- неизменный бизнес-ID клиента
|
n.customer_id, -- неизменный бизнес-ID клиента
|
||||||
n.name,
|
n.name,
|
||||||
n.category,
|
n.category,
|
||||||
valid_from
|
COALESCE(n.effective_date, current_date) AS valid_from -- дата начала действия новой версии
|
||||||
FROM new_customers n
|
FROM new_customers n
|
||||||
LEFT JOIN current_customers c ON n.customer_id = c.customer_id
|
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; -- условие верно, если какие-то из полей справочника поменялись
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 🎯 Как работает этот запрос: пошаговое объяснение
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[new_customers<br/>новые данные] --> B[CTE: current_customers]
|
||||||
|
C[dim_customers_scd2<br/>текущее состояние] --> B
|
||||||
|
B --> D[LEFT JOIN по customer_id]
|
||||||
|
A --> D
|
||||||
|
D --> E{Условие WHERE}
|
||||||
|
E -->|Новый клиент| F[Вставить запись]
|
||||||
|
E -->|Изменения| F
|
||||||
|
E -->|Без изменений| G[Пропустить]
|
||||||
|
F --> H[dim_customers_scd2<br/>обновленная таблица]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Шаг 1: Подготовка данных (CTE current_customers)
|
||||||
|
CTE `current_customers` находит **последнюю версию** каждого клиента из таблицы `dim_customers_scd2`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT *, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY valid_from DESC) AS rn
|
||||||
|
FROM dim_customers_scd2
|
||||||
|
```
|
||||||
|
|
||||||
|
- `PARTITION BY customer_id` — группируем по клиентам
|
||||||
|
- `ORDER BY valid_from DESC` — сортируем версии от самой новой к самой старой
|
||||||
|
- `WHERE rn = 1` — выбираем только самую свежую версию
|
||||||
|
|
||||||
|
#### Шаг 2: Сравнение данных (LEFT JOIN + WHERE)
|
||||||
|
Теперь сравниваем новые данные с текущими:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
FROM new_customers n
|
||||||
|
LEFT JOIN current_customers c ON n.customer_id = c.customer_id
|
||||||
|
```
|
||||||
|
|
||||||
|
**Возможные сценарии после JOIN:**
|
||||||
|
|
||||||
|
| Сценарий | n.customer_id | c.customer_id | Условие WHERE | Результат |
|
||||||
|
|----------|---------------|---------------|---------------|-----------|
|
||||||
|
| Новый клиент | 2 | NULL | ✅ `c.customer_id IS NULL` | Вставляется |
|
||||||
|
| Категория изменилась | 1 | 1 | ✅ `c.category != n.category` | Вставляется |
|
||||||
|
| Без изменений | 3 | 3 | ❌ оба условия ложны | Пропускается |
|
||||||
|
|
||||||
|
#### Шаг 3: Вставка новых версий
|
||||||
|
Для подходящих записей создаём новую версию:
|
||||||
|
- `uuid()` — генерируем уникальный ключ для новой версии (функция, скобки обязательны)
|
||||||
|
- `COALESCE(n.effective_date, current_date)` — устанавливаем дату начала действия новой версии
|
||||||
|
|
||||||
|
> 💡 **Синтаксическая поправка**:
|
||||||
|
> - `current_date` — это ключевое слово, а не функция, поэтому скобки не нужны
|
||||||
|
> - `uuid()` — это функция, поэтому скобки обязательны
|
||||||
|
> - Разница: `current_date` возвращает текущую дату, `uuid()` генерирует уникальный идентификатор
|
||||||
|
|
||||||
|
> 💡 **Правильный подход к датам**: В реальных ETL-процессах важно использовать дату из исходных данных, когда она доступна. Мы используем `COALESCE(n.effective_date, current_date)`, что означает:
|
||||||
|
> - Если в `new_customers` есть поле `effective_date` — используем его
|
||||||
|
> - Если нет — используем текущую дату (`current_date`)
|
||||||
|
>
|
||||||
|
> **Почему это важно:**
|
||||||
|
> - `effective_date` отражает реальную дату изменения (например, когда клиент стал VIP)
|
||||||
|
> - `current_date` — это дата загрузки данных, которая может не совпадать с датой изменения
|
||||||
|
> - Использование правильной даты критично для точного исторического анализа
|
||||||
|
>
|
||||||
|
> **Пример правильной структуры исходных данных:**
|
||||||
|
> ```sql
|
||||||
|
> new_customers:
|
||||||
|
> customer_id | name | category | effective_date
|
||||||
|
> 1 | Иван Петров | VIP | 2025-04-15 ← дата реального изменения
|
||||||
|
> ```
|
||||||
|
|
||||||
|
#### Практический пример
|
||||||
|
|
||||||
|
**До выполнения запроса:**
|
||||||
|
```
|
||||||
|
dim_customers_scd2:
|
||||||
|
customer_id | name | category | valid_from
|
||||||
|
1 | Иван Петров | Regular | 2024-01-01
|
||||||
|
```
|
||||||
|
|
||||||
|
**Новые данные:**
|
||||||
|
```
|
||||||
|
new_customers:
|
||||||
|
customer_id | name | category
|
||||||
|
1 | Иван Петров | VIP ← изменилась категория
|
||||||
|
2 | Мария Иванова| Regular ← новый клиент
|
||||||
|
```
|
||||||
|
|
||||||
|
**После выполнения запроса:**
|
||||||
|
```
|
||||||
|
dim_customers_scd2:
|
||||||
|
customer_id | name | category | valid_from
|
||||||
|
1 | Иван Петров | Regular | 2024-01-01 ← старая версия
|
||||||
|
1 | Иван Петров | VIP | 2025-11-04 ← новая версия
|
||||||
|
2 | Мария Иванова| Regular | 2025-11-04 ← новый клиент
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💡 **Ключевой момент**: В append-only системах мы **не обновляем** старые записи, а только **добавляем новые**. История сохраняется автоматически!
|
||||||
|
|
||||||
**Главный вопрос после загрузки: как же читать эти данные?**
|
**Главный вопрос после загрузки: как же читать эти данные?**
|
||||||
|
|
||||||
Поскольку мы не можем обновлять `valid_to` у предыдущей версии (у неё останется `NULL`), стандартный подход с `BETWEEN` не сработает. Вместо этого, для поиска нужной версии мы полагаемся на **оконные функции** или на логику «ближайшей даты, но не позже».
|
Поскольку мы не можем обновлять `valid_to` у предыдущей версии (у неё останется `NULL`), стандартный подход с `BETWEEN` не сработает. Вместо этого, для поиска нужной версии мы полагаемся на **оконные функции** или на логику «ближайшей даты, но не позже».
|
||||||
@@ -281,7 +380,7 @@ WITH ranked AS (
|
|||||||
SELECT
|
SELECT
|
||||||
*,
|
*,
|
||||||
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY valid_from DESC) AS rn
|
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY valid_from DESC) AS rn
|
||||||
FROM customers_scd2
|
FROM dim_customers_scd2
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
customer_id, name, category, valid_from
|
customer_id, name, category, valid_from
|
||||||
@@ -303,7 +402,7 @@ WITH as_of_date AS (
|
|||||||
SELECT
|
SELECT
|
||||||
*,
|
*,
|
||||||
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY valid_from DESC) AS rn
|
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY valid_from DESC) AS rn
|
||||||
FROM customers_scd2
|
FROM dim_customers_scd2
|
||||||
WHERE valid_from <= DATE '2025-03-15'
|
WHERE valid_from <= DATE '2025-03-15'
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
@@ -360,6 +459,3 @@ WHERE rn = 1;
|
|||||||
|
|
||||||
И помните: даже в системах без `UPDATE` вы можете хранить полную историю — достаточно понимать, как правильно читать данные с помощью оконных функций и временных границ.
|
И помните: даже в системах без `UPDATE` вы можете хранить полную историю — достаточно понимать, как правильно читать данные с помощью оконных функций и временных границ.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Готово! Статья теперь включает полный цикл: от концепции — к реализации в традиционной СУБД — и далее к адаптации для современных аналитических платформ.
|
|
||||||
Reference in New Issue
Block a user