Learning Platform
Глоссарий Troubleshooting
Урок 09.02 · 22 мин
Начальный
star-schemadenormalizationjoinkimball

Что такое Star Schema

Star Schema (звёздная схема) — самая распространённая dimensional model. Структура:

  • Один fact table в центре.
  • Несколько dimensions вокруг, соединённые с fact через foreign keys.
  • Dimensions денормализованы — иерархия и связанные атрибуты inline.

Визуально похоже на звезду: fact в центре, dimensions — лучи.

              dim_date
                 |
                 |
dim_customer --- fact_orders --- dim_product
                 |
                 |
              dim_store

Простая структура, простой SQL, быстрые queries. Это стандарт BI tools (Tableau, Looker, Power BI) — они оптимизированы под star schema.

Пример: e-commerce sales mart

Возьмём интернет-магазин. Бизнес-процесс: заказ был размещён. Грейн: один товар в заказе (line item).

Fact table

CREATE TABLE marts.sales.fact_order_items (
  -- Surrogate PK
  order_item_key BIGINT PRIMARY KEY,

  -- Foreign keys к dimensions
  date_key INT,             -- когда заказ
  customer_key BIGINT,      -- кто
  product_key BIGINT,       -- что
  store_key BIGINT,         -- где (channel/online store)
  promotion_key BIGINT,     -- по какой промо (NULL если без)

  -- Degenerate dimension (см. ниже)
  order_id BIGINT NOT NULL,

  -- Numeric measures (additive)
  qty INT NOT NULL,
  unit_price NUMERIC(10, 2),
  list_price NUMERIC(10, 2),
  total_amount NUMERIC(12, 2),    -- qty * unit_price
  discount_amount NUMERIC(10, 2),
  net_amount NUMERIC(12, 2),       -- total - discount
  cost_amount NUMERIC(12, 2),
  margin_amount NUMERIC(12, 2)     -- net - cost
);

Грейн — один line item (один товар в заказе). Если в заказе 3 товара — 3 строки fact. Это даёт максимальную гибкость для analytics.

Dimensions

dim_date — calendar:

CREATE TABLE marts.sales.dim_date (
  date_key INT PRIMARY KEY,        -- 20260517 (yyyymmdd format)
  full_date DATE,
  year INT,
  quarter INT,
  quarter_name TEXT,               -- 'Q1', 'Q2'
  month INT,
  month_name TEXT,                 -- 'January'
  week_of_year INT,
  day_of_month INT,
  day_of_week INT,
  day_of_week_name TEXT,           -- 'Monday'
  is_weekend BOOLEAN,
  is_holiday BOOLEAN,
  holiday_name TEXT,
  fiscal_year INT,
  fiscal_quarter INT
);

Не загружается из источника — генерируется в DWH на 10-20 лет вперёд. Используется всеми marts.

dim_customer — клиенты:

CREATE TABLE marts.sales.dim_customer (
  customer_key BIGINT PRIMARY KEY,
  customer_id BIGINT,              -- natural key
  email TEXT,
  name TEXT,
  country TEXT,
  city TEXT,

  -- Сегментация
  customer_segment TEXT,           -- 'premium', 'standard', 'churned'
  age_band TEXT,                   -- '18-24', '25-34', '35-44', '45-54', '55+'
  signup_date DATE,
  tenure_years INT,                -- pre-computed

  -- Demographics
  preferred_language TEXT,
  marketing_consent BOOLEAN,

  -- SCD Type 2
  valid_from DATE,
  valid_to DATE,
  is_current BOOLEAN
);

dim_product — товары с денормализованной иерархией:

CREATE TABLE marts.sales.dim_product (
  product_key BIGINT PRIMARY KEY,
  product_id BIGINT,               -- natural key (SKU)
  product_name TEXT,
  product_description TEXT,

  -- Иерархия inline (денормализация!)
  category_name TEXT,              -- 'Electronics'
  subcategory_name TEXT,           -- 'Smartphones'
  department_name TEXT,            -- 'Consumer Electronics'

  -- Brand info inline
  brand_name TEXT,
  brand_country TEXT,
  brand_segment TEXT,              -- 'luxury', 'mainstream', 'budget'

  -- Pricing tier
  list_price NUMERIC(10, 2),
  price_band TEXT,                 -- 'low', 'medium', 'premium'

  -- Physical attributes
  weight_kg NUMERIC(8, 2),
  is_seasonal BOOLEAN,

  -- SCD Type 2
  valid_from DATE,
  valid_to DATE,
  is_current BOOLEAN
);

