Type 0 (retain) и Type 1 (overwrite)
В прошлом уроке мы увидели проблему: атрибуты dimension меняются, и от способа обработки изменения зависит достоверность отчётов. Начнём с двух простейших решений каталога SCD — Type 0 и Type 1. Оба не хранят историю изменений. И оба — не «плохие» или «урезанные» варианты, а корректные инженерные решения для атрибутов определённого рода.
Type 0 — для атрибутов, которые по своей природе не меняются вообще. Type 1 — для атрибутов, у которых меняется значение, но бизнес сознательно решает хранить только актуальное.
Type 0 — Retain original
SCD Type 0 означает: атрибут никогда не меняется. Если из источника приходит новое значение этого атрибута — оно игнорируется, исходное остаётся нетронутым.
Type 0 подходит для атрибутов, неизменных по своей сути. Дата рождения человека. Оригинальная дата открытия счёта. Дата первой регистрации пользователя. Номер VIN автомобиля. Эти значения зафиксированы в момент создания сущности и физически не могут стать другими — а если в источнике они «изменились», это почти наверняка исправление ошибки ввода, а не реальное изменение.
CREATE TABLE dim_customer (
customer_key INT PRIMARY KEY,
customer_name VARCHAR(120),
date_of_birth DATE, -- Type 0: задаётся при создании, не меняется
original_signup_date DATE, -- Type 0: дата первой регистрации, неизменна
email VARCHAR(160), -- (тип разберём ниже)
city VARCHAR(80) -- (тип разберём в следующем уроке)
);
Механика Type 0 — это, по сути, отсутствие механики: ETL-процесс при загрузке просто не трогает Type 0-столбцы существующих строк. Значение записывается ровно один раз — в момент создания строки dimension — и дальше остаётся неприкосновенным навсегда.
Тонкий момент: что делать, если из источника всё-таки пришло другое значение Type 0-атрибута? Например, в системе исправили дату рождения клиента. Type 0 предписывает это новое значение проигнорировать. Логика в том, что для по-настоящему неизменного атрибута «другое значение из источника» — это либо исправление давней опечатки, либо ошибка источника, и автоматически принимать его опасно: можно затереть верное значение неверным. На практике такие расхождения обычно не молча игнорируют, а логируют для разбора человеком — но в саму dimension они не попадают. Type 0 — это утверждение «этот столбец менять нельзя», и ETL его соблюдает строго.
-- При обновлении клиента Type 0-столбцы НЕ участвуют в SET
UPDATE dim_customer
SET email = :new_email -- обновляем
-- date_of_birth НЕ трогаем: это Type 0
WHERE customer_key = :id;
История для Type 0 не нужна — её и нечего хранить, ведь значение по определению одно на всё время жизни сущности.
Type 0 — это про неизменность по природе, а не про лень. Не путайте «атрибут не меняется» с «нам сейчас некогда обрабатывать изменение». Если атрибут реально меняется (адрес, должность), а вы объявили его Type 0 — вы будете молча терять реальные изменения. Type 0 законен только когда изменение атрибута невозможно по смыслу.
Type 1 — Overwrite
SCD Type 1 означает: при изменении атрибута старое значение перезаписывается новым. Хранится одна строка на сущность, в ней — всегда актуальное значение. Предыдущих значений не остаётся.
-- Type 1: прямая перезапись
UPDATE dim_customer
SET email = '[email protected]'
WHERE customer_key = 7012;
До UPDATE:
customer_key | customer_name | email
--------------+---------------+----------------------
7012 | Анна Петрова | [email protected]
После UPDATE:
customer_key | customer_name | email
--------------+---------------+----------------------
7012 | Анна Петрова | [email protected]
Старого email больше нет нигде. customer_key не изменился — все fact-строки, ссылающиеся на 7012, теперь «видят» новый email. Именно это поведение мы в прошлом уроке наблюдали как проблему — но там атрибутом был регион, важный для исторической отчётности. Ключевая мысль этого урока: для других атрибутов ровно то же поведение — это именно то, что нужно.
Стоит проговорить механику Type 1 с точки зрения хранилища. Перезапись — это обычный UPDATE одной строки: СУБД находит строку по customer_key, заменяет значение столбца. Никаких новых строк, никаких служебных дат, никакого роста таблицы. Dimension остаётся ровно того же размера — одна строка на сущность. Это самый дешёвый из всех SCD-типов и по объёму, и по сложности запросов: чтобы узнать значение атрибута, достаточно прочитать единственную строку клиента, без всякой фильтрации по датам или флагам. Простота — реальное достоинство Type 1, а не «то, чем приходится довольствоваться».
Type 1 — это сознательный выбор дизайна, а не дефект
Важно зафиксировать правильную рамку. Type 1 — не «урезанный Type 2», не «компромисс из-за нехватки ресурсов» и не признак плохого дизайна. Для целого класса атрибутов перезапись — намеренное и правильное проектное решение.
Type 1 уместен, когда бизнес сознательно решает: история изменений этого атрибута не нужна, важно только текущее значение. Когда такое решение разумно?
Исправление ошибок. Имя клиента было введено с опечаткой: «Анна Петорва». Опечатку исправляют на «Анна Петрова». Хранить «историю» опечатки бессмысленно и даже вредно — это не факт о клиенте, а дефект ввода. Здесь перезапись (Type 1) — единственно правильный выбор; Type 2 завёл бы фальшивую «версию» клиента.
Атрибуты, где значима только актуальность. Текущий контактный email, текущий телефон, текущий аватар. Для большинства бизнес-задач важно лишь, как связаться с клиентом СЕЙЧАС. Прошлые email-адреса аналитике не нужны — хранить их историю значит усложнять dimension ради данных, которые никто не запросит.
Технические и служебные атрибуты. Дата последнего обновления строки, флаг «прошёл валидацию», служебные метки источника. Их историзировать не нужно — нужно текущее состояние. История значения служебной метки никому не интересна, она не описывает бизнес-сущность — она описывает технический процесс загрузки.
Атрибуты, где история избыточна для задач анализа. Иногда атрибут меняется, но ни один отчёт и ни одна метрика по его прошлым значениям не строятся. Например, ссылка на аватар клиента: она меняется, но «выручку по аватарам» никто не считает. Хранить историю аватаров — значит усложнять dimension ради данных, которые не будут запрошены. Type 1 здесь — экономия сложности без потери чего-либо ценного.
Решение «этот атрибут — Type 1» принимается так же серьёзно и осознанно, как решение «этот атрибут — Type 2». Вопрос не «можем ли мы себе позволить хранить историю», а «нужна ли история этого конкретного атрибута бизнесу для ответа на реальные вопросы». Если честный ответ — «нет», то Type 1 не компромисс, а правильный дизайн.
Цена Type 1: что нужно понимать осознанно
У Type 1 есть последствие, и грамотный инженер выбирает Type 1, понимая его. Поскольку старое значение исчезает, агрегаты, посчитанные по этому атрибуту, меняются задним числом.
Если регион клиента — Type 1, и клиент переехал из «Центра» в «Сибирь», то отчёт «продажи по регионам за прошлый январь», пересчитанный сегодня, отнесёт январские покупки уже к «Сибири». Вчерашняя и сегодняшняя версии одного и того же исторического отчёта разойдутся.
Для атрибута вроде email это не проблема: по email регионы продаж не считают. Для региона — проблема, и поэтому регион обычно делают Type 2. Вывод: цена Type 1 — невозможность воспроизвести исторический срез по этому атрибуту. Это приемлемо ровно тогда, когда такой срез никому не нужен.
Type 0 и Type 1 рядом
Оба типа дают одну строку на сущность и не хранят историю — но по разным причинам, и разница принципиальна.
| Аспект | Type 0 (retain) | Type 1 (overwrite) |
|---|---|---|
| Меняется ли атрибут | Нет, неизменен по природе | Да, но историю не хранят намеренно |
| Что делает ETL при новом значении | Игнорирует его | Перезаписывает старое |
| Строк на сущность | Одна | Одна |
| Когда уместен | Дата рождения, VIN, дата создания | Email, телефон, исправления опечаток |
| Почему истории нет | Её не существует | Сознательно решили не хранить |
Type 0 говорит: «менять нечего». Type 1 говорит: «менять есть что, но прошлое нам не нужно — нужно актуальное». Оба — полноправные, корректные решения каталога SCD.
Полезно зафиксировать, как принимать решение на практике. Идя по атрибутам dimension, для каждого задайте два вопроса подряд. Первый: «может ли этот атрибут вообще измениться?» Если нет — Type 0, дальше не думаем. Если да — второй вопрос: «нужна ли бизнесу история его изменений для каких-либо отчётов или анализа?» Если честный ответ «нет» — Type 1. Если «да» — это уже территория Type 2 и далее. Большинство атрибутов реальной dimension отвечают «нет» на второй вопрос: имена, контакты, мелкие технические признаки — для них Type 1 правильный выбор. Type 2 нужен меньшинству атрибутов — тем, чья история действительно используется. Не переоценивайте долю таких атрибутов: чрезмерное применение Type 2 — частая ошибка.
В следующем уроке перейдём к Type 2 — он, наоборот, хранит полную историю, и нужен для атрибутов вроде региона, где исторический срез критичен.
Попробуй сам
Дана dim_product: product_key, product_name, category, brand, weight_grams, manufacture_country, current_price.
- Для каждого атрибута определите Type 0 или Type 1 и обоснуйте. Подсказка: вес товара и страна производства физически зафиксированы при производстве; название и категория могут корректироваться.
- Напишите
UPDATE, исправляющий опечатку вproduct_name. Объясните, почему здесь правильна именно перезапись, а не создание новой версии. - Представьте, что
categoryсделали Type 1, а потом понадобился отчёт «выручка по категориям за прошлый год». Что с ним не так? Какой это аргумент — за или против Type 1 для category? - Сформулируйте словами: чем «атрибут не меняется» (Type 0) отличается от «атрибут меняется, но историю не храним намеренно» (Type 1).