Проблема: атрибуты dimension меняются во времени
Клиент Анна жила в Москве и относилась к региону «Центр». В январе она купила товаров на 50 000 рублей. В марте Анна переехала в Новосибирск — теперь её регион «Сибирь». В апреле она купила ещё на 30 000.
Простой вопрос: на какой регион записать январскую покупку Анны? Если на «Сибирь» (текущий регион), то отчёт «продажи по регионам за январь» соврёт — в январе Анна физически покупала, будучи в «Центре». Если оставить «Центр», нужно где-то помнить, что когда-то регион был другим. А если в мае Анна переедет ещё раз?
Это и есть проблема slowly changing dimensions (медленно меняющихся измерений, SCD). Атрибуты сущностей в dimensions со временем меняются — и от того, как мы обработаем это изменение, зависит, будут ли исторические отчёты говорить правду. Эта тема — одна из самых практически важных во всём размерном моделировании: неправильно выбранная стратегия SCD означает либо потерю нужной истории, либо лавину лишних данных, и исправлять такую ошибку в работающем warehouse дорого.
Почему «медленно меняющиеся»
Термин ввёл Ralph Kimball. Слово «медленно» противопоставляет dimensions fact-таблицам.
Fact-таблица растёт быстро: каждое событие — новая строка, миллионы строк в день, и старые строки обычно не меняются (событие случилось — оно неизменно). Dimension меняется медленно: клиент переезжает раз в несколько лет, товар меняет категорию изредка, название отдела — ещё реже. Изменения dimension — события не частые, но регулярные, и игнорировать их нельзя.
«Медленно» не значит «неважно». Наоборот: именно потому, что изменения редки, их легко упустить при проектировании — а потом обнаружить, что исторические отчёты врут.
Стоит сразу отделить SCD от другого вида изменений dimension — от роста числа сущностей. Когда в компании появляется новый клиент, в dim_customer просто добавляется новая строка — это не «изменение dimension» в смысле SCD, это обычное пополнение. SCD — это про изменение АТРИБУТОВ уже существующей сущности: тот же клиент, но у него поменялся регион, сегмент, фамилия. Добавление новых сущностей тривиально и проблем не создаёт. Изменение атрибутов существующих — вот что требует специального дизайна, и именно об этом весь модуль.
Ещё одно важное уточнение: SCD касается dimensions, а не fact-таблиц. Строка fact-таблицы фиксирует свершившееся событие — продажа на такую-то сумму в такой-то день. Событие неизменно: оно случилось так, как случилось. Поэтому fact-строки, как правило, не обновляются (исключение — accumulating snapshot, где строка процесса дополняется по мере прохождения вех). Меняются именно описательные атрибуты сущностей в dimensions — клиент, товар, магазин живут во времени и их характеристики дрейфуют. SCD — это набор техник управления этим дрейфом.
Что именно «ломается» при изменении
Разберём механику на примере Анны. Допустим, dim_customer устроена просто — одна строка на клиента:
dim_customer (наивный вариант, одна строка на клиента)
customer_key | customer_name | city | region
--------------+---------------+-------------+--------
7012 | Анна Петрова | Москва | Центр
fct_sales ссылается на клиента через customer_key. Январская покупка Анны — строка в fct_sales с customer_key = 7012. Апрельская покупка — тоже customer_key = 7012.
Теперь Анна переехала. У нас есть один очевидный способ обновить dimension — перезаписать city и region прямо в строке 7012:
UPDATE dim_customer
SET city = 'Новосибирск', region = 'Сибирь'
WHERE customer_key = 7012;
После этого UPDATE строка 7012 говорит: регион Анны — «Сибирь». И теперь смотрим, что стало с январским отчётом:
-- "Продажи по регионам за январь"
SELECT c.region, SUM(s.sale_amount) AS revenue
FROM fct_sales s
JOIN dim_customer c ON c.customer_key = s.customer_key
WHERE s.date_key BETWEEN 20260101 AND 20260131
GROUP BY c.region;
region | revenue
---------+--------
Сибирь | 50000.0 <- ОШИБКА: в январе Анна жила в "Центре"
Январская покупка приписалась к «Сибири», хотя в январе Анна была в «Центре». Отчёт за уже закрытый месяц изменился задним числом и стал неверным. Это происходит, потому что fct_sales хранит только customer_key, а вся информация о регионе живёт в dimension — и UPDATE переписал её для ВСЕХ покупок Анны сразу, прошлых и будущих.
Корень проблемы: fact-строка ссылается на dimension по ключу, а не хранит копию атрибутов. Значит, любое изменение dimension мгновенно «переписывает прошлое» для всех связанных fact-строк. Без специального дизайна dimension аналитика теряет способность воспроизвести исторический отчёт.
Два разных вопроса к данным
Здесь важно увидеть, что у бизнеса есть два РАЗНЫХ законных вопроса, и они требуют разных ответов.
Вопрос «как было» (as-was, historical truth). «Сколько мы продали в регионе Центр в январе?» Здесь правильный ответ — приписать январскую покупку Анны к «Центру», потому что в январе она там и была. Этот вопрос требует хранения истории.
**Вопрос «как есть» (as-is, current truth).» «Сколько всего за всё время купили клиенты, которые СЕЙЧАС живут в Сибири?» Здесь правильно отнести все покупки Анны (и январь, и апрель) к «Сибири», потому что вопрос про текущую принадлежность. Этот вопрос требует текущего значения.
Оба вопроса осмысленны. Маркетингу для ретро-анализа нужен as-was. Тому же маркетингу для планирования по текущим сегментам нужен as-is. Хорошая модель dimension должна уметь отвечать на тот вопрос, который реально задаёт бизнес, — а иногда и на оба сразу.
Чтобы почувствовать, насколько это не схоластика, представьте конкретный спор в компании. Финансовый отдел сформировал годовой отчёт по выручке регионов в январе. В мае аналитик переоткрывает тот же отчёт — и числа другие, потому что часть клиентов за это время сменила регион. Финансист в ярости: «отчёт за закрытый год не может меняться». Аналитик разводит руками: «я просто запустил тот же запрос». Оба правы — и оба стали жертвой того, что в dimension не была заранее продумана обработка изменений. Если бы регион был спроектирован под as-was (с хранением истории), отчёт воспроизводился бы стабильно. Выбор SCD-типа — это, по сути, заранее принятое решение о том, какой из двух конфликтующих ответов система будет давать.
SCD-типы: каталог инженерных решений
Kimball формализовал набор стандартных техник обработки изменений dimension — SCD types, от Type 0 до Type 7. Это не «уровни качества», где больше значит лучше. Это каталог решений, каждое со своим компромиссом между хранением истории, простотой и стоимостью.
Откуда взялась эта нумерация и почему типов так много? Базовые типы 0, 1, 2 ввёл Ralph Kimball ещё в первых изданиях своей книги — это три фундаментальных ответа: «не меняем», «перезаписываем», «добавляем строку». Со временем практика показала, что бизнесу нужны промежуточные и комбинированные варианты, и каталог дополнили типами 3, 4, 5, 6, 7. Высокие номера — это не «более продвинутые» версии низких, а именно отдельные техники и их комбинации под конкретные потребности. Не нужно стремиться «дорасти» до Type 7 — нужно для каждого атрибута выбрать подходящий тип, и очень часто это будет Type 1 или Type 2.
Беглый обзор того, что предстоит изучить в модуле:
| Тип | Краткая суть | Хранит историю |
|---|---|---|
| Type 0 | Атрибут никогда не меняется (retain) | не нужна |
| Type 1 | Перезапись значения (overwrite) | нет, только текущее |
| Type 2 | Новая строка на каждое изменение (add row) | да, полную |
| Type 3 | Новый столбец «предыдущее значение» (add column) | одно прошлое значение |
| Type 4 | Mini-dimension / отдельная history-таблица | да, отдельно |
| Type 5, 6, 7 | Гибриды и bi-temporal моделирование | да, по-разному |
Главная мысль на старте модуля: выбор SCD-типа делается per-attribute — для каждого атрибута отдельно, исходя из того, какой вопрос бизнес задаёт именно к этому атрибуту. Дата рождения клиента не меняется — это Type 0. Адрес электронной почты, возможно, достаточно хранить текущим — Type 1. История смены региона критична для отчётности — Type 2. В одной dimension dim_customer спокойно сосуществуют атрибуты разных SCD-типов.
Это «per-attribute» — действительно важный принцип, и новички часто его пропускают, выбирая один тип на всю dimension. Так делать не нужно. Реальная dim_customer — это смесь: дата рождения по правилам Type 0, рабочий email по правилам Type 1, регион по правилам Type 2. Тип определяет не «уровень крутизны dimension», а конкретный ответ на конкретный вопрос: «нужна ли бизнесу история изменений ИМЕННО ЭТОГО атрибута». Где нужна — Type 2. Где важно только текущее значение — Type 1. Где атрибут вообще не меняется — Type 0. Проектирование SCD — это, по сути, проход по списку атрибутов dimension с этим вопросом к каждому.
Распространённое заблуждение: «Type 2 — всегда правильный выбор, ведь он хранит всю историю». Нет. Type 2 усложняет dimension и запросы; для атрибута, у которого важно только текущее значение, он избыточен. Тип подбирают под конкретный атрибут и под конкретный вопрос бизнеса, а не «по максимуму на всякий случай».
Попробуй сам
Возьмите dimension dim_employee с атрибутами: дата рождения, ФИО, отдел, должность, рабочий email, размер оклада.
- Для каждого атрибута решите: меняется он вообще или нет? Если меняется — важна ли история изменений для бизнеса?
- Сформулируйте по одному as-was и одному as-is вопросу про атрибут «отдел». Например, as-was: «сколько продал отдел Продаж в 2025?»; as-is: ваш вариант.
- Представьте: сотрудник перешёл из отдела A в отдел B. Если бы dim_employee была наивной (одна строка, перезапись), какой исторический отчёт сломался бы?
- Предположите для каждого атрибута, какой SCD-тип из таблицы выше подойдёт. Точную механику разберём в следующих уроках — пока важна интуиция.