Обратите внимание: category_name, subcategory_name, department_nameвсе inline. Не отдельная dim_category таблица. Это и есть денормализация Kimball — единственная dimension содержит всю иерархию.

dim_store — каналы продаж:

CREATE TABLE marts.sales.dim_store (
  store_key BIGINT PRIMARY KEY,
  store_id BIGINT,
  store_name TEXT,
  channel TEXT,                    -- 'web', 'mobile_app', 'pos', 'marketplace'
  region TEXT,
  country TEXT,
  is_active BOOLEAN
);

dim_promotion:

CREATE TABLE marts.sales.dim_promotion (
  promotion_key BIGINT PRIMARY KEY,
  promotion_id BIGINT,
  promotion_name TEXT,
  campaign_name TEXT,
  promo_type TEXT,                 -- 'percentage', 'bogo', 'fixed'
  discount_value NUMERIC(10, 2),
  start_date DATE,
  end_date DATE
);

Visual

Star Schema: один fact + dimensions

Простая структура, легко читается, быстрые JOIN-ы

dim_date: pre-generated calendar на 10-20 лет. Pre-computed атрибуты (quarter, day_of_week, is_holiday). 3650-7300 строк. Используется всеми marts
dim_customer: surrogate key, natural key (customer_id), descriptive attributes, sgement, age_band. SCD Type 2 для истории изменений
dim_product: surrogate key, name, денормализованная иерархия (category, subcategory, department), brand info, pricing tier — всё inline
fact_order_items: центральная таблица. FK к dimensions + measures (qty, prices, discounts, margins). Грейн = line item. Большая (миллиарды строк), узкая (~15 колонок)
dim_store: каналы продаж, online/offline. Маленькая dim (десятки-сотни строк), редко меняется
dim_promotion: маркетинговые акции. Promo type, dates, discount value. Используется для attribution анализа

Degenerate dimension

Заметили, что в fact_order_items есть order_id, но нет dim_order? Это degenerate dimension.

Order_id — это идентификатор заказа в OLTP. Все его атрибуты (customer, date, etc) уже разнесены по dimensions через FK. Поэтому dim_order не нужна — её attributes уже есть в других dimensions. Но order_id полезен:

  • Группировка line items одного заказа: GROUP BY order_id.
  • Drill-back в OLTP-систему: “найди заказ 12345 в Postgres”.

Решение — хранить order_id прямо в fact без отдельной dimension. Это degenerate dimension (или junk dimension в другой терминологии).

Query на star schema

Типичный BI запрос:

SELECT
  d.year,
  d.month_name,
  p.category_name,
  c.country,
  SUM(f.net_amount) AS revenue,
  SUM(f.margin_amount) AS margin,
  SUM(f.qty) AS units_sold,
  COUNT(DISTINCT f.order_id) AS orders_count,
  COUNT(DISTINCT f.customer_key) AS unique_customers
FROM marts.sales.fact_order_items f
JOIN marts.sales.dim_date d ON f.date_key = d.date_key
JOIN marts.sales.dim_customer c ON f.customer_key = c.customer_key
JOIN marts.sales.dim_product p ON f.product_key = p.product_key
WHERE d.year = 2026
  AND c.country IN ('US', 'CA', 'MX')
GROUP BY 1, 2, 3, 4
ORDER BY revenue DESC;

Чистый, читаемый SQL. Три JOIN, чёткая структура. На columnar storage с partitioning по date_key — секунды на миллиардах строк.

Почему star — стандарт BI

1. Простота для аналитика. SQL пишется естественно: SELECT measures FROM fact JOIN dims. Минимум “thinking time” для понимания структуры.

