Learning Platform
Глоссарий Troubleshooting
Урок 15.01 · 17 мин
Начальный
slowly-changing-dimensionsscdhistorydimension-design

Проблема: атрибуты 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 — это набор техник управления этим дрейфом.

Fact растёт быстро, dimension меняется медленно
Fact-таблицаРастёт быстро: каждое событие — новая строка. Миллионы строк в день. Старые строки неизменны.
DimensionМеняется медленно: клиент переезжает раз в годы, товар меняет категорию изредка. Редкие, но регулярные изменения атрибутов.

Что именно «ломается» при изменении

Разберём механику на примере Анны. Допустим, 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 переписал её для ВСЕХ покупок Анны сразу, прошлых и будущих.

WARNING

Корень проблемы: fact-строка ссылается на dimension по ключу, а не хранит копию атрибутов. Значит, любое изменение dimension мгновенно «переписывает прошлое» для всех связанных fact-строк. Без специального дизайна dimension аналитика теряет способность воспроизвести исторический отчёт.


Два разных вопроса к данным

Здесь важно увидеть, что у бизнеса есть два РАЗНЫХ законных вопроса, и они требуют разных ответов.

Вопрос «как было» (as-was, historical truth). «Сколько мы продали в регионе Центр в январе?» Здесь правильный ответ — приписать январскую покупку Анны к «Центру», потому что в январе она там и была. Этот вопрос требует хранения истории.

**Вопрос «как есть» (as-is, current truth).» «Сколько всего за всё время купили клиенты, которые СЕЙЧАС живут в Сибири?» Здесь правильно отнести все покупки Анны (и январь, и апрель) к «Сибири», потому что вопрос про текущую принадлежность. Этот вопрос требует текущего значения.

Оба вопроса осмысленны. Маркетингу для ретро-анализа нужен as-was. Тому же маркетингу для планирования по текущим сегментам нужен as-is. Хорошая модель dimension должна уметь отвечать на тот вопрос, который реально задаёт бизнес, — а иногда и на оба сразу.

Чтобы почувствовать, насколько это не схоластика, представьте конкретный спор в компании. Финансовый отдел сформировал годовой отчёт по выручке регионов в январе. В мае аналитик переоткрывает тот же отчёт — и числа другие, потому что часть клиентов за это время сменила регион. Финансист в ярости: «отчёт за закрытый год не может меняться». Аналитик разводит руками: «я просто запустил тот же запрос». Оба правы — и оба стали жертвой того, что в dimension не была заранее продумана обработка изменений. Если бы регион был спроектирован под as-was (с хранением истории), отчёт воспроизводился бы стабильно. Выбор SCD-типа — это, по сути, заранее принятое решение о том, какой из двух конфликтующих ответов система будет давать.

Один переезд — два правильных ответа
Событие: Анна переехала Москва -> НовосибирскВ марте атрибут region клиента изменился с 'Центр' на 'Сибирь'.
as-was: январь -> ЦентрВопрос 'как было': исторический отчёт должен показывать регион, актуальный на момент события. Требует хранения истории.
as-is: всё -> СибирьВопрос 'как есть': анализ по текущим сегментам относит все события к текущему значению атрибута.

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 4Mini-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 с этим вопросом к каждому.

NOTE

Распространённое заблуждение: «Type 2 — всегда правильный выбор, ведь он хранит всю историю». Нет. Type 2 усложняет dimension и запросы; для атрибута, у которого важно только текущее значение, он избыточен. Тип подбирают под конкретный атрибут и под конкретный вопрос бизнеса, а не «по максимуму на всякий случай».

SCD Type 2 автоматически в dbt — механика snapshot

Попробуй сам

Возьмите dimension dim_employee с атрибутами: дата рождения, ФИО, отдел, должность, рабочий email, размер оклада.

  1. Для каждого атрибута решите: меняется он вообще или нет? Если меняется — важна ли история изменений для бизнеса?
  2. Сформулируйте по одному as-was и одному as-is вопросу про атрибут «отдел». Например, as-was: «сколько продал отдел Продаж в 2025?»; as-is: ваш вариант.
  3. Представьте: сотрудник перешёл из отдела A в отдел B. Если бы dim_employee была наивной (одна строка, перезапись), какой исторический отчёт сломался бы?
  4. Предположите для каждого атрибута, какой SCD-тип из таблицы выше подойдёт. Точную механику разберём в следующих уроках — пока важна интуиция.

Проверка знанийKnowledge check
Почему изменение атрибута в dimension — нетривиальная задача, и в чём разница между вопросами "as-was" и "as-is" к данным?
ОтветAnswer
Изменение атрибута dimension нетривиально, потому что fact-строка ссылается на dimension по ключу, а не хранит копию атрибутов. Значит, простая перезапись атрибута (UPDATE) мгновенно меняет его для ВСЕХ связанных fact-строк сразу — и будущих, и уже существующих. В результате отчёт за уже закрытый период меняется задним числом: например, январская покупка клиента, переехавшего в марте, начинает приписываться к новому региону, хотя в январе клиент был в старом. Аналитика теряет способность воспроизвести исторический отчёт. У бизнеса при этом есть два разных законных вопроса. As-was (историческая правда, "как было") — событие должно быть отнесено к значению атрибута, актуальному на момент события: январская покупка к региону, где клиент был в январе. Этот вопрос требует хранения истории. As-is (текущая правда, "как есть") — все события сущности относятся к её текущему значению атрибута: все покупки клиента к региону, где он живёт сейчас, для анализа по актуальным сегментам. Оба вопроса осмысленны и нужны бизнесу. Именно поэтому существует набор SCD-типов (от 0 до 7) — каталог инженерных решений, и тип выбирается per-attribute, под конкретный вопрос, который бизнес задаёт к конкретному атрибуту.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Почему dimensions называют 'медленно меняющимися' (slowly changing) в противоположность fact-таблицам?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5