deшка
Урок 6.4

Звезда, снежинка и созвездие: продажи магазина

Факт продажи, описания товара, иерархия категорий и несколько процессов на одном примере.

Моделирование измерений организует данные вокруг процесса, который считают, и описаний, по которым его группируют. На трёх позициях чека сравним звезду, снежинку и созвездие.

Что хранит факт

Выберем зерно: одна строка — одна позиция в чеке. Тогда сумма строки зависит от количества и цены именно этой позиции. Номер чека повторится у двух позиций чека 701 — это нормально; ключом факта служит line_id или пара (receipt_id, line_no).

line_idreceipt_iddate_keyproduct_keystore_keyqtyamount_rub
1701202609011152300
2701202609011251250
3702202609021151150

Это таблица фактов fact_sale_line. Ключи задают контекст, меры — количество и сумму. product_key служит ссылкой на описание товара, qty и amount_rub участвуют в расчётах.

Измерения расшифровывают ключи: какой товар купили, в каком магазине, в какой день. Дата продажи и дата возврата могут ссылаться на один календарь, но играют разные роли. Описания нужны для фильтрации и группировки, а не для арифметики по их кодам.

Звезда: короткий путь к описанию

В звезде факт находится в центре, а каждое измерение присоединяется к нему напрямую. Например, dim_product сразу содержит название товара, категорию и отдел:

product_keyskuтоваркатегорияотдел
11TEA-01ЧайНапиткиПродукты
12COF-01КофеНапиткиПродукты

dim_store с ключом 5 содержит магазин «Центральный», город Казань. dim_date расшифровывает date_key в календарную дату, неделю и месяц. Теперь запрос «сумма по категориям» соединяет факт с dim_product. Для наших строк категория «Напитки» даст 300 + 250 + 150 = 700 ₽.

Звезда с позициями продаж и описаниями товара, магазина и даты

WITH fact_sale_line(product_key, amount_rub) AS (
    VALUES (11, 300), (12, 250), (11, 150)
), dim_product(product_key, category_name) AS (
    VALUES (11, 'Напитки'), (12, 'Напитки')
)
SELECT p.category_name, SUM(f.amount_rub) AS sales_rub
FROM fact_sale_line AS f
JOIN dim_product AS p ON p.product_key = f.product_key
GROUP BY p.category_name;

Для каждого product_key должна находиться ровно одна подходящая строка измерения. Если у чая две исторические версии описания и факт соединяют только по sku, строки продажи размножатся и сумма будет неверной. Историю измерений разберём в SCD; пока достаточно понимать, зачем факт ссылается на конкретный ключ версии.

Плюс звезды — ясный интерфейс для аналитика: атрибуты товара собраны в одном месте, путь запроса короткий. Цена — название категории «Напитки» повторяется у многих товаров. Это сознательная денормализация для чтения. Она требует управляемого обновления описаний, зато пользователю не нужно всякий раз восстанавливать цепочку товар → категория → отдел.

Снежинка: иерархия вынесена отдельно

В снежинке часть измерения нормализована. dim_product хранит товар и category_key, dim_category — название категории и department_key, dim_department — отдел. Факт продажи остаётся тем же: одна позиция чека и те же 700 ₽. Меняется только путь к описанию.

ТаблицаПример строки
dim_product11, TEA-01, «Чай», category_key=3
dim_category3, «Напитки», department_key=2
dim_department2, «Продукты»

Та же продажа в снежинке: описание товара проходит через категорию и отдел

Снежинка добавляет JOIN с категорией. Отдельная таблица полезна, когда категория имеет собственные атрибуты, права или используется несколькими измерениями. В BI такой путь сложнее; стоимость чтения и сопровождения сравнивают для конкретных запросов.

Запрос по тем же строкам добавляет одно соединение:

WITH fact_sale_line(product_key, amount_rub) AS (
    VALUES (11, 300), (12, 250), (11, 150)
), dim_product(product_key, category_key) AS (
    VALUES (11, 3), (12, 3)
), dim_category(category_key, category_name) AS (
    VALUES (3, 'Напитки')
)
SELECT c.category_name, SUM(f.amount_rub) AS sales_rub
FROM fact_sale_line AS f
JOIN dim_product AS p ON p.product_key = f.product_key
JOIN dim_category AS c ON c.category_key = p.category_key
GROUP BY c.category_name;

Оба запроса возвращают «Напитки — 700 ₽». Каждая строка факта должна находить одно описание товара и одну категорию.

Для исторического отчёта версии описаний нужны в обеих структурах. В снежинке согласуют версии всей цепочки товар → категория → отдел.

Созвездие: несколько процессов на общих измерениях

Продажа и возврат — разные процессы. В fact_sale_line одна строка означает позицию чека продажи; в fact_return_line — позицию возврата с датой возврата и суммой. Обе таблицы могут использовать согласованное измерение товара и магазина. Несколько фактов вокруг общих измерений образуют созвездие.

Допустим, по чаю были две строки продаж: 300 ₽ и 150 ₽, а затем один возврат на 150 ₽. Если соединить каждый факт продажи с возвратом только по product_key, строка возврата повторится дважды и получится возврат 300 ₽. Правильнее сначала агрегировать продажи и возвраты каждый на своём зерне до сравнимого разреза — например, товар и месяц, — а потом сопоставить полученные итоги. Сумма продаж чая 450 ₽, сумма возврата 150 ₽. Если отчёт называется «чистые продажи», нужно отдельно решить, относить ли возврат к месяцу продажи или к месяцу фактического возврата.

Общее измерение не означает одинаковую роль даты или одинаковую частоту строк. Согласованный справочник товара позволяет сравнить процессы; он не разрешает прямой JOIN двух больших фактов без определения зерна результата.

Когда какую форму выбрать

ВопросЗвездаСнежинка
Где категория товара?В строке dim_productВ отдельной dim_category
Сколько переходов до неё?Факт → товарФакт → товар → категория
Где поддерживать общее описание?В затронутых строках измеренияВ отдельной таблице категории
Что с историей?Нужна версия измерения или другое явное правилоНужно согласовать версии всей цепочки
Что обычно проще пользователю?Искать разрезы в одном измеренииРазбираться в нескольких связанных таблицах

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

Войти, чтобы отмечатьдальше →
6.5 →Типы таблиц фактов