Раскрыт детальнее update для trino
This commit is contained in:
@@ -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` вы можете хранить полную историю — достаточно понимать, как правильно читать данные с помощью оконных функций и временных границ.
|
||||
|
||||
---
|
||||
|
||||
Готово! Статья теперь включает полный цикл: от концепции — к реализации в традиционной СУБД — и далее к адаптации для современных аналитических платформ.
|
||||
Reference in New Issue
Block a user