Learning Platform
Глоссарий Troubleshooting
Урок 14.01 · 18 мин
Начальный
conformed-dimensionsbus-matrixkimballdrill-across

Conformed dimensions и шина (bus matrix)

Представьте крупную компанию: продажи, доставка, поддержка, возвраты — у каждого процесса есть своя fact-таблица. Аналитик задаёт вопрос: «дайте выручку и количество обращений в поддержку по одним и тем же регионам за один и тот же месяц». Если у fct_sales своя таблица регионов, а у fct_support — своя, ответить честно нельзя: в одной таблице регион называется Сев-Запад, в другой Северо-Западный ФО, коды разные, границы регионов обновлялись в разное время. Числа из двух отчётов невозможно положить рядом.

Conformed dimension решает ровно эту проблему. Это dimension с идентичным смыслом и идентичным содержимым, переиспользуемая несколькими fact-таблицами. Одна dim_region, одна dim_date, одна dim_product — на весь warehouse. Тогда «регион» в отчёте по продажам и «регион» в отчёте по поддержке — это буквально одни и те же строки с одними и теми же ключами. Числа становятся сравнимыми.


Что значит «conformed» технически

Dimension считается conformed между двумя fact-таблицами, если выполняется одно из двух условий. Первое: dimension физически одна и та же таблица, и обе fact-таблицы ссылаются на неё своими foreign keys. Второе, более слабое: dimensions разные таблицы, но одна является строгим подмножеством другой по строкам и атрибутам, и общие атрибуты имеют одинаковые имена и значения. Второй случай называют conformed rollup — например, dim_month есть агрегированная версия dim_date, и месяц в обеих таблицах означает одно и то же.

Главное требование — согласованность ключей и значений. Если в dim_product товар с product_key = 4071 это «Кофе молотый 250г», то этот же ключ обязан означать тот же товар во всех fact-таблицах. Тогда JOIN по product_key корректен независимо от того, к какой fact-таблице мы присоединяем dimension.

Здесь важно понять, почему conformed dimension должна быть именно одной физической таблицей или строгим подмножеством, а не «двумя похожими справочниками». Похожесть — это не согласованность. Два справочника товаров, заведённых разными командами, почти наверняка разойдутся: один обновит категорию товара в марте, другой — в мае; один напишет «Кофе», другой «Кофе молотый»; один присвоит товару ключ 4071, другой 9203. Каждое такое расхождение делает JOIN по этим dimensions либо неполным, либо ошибочным. Единственный способ гарантировать, что «товар» в одной fact-таблице и «товар» в другой — это буквально одно и то же, — иметь под ними одну общую таблицу с одним набором ключей. Conformed dimension — это не «договорённость о схожем содержимом», а физическое единство справочника.

Из этого следует и практическое правило обновления. Раз conformed dimension одна, у неё должен быть один владелец — одна команда или один ETL-процесс, отвечающий за её наполнение и изменения. Если каждая команда, использующая dimension, начнёт вносить в неё свои правки независимо, согласованность снова разрушится. Conformed dimension живёт по принципу «один источник изменений — много потребителей».

Одна conformed dimension — две fact-таблицы
fct_salesFact-таблица продаж: grain — одна строка на позицию в чеке. Содержит sale_amount, quantity и foreign keys на dimensions.
fct_returnsFact-таблица возвратов: grain — одна строка на возвращённую позицию. Содержит return_amount и те же foreign keys.
product_key
product_key
dim_productОдна физическая dimension-таблица. Обе fact-таблицы ссылаются на неё. Один product_key означает один и тот же товар везде.

Эффект: можно посчитать «чистые продажи = продажи минус возвраты по каждой категории товаров» и быть уверенным, что категория товара в обоих слагаемых определена одинаково.


Drill-across: зачем это всё нужно

Операция, ради которой существуют conformed dimensions, называется drill-across — анализ, объединяющий measures из нескольких fact-таблиц по общим dimensions. Drill-across нельзя делать одним JOIN-ом fact-таблиц напрямую: у них разный grain, и соединение по dimension-ключам размножит строки (декартово раздувание по пересекающимся ключам).

Правильный приём — двухфазный. Сначала каждую fact-таблицу агрегируют отдельно до общего набора dimensions. Потом два уже агрегированных результата соединяют по этим dimensions.

