Раскрыт детальнее update для trino

This commit is contained in:
2025-11-04 17:39:17 +03:00
parent f789839643
commit f18bd7ea39
+110 -14
View File
@@ -243,6 +243,8 @@ WHERE customer_id = 1 AND is_current = true
Пример в Trino/Hive-стиле (только INSERT):
Давайте разберём этот запрос по шагам, чтобы понять его логику:
```sql
-- new_customers — staging-таблица с обновлёнными данными
-- dim_customers_scd2 — основная таблица (append-only)
@@ -253,7 +255,7 @@ WITH current_customers AS (
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY valid_from DESC) AS rn
FROM dim_customers_scd2
)
)
WHERE rn = 1 -- отбираем первую, самую свежую, запись
)
SELECT
@@ -261,12 +263,109 @@ SELECT
n.customer_id, -- неизменный бизнес-ID клиента
n.name,
n.category,
valid_from
COALESCE(n.effective_date, current_date) AS valid_from -- дата начала действия новой версии
FROM new_customers n
LEFT JOIN current_customers c ON n.customer_id = c.customer_id
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` не сработает. Вместо этого, для поиска нужной версии мы полагаемся на **оконные функции** или на логику «ближайшей даты, но не позже».
@@ -278,14 +377,14 @@ WHERE c.customer_id IS NULL OR c.category != n.category; -- условие ве
```sql
-- Вариант с оконной функцией (универсальный и надежный)
WITH ranked AS (
SELECT
*,
SELECT
*,
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
FROM ranked
FROM ranked
WHERE rn = 1;
```
@@ -300,15 +399,15 @@ WHERE rn = 1;
```sql
-- Запрос для получения состояния на '2025-03-15'
WITH as_of_date AS (
SELECT
SELECT
*,
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'
)
SELECT
SELECT
customer_id, name, category, valid_from
FROM as_of_date
FROM as_of_date
WHERE rn = 1;
```
@@ -360,6 +459,3 @@ WHERE rn = 1;
И помните: даже в системах без `UPDATE` вы можете хранить полную историю — достаточно понимать, как правильно читать данные с помощью оконных функций и временных границ.
---
Готово! Статья теперь включает полный цикл: от концепции — к реализации в традиционной СУБД — и далее к адаптации для современных аналитических платформ.