2. Производительность. Минимум JOIN-ов (всё через fact). Денормализованные dimensions устраняют дополнительные JOIN-ы в иерархиях. Современные columnar DWH идеально подходят.

3. BI tools fit. Tableau, Looker, Power BI generated SQL автоматически из drag-and-drop UI работает на star schemas из коробки. Snowflake schemas (нормализованные dims) хуже работают.

4. Симметрия. Каждая dimension — equal citizen. Можно делать slicing по любому atribute любой dimension без особого мышления о joins.

5. Кеширование. BI tools кэшируют join result. Star — простой shape для кэширования.

Conformed dimensions: для multiple marts

Если у вас несколько marts (sales, marketing, finance), и в каждом есть dim_customer — они должны быть conformed: одни и те же customer_keys, одни атрибуты, одна логика. Иначе marts будут противоречить друг другу.

Решение: один dim_customer в core слое, marts используют его через JOIN или копируют. dbt автоматизирует это через model references.

# Реальная dbt-структура
models/
  core/
    dim_customer.sql           # conformed, single source of truth
    dim_product.sql
    dim_date.sql
  marts/
    sales/
      fact_order_items.sql     # references core dimensions
    marketing/
      fact_email_events.sql    # references same dim_customer
    finance/
      fact_payments.sql        # references same dim_customer

Это даёт data bus (Kimball term): marts соединены через shared conformed dimensions. Sales analytics и marketing analytics видят одинаковых customers, можно делать cross-mart queries.

TIP

Conformed dimensions — главное правило при scaling Kimball-подхода. Без них marts расходятся, появляются inconsistencies, теряется trust в данных. dbt сильно помогает соблюдать conformity через model references и testing.

Pre-computed measures

В fact-таблицу можно (и часто стоит) добавить pre-computed measures:

  • total_amount = qty * unit_price — посчитано в ETL, не на лету.
  • net_amount = total_amount - discount_amount.
  • margin_amount = net_amount - cost_amount.

Это денормализация measures. Стоимость — дублирование данных (можно посчитать на лету), но преимущество — простой SQL для аналитика и быстрые queries.

-- С pre-computed measures: чистый SQL
SELECT SUM(margin_amount) FROM fact_order_items;

-- Без: каждый аналитик вычисляет на лету, легко ошибиться
SELECT SUM(qty * unit_price - discount_amount - cost_amount) FROM fact_order_items;

Pre-computed measures — стандарт Kimball. Принцип: make BI easy for analysts. Они не должны помнить бизнес-формулы — формулы embedded в fact.

Когда star не подходит

Star schema — отличный default, но не панацея. Где он плохо работает:

1. Many-to-many relationships. Если один заказ может относиться к multiple campaigns (a customer responded to email AND saw a Facebook ad before buying), star становится сложным. Решение — bridge tables.

2. Hierarchies с многими уровнями. Если категория имеет 7 уровней — денормализация раздувает dim_product. Тогда snowflake (lesson 3) или recursive structure.

3. Slowly changing attributes с большим объёмом изменений. Если customer.country меняется каждый месяц — SCD Type 2 раздувает dim_customer быстро. Тогда compromise (Type 3 или history bridge).

4. Streaming аналитика с sub-second latency. Star schema хорош для batch-аналитики. Для real-time часто используют denormalized OBT (One Big Table) — fact с inline dim attributes.

Star vs OBT

OBT (One Big Table) — крайний случай денормализации. Всё в одной таблице:

CREATE TABLE marts.orders_obt (
  -- Все атрибуты заказа
  order_id BIGINT,
  date DATE,
  -- Атрибуты клиента (inline)
  customer_id BIGINT,
  customer_name TEXT,
  customer_country TEXT,
  customer_segment TEXT,
  -- Атрибуты продукта (inline)
  product_id BIGINT,
  product_name TEXT,
  category_name TEXT,
  brand_name TEXT,
  -- Measures
  qty INT,
  amount NUMERIC,
  -- ... 50 колонок
);

Никаких JOIN-ов. Все queries — flat scans. На ClickHouse/columnar DBs часто самый быстрый вариант. Цена — дублирование (customer name в каждой строке), сложность maintenance (изменение customer.country = обновление миллионов orders).