-- Drill-across: выручка и сумма возвратов по категории и месяцу
WITH sales AS (
    SELECT p.category, d.year_month,
           SUM(s.sale_amount) AS revenue
    FROM fct_sales s
    JOIN dim_product p ON p.product_key = s.product_key
    JOIN dim_date d    ON d.date_key    = s.date_key
    GROUP BY p.category, d.year_month
),
returns AS (
    SELECT p.category, d.year_month,
           SUM(r.return_amount) AS returned
    FROM fct_returns r
    JOIN dim_product p ON p.product_key = r.product_key
    JOIN dim_date d    ON d.date_key    = r.date_key
    GROUP BY p.category, d.year_month
)
SELECT s.category, s.year_month,
       s.revenue,
       COALESCE(r.returned, 0)               AS returned,
       s.revenue - COALESCE(r.returned, 0)   AS net_revenue
FROM sales s
LEFT JOIN returns r
       ON r.category = s.category
      AND r.year_month = s.year_month
ORDER BY s.category, s.year_month;
 category   | year_month | revenue  | returned | net_revenue
------------+------------+----------+----------+------------
 Кофе       | 2026-01    | 412300.0 |  18400.0 |   393900.0
 Кофе       | 2026-02    | 388100.0 |  12050.0 |   376050.0
 Чай        | 2026-01    | 207600.0 |   4300.0 |   203300.0
 Чай        | 2026-02    | 221900.0 |   6800.0 |   215100.0

Этот запрос корректен только потому, что dim_product и dim_date — conformed. category и year_month берутся из одних и тех же dimension-таблиц, поэтому строки sales и returns соединяются точно. Если бы у возвратов была отдельная таблица товаров со своими категориями, JOIN по category либо потерял бы строки (несовпадение названий), либо склеил неправильные.

Разберём, почему именно две фазы. В первой фазе каждая fact-таблица сворачивается до общего «зерна анализа» — здесь это пара (категория, месяц). После свёртки sales содержит ровно одну строку на каждую пару, и returns — тоже одну. Во второй фазе мы соединяем два набора, в каждом из которых ключ соединения уникален. Соединение «один к одному» по (категория, месяц) не может ничего размножить — это ключевое свойство, дающее корректность. Если же соединять fact-таблицы напрямую, до свёртки, то одной строке продаж соответствовало бы много строк возвратов с тем же товаром и датой (и наоборот), и SUM посчитал бы каждую сумму многократно. Именно поэтому drill-across — это всегда «сначала агрегировать каждую таблицу по отдельности, потом соединить агрегаты».

LEFT JOIN во второй фазе выбран намеренно: товар мог продаваться, но ни разу не возвращаться. При INNER JOIN такие категории выпали бы из отчёта целиком — мы потеряли бы продажи товаров с нулевыми возвратами. LEFT JOIN сохраняет все строки продаж, а COALESCE(r.returned, 0) подставляет ноль там, где возвратов не было. Выбор типа соединения в drill-across — это содержательное решение, а не формальность: оно определяет, какие строки попадут в итог.

TIP

Запомните правило: fact-таблицы между собой напрямую НЕ соединяют. Объединение разных бизнес-процессов всегда идёт через conformed dimensions и двухфазную агрегацию. Это и есть drill-across.


Bus matrix: карта enterprise DWH

Когда conformed dimensions нужно спланировать на весь warehouse заранее, Kimball предлагает инструмент — bus matrix (матрица шины). Это таблица: строки — бизнес-процессы (будущие fact-таблицы), столбцы — conformed dimensions. На пересечении стоит отметка, если данная dimension применима к данному процессу.

Название «шина» (bus) — из электроники: общая шина, к которой подключаются разные устройства. Здесь общая шина — это набор conformed dimensions, а «устройства» — fact-таблицы, которые к ним подключаются.

Бизнес-процессdim_datedim_productdim_customerdim_storedim_employee
Продажи[x][x][x][x][x]
Возвраты[x][x][x][x][x]
Поставки[x][x][x]
Обращения в поддержку[x][x][x][x]
Маркетинг-кампании[x][x]

Читать матрицу можно двумя способами. По строке — какие dimensions нужны конкретной fact-таблице (продажам нужны все пять). По столбцу — сколько процессов разделяют одну dimension. dim_date стоит в каждой строке: это самая «conformed» dimension в любом warehouse, её строит и переиспользует весь проект. dim_product встречается почти везде — значит, спроектировать её надо особенно тщательно, цена ошибки умножается на число процессов.

Bus matrix как план: процессы подключаются к общей шине dimensions
Шина conformed dimensionsОбщий набор согласованных dimensions: dim_date, dim_product, dim_customer, dim_store. Проектируется один раз на весь warehouse.
подключение через FK
fct_salesПодключается к date, product, customer, store одновременно.
fct_returnsПодключается к тем же dimensions — потому числа сравнимы с продажами.
fct_shipmentsПодключается к date, product, store. Customer не нужен.

