Skip to content
PПромтбук
RUEN
07Моделирование

Slowly Changing Dimensions: Type 1/2/3/6 без боли

Когда что выбирать, что считается «изменением», как влияет на запросы и размер БД, и как мигрировать без переписывания витрин.

Спроектируй стратегию SCD для измерения {{dimension}}. Частота изменений: {{change_rate}}. Зачем история: {{history_need}}.

Шаг 1. Классифицируй каждый атрибут

Не «у измерения SCD-тип X». У каждого атрибута свой тип.

| Атрибут | Меняется | Нужна история? | Тип | | name | редко | нет | 1 | | segment | месячно | да, для отчётов | 2 | | email | редко | только «текущий + предыдущий» | 3 | | address | средне | да + «текущий» рядом | 6 |

Шаг 2. Выбери тип

Type 1 — overwrite. UPDATE на месте. История теряется.

  • Когда: исправление опечатки, атрибут не интересен исторически (имя клиента).
  • Минус: отчёты задним числом «переписывают» прошлое.

Type 2 — versioned rows. Новая строка при изменении.

  • Структура: surrogate_key, natural_key, attr..., valid_from, valid_to, is_current.
  • Запросы по «как было на дату»: JOIN fact ON dim.surrogate_key = fact.dim_key (исторический FK) или WHERE event_date BETWEEN valid_from AND valid_to.
  • Минус: измерение растёт. Для 10M customers с месячным изменением — 120M строк за год.

Type 3 — prior value column. segment_current, segment_previous, segment_changed_at.

  • Когда: интересна только последняя смена.
  • Минус: не работает для трёх и более состояний.

Type 6 — гибрид (1+2+3). Type 2 строки + Type 1 «вечнотекущая» колонка + Type 3 «previous».

  • Запросы «как сейчас» и «как было тогда» в одной таблице.
  • По умолчанию для важных бизнес-измерений.

Шаг 3. Сурогатные ключи

Type 2 ОБЯЗАТЕЛЬНО на сурогатных ключах. Факт ссылается на сурогатный, не на natural:

  • fact_orders.customer_key = 12345 (конкретная версия)
  • НЕ fact_orders.customer_id = 'C-9821' (вечный)

Natural key сохраняй в dim для join'ов «дай мне ВСЕ версии этого клиента».

Шаг 4. Эффект на запросы

«Сколько заказов от Premium-клиентов в Q3 2025?»

  • Type 1: соврёт — кто сейчас Premium, тот всегда был.
  • Type 2: правда — JOIN по surrogate, segment === 'Premium' на момент заказа.

Это решение принимай ДО построения витрин. Перевод Type 1 → Type 2 — болезненная переделка.

Шаг 5. Мониторинг

  • Размер: COUNT(*) GROUP BY natural_key — нет ли клиента с 500 версий из-за бага в источнике (false changes).
  • Целостность: SUM(CASE WHEN is_current THEN 1 ELSE 0 END) GROUP BY natural_key — должно быть ровно 1.
  • Дыры: valid_to - LEAD(valid_from) — нет ли пропусков во времени.

Anti-patterns

  • ❌ Type 2 на всём измерении «на всякий случай» — таблица раздуется на 10× без бизнес-ценности
  • ❌ Type 1 для атрибутов, по которым считают историю (segment, plan_tier) — отчёты задним числом сломаются
  • ❌ Сравнивать строки по hash всей строки для детекции изменения — поле updated_at источника поменяется без реального изменения, получишь дубликаты
  • valid_to = NULL для текущей записи — индексы и JOIN'ы ломаются; используй 9999-12-31
  • ❌ Факт ссылается на natural_key — теряешь смысл Type 2, JOIN всегда возвращает «как сейчас»
К подразделу «Моделирование»
Похожие промты