Star vs OBT — это trade-off:

  • Star: меньше storage, проще updates, чуть медленнее queries.
  • OBT: больше storage, сложнее updates, быстрее queries.

Для batch DWH (Snowflake/BigQuery) — обычно star. Для real-time OLAP (ClickHouse) — часто OBT.

Snowflake schema — нормализованная вариация

У star есть близкий родственник — snowflake schema. Это та же звезда, но dimensions частично нормализованы: вместо одной wide dim_product с inline иерархией (category_name, subcategory_name, department_name колонками внутри) — отдельные таблицы dim_category, dim_subcategory, dim_department, связанные FK-цепочкой. Получается snowflake (снежинка): fact в центре, dimensions ветвятся в подtaблицы.

Плюсы нормализации: меньше storage на больших dimensions, чистота при rename категорий, явная иерархия в схеме. Минусы существенные: больше JOIN-ов в каждом запросе (для запроса по department нужно JOIN fact -> dim_product -> dim_subcategory -> dim_department вместо одного JOIN до dim_product); BI tools (Tableau, Looker) хуже работают с snowflake, оптимизированы под star; SQL аналитика становится сложнее.

В 2026 году snowflake schema редко используется в чистом виде. На columnar DWH (Snowflake, BigQuery) dictionary encoding делает дублирование значений почти бесплатным по storage — миллион Electronics в dim_product занимает несколько байт. Главный исторический аргумент за нормализацию пропадает.

Snowflake встречается в legacy DWH банков и страховых (регуляторика требует нормализации), в healthcare DWH (ICD-10 codes имеют семиуровневую иерархию), в редких retail-системах Amazon-масштаба с миллиардами продуктов. В стартапах и SMB-проектах — star везде.

Часто встречается гибрид: большая часть dimensions денормализованы (star), но некоторые специфические — нормализованы. Например, dim_customer и dim_product денормализованы, а dim_geography (страны -> регионы -> города) — нормализована, потому что используется в нескольких dimensions. Это нормально — нет догмы “всё star или всё snowflake”.

TIP

Не путайте snowflake schema (методология dimensional modeling) с Snowflake (cloud DWH компания). Это совершенно разные вещи, просто одинаковое имя.

data-modeling: разбор star schema — conformed dimensions и bus matrix

Дизайн dimensional моделей разбираем в будущем dwh-design-course.

Попробуй сам

  1. Спроектируй sales mart для своего hypothetical e-commerce: какие fact, dimensions, measures? Реализуй CREATE TABLE statements.
  2. На public dataset (например, TPC-H lineitem, orders, customer, supplier, part) — построй star schema. Сравни SQL запросы на 3NF (TPC-H native) vs на star (твой mart).
  3. Найди в открытом dbt project (dbt-jaffle-shop) marts/. Идентифицируй fact и dimensions. Зарисуй star schema diagram.
  4. Спроектируй marketing mart с conformed dim_customer (shared с sales mart). Какой будет fact для email events?
Проверка знанийKnowledge check
Архитектор предлагает sales mart структуру: fact_orders, dim_customer, dim_product, dim_category (с FK от dim_product к dim_category), dim_brand (с FK от dim_product к dim_brand). Junior data engineer возражает. Что не так и какой правильный подход в star schema?
ОтветAnswer
Это не star, это snowflake schema (тема следующего урока). В star schema dimensions денормализованы — все атрибуты иерархии inline в одной dimension. Правильно: dim_product содержит category_name, subcategory_name, department_name, brand_name, brand_country — все колонки inline, никаких отдельных dim_category и dim_brand. Преимущества star: 1) минимум JOIN-ов в queries (один JOIN до dim_product вместо двух); 2) BI tools (Tableau, Looker) работают лучше с denormalized dims; 3) запросы проще читаются; 4) на columnar DBs денормализация почти бесплатна по storage (dictionary encoding). Цена — обновления (изменение category name требует UPDATE миллиона строк dim_product), но это батч-операция, происходит редко. Star — стандарт Kimball и большинства production marts.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Что такое Star Schema и почему она стандарт для BI tools (Tableau, Looker, Power BI)?

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

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

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

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