Почему bus matrix экономит проект

Bus matrix — это не документация ради документации, а способ избежать самой дорогой ошибки в warehouse: несогласованных dimensions. Без него каждая команда строит свою fact-таблицу со своими справочниками, и через год выясняется, что десять разных таблиц «клиентов» нельзя согласовать.

Bus matrix даёт три практических выигрыша. Первый — порядок работ: dimensions с большим числом отметок в столбце строят первыми, потому что от них зависит больше всего. Второй — параллельность: разные команды могут одновременно делать fct_sales и fct_support, договорившись о наборе общих dimensions заранее; интеграция произойдёт автоматически, потому что ключи совпадут. Третий — incremental-доставка: warehouse не нужно строить целиком, можно выпустить одну fact-таблицу, потом вторую — и они всё равно состыкуются через шину.

Стоит подчеркнуть, как bus matrix меняет сам характер работы над warehouse. Без неё построение хранилища — это монолитный проект: пока не готово всё, не работает ничего, а интеграцию приходится «доделывать» в конце, когда выясняется, что справочники не сходятся. С bus matrix построение становится потоком независимых задач. Каждая fact-таблица — отдельная поставка, и поскольку набор conformed dimensions зафиксирован заранее как контракт, новая fact-таблица гарантированно встраивается в уже существующее хранилище без переделок. Это превращает рискованный большой проект в управляемую серию мелких.

Полезно также понимать, что bus matrix — живой документ, а не разовый артефакт. По мере роста компании появляются новые бизнес-процессы (новые строки) и иногда новые conformed dimensions (новые столбцы). Матрицу дополняют, но уже введённые conformed dimensions стараются не менять — на них завязаны все существующие fact-таблицы. Поэтому первые conformed dimensions проектируют с запасом: лучше сразу заложить в dim_customer атрибуты, которые понадобятся будущим процессам, чем потом перестраивать dimension, от которой зависит десяток fact-таблиц.

WARNING

Самая частая ошибка джуниора — начать строить fact-таблицы, не нарисовав bus matrix. Через несколько месяцев в warehouse оказывается три dim_customer с разными ключами, и drill-across между процессами становится невозможен без болезненной миграции. Матрица рисуется до первой fact-таблицы.

Bus matrix на практике — как dbt-проект отражает conformed dimensions

Conformed dimension — это контракт. Подписав его (внеся dimension в шину), команда обязуется: ключи стабильны, значения согласованы, изменения координируются. Цена — дисциплина. Выигрыш — warehouse, где любые два процесса можно честно сравнить.


Попробуй сам

Возьмите три бизнес-процесса знакомой вам области — например, для интернет-магазина это «оформление заказа», «доставка», «отзыв на товар». Для каждого:

  1. Выпишите, какая одна строка fact-таблицы (grain) представляет этот процесс.
  2. Перечислите dimensions, которые описывают эту строку (по кому, по чему, когда, где).
  3. Сведите всё в bus matrix: процессы по строкам, dimensions по столбцам, отметки на пересечениях.
  4. Найдите dimension с наибольшим числом отметок в столбце — это кандидат строить первым. Найдите dimension, которая встречается лишь раз — подумайте, не станет ли она conformed позже, когда добавится четвёртый процесс.

Проверьте себя: если две fact-таблицы делят dimension, смогли бы вы написать drill-across запрос по ней? Если нет — dimension ещё не conformed.


Проверка знанийKnowledge check
Почему для анализа, объединяющего measures из двух fact-таблиц (drill-across), нельзя просто соединить эти fact-таблицы одним JOIN, и при чём здесь conformed dimensions?
ОтветAnswer
Две fact-таблицы обычно имеют разный grain (одна строка означает разные вещи), поэтому прямой JOIN по общим dimension-ключам размножает строки — возникает декартово раздувание по пересекающимся ключам, и measures суммируются неверно. Правильный приём — двухфазный: каждую fact-таблицу сначала независимо агрегируют до общего набора dimensions, а уже агрегированные результаты соединяют по этим dimensions. Этот приём работает корректно только если dimensions conformed — то есть имеют идентичный смысл, идентичные ключи и идентичные значения для обеих fact-таблиц. Тогда, например, "категория товара" в агрегате продаж и в агрегате возвратов — это буквально одни и те же значения, и строки соединяются точно. Если бы каждая fact-таблица имела свой справочник товаров, соединение по категории либо теряло бы строки из-за несовпадения названий, либо склеивало неправильные. Conformed dimensions — это и есть то, что делает drill-across возможным и достоверным.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Что делает dimension conformed между двумя fact-таблицами?

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

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

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

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