Звёздная и снежинка-схема
Моделируйте данные для быстрой аналитики
«Звёздная и снежинка-схема» — бесплатный урок SQL Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения SQL Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс SQL Academy содержит 4 уроков всего.
Что такое схема хранилища данных
В транзакционной базе данных (OLTP) данные нормализуют, чтобы избежать избыточности. В хранилище данных их часто намеренно денормализуют, жертвуя объемом хранения ради скорости запросов. Два классических шаблона организации таблиц хранилища — звездная схема и снежная схема.
В основе обеих схем лежит центральная таблица фактов, окруженная таблицами измерений. Различие заключается в том, насколько глубоко нормализованы эти измерения.
Таблицы фактов и таблицы измерений
Таблица фактов хранит измеримые события — продажи, нажатия, отправки. Она содержит много строк и включает числовые меры, а также внешние ключи измерений.
Таблица измерений описывает контекст каждого события: кто, что, когда и где. Таблицы измерений содержат меньше строк, но больше описательных столбцов.
CREATE TABLE fact_sales (
sale_id SERIAL PRIMARY KEY,
date_key INT NOT NULL,
product_key INT NOT NULL,
customer_key INT NOT NULL,
store_key INT NOT NULL,
quantity INT NOT NULL,
revenue NUMERIC(12, 2) NOT NULL
);
CREATE TABLE dim_product (
product_key SERIAL PRIMARY KEY,
product_name VARCHAR(200),
category VARCHAR(100),
brand VARCHAR(100),
unit_price NUMERIC(10, 2)
);Звездная схема
В звездной схеме каждая таблица измерений соединяется непосредственно с таблицей фактов. Если изобразить связи на бумаге, получится звезда: таблица фактов находится в центре, а измерения образуют ее лучи.
Таблицы измерений полностью денормализованы: все описательные атрибуты хранятся в одной таблице, даже если некоторые атрибуты повторяются в разных строках.
-- Star schema: all product info in one flat dimension table
CREATE TABLE dim_product (
product_key SERIAL PRIMARY KEY,
product_name VARCHAR(200),
category_name VARCHAR(100), -- denormalized
subcategory VARCHAR(100), -- denormalized
brand_name VARCHAR(100), -- denormalized
brand_country VARCHAR(100), -- denormalized
unit_price NUMERIC(10, 2)
);
CREATE TABLE dim_date (
date_key INT PRIMARY KEY, -- e.g. 20240315
full_date DATE,
year INT,
quarter INT,
month INT,
month_name VARCHAR(20),
week INT,
day_of_week VARCHAR(10)
);Запрос к звездной схеме
Плоские таблицы измерений упрощают запросы. Вы соединяете таблицу фактов с одним или несколькими измерениями и выполняете агрегацию. Дополнительных соединений по цепочкам нормализованных таблиц нет.
Именно поэтому звездные схемы обеспечивают быстрые аналитические запросы: граф соединений неглубокий.
SELECT
d.year,
d.quarter,
p.category_name,
SUM(f.revenue) AS total_revenue,
SUM(f.quantity) AS units_sold
FROM fact_sales f
JOIN dim_date d ON d.date_key = f.date_key
JOIN dim_product p ON p.product_key = f.product_key
WHERE d.year = 2024
GROUP BY d.year, d.quarter, p.category_name
ORDER BY d.quarter, total_revenue DESC;Схема «снежинка»
Схема снежинка дополнительно нормализует таблицы измерений, разделяя их на подизмерения. Например, вместо хранения category_name и brand_name в таблице dim_product создаются отдельные таблицы dim_category и dim_brand.
Получившаяся диаграмма напоминает снежинку — ветви связанных таблиц.
-- Snowflake schema: product dimension is normalized
CREATE TABLE dim_brand (
brand_key SERIAL PRIMARY KEY,
brand_name VARCHAR(100),
brand_country VARCHAR(100)
);
CREATE TABLE dim_category (
category_key SERIAL PRIMARY KEY,
category_name VARCHAR(100),
subcategory VARCHAR(100)
);
CREATE TABLE dim_product (
product_key SERIAL PRIMARY KEY,
product_name VARCHAR(200),
category_key INT REFERENCES dim_category(category_key),
brand_key INT REFERENCES dim_brand(brand_key),
unit_price NUMERIC(10, 2)
);Запрос к схеме «снежинка»
Для запроса к схеме «снежинка» требуется больше соединений, чтобы восстановить данные измерений, разделённые между таблицами. Оптимизатор запросов должен пройти дополнительные уровни, что может увеличить задержку по сравнению со звёздной схемой.
Однако нормализованные измерения занимают меньше места и согласованы между собой: изменение названия бренда в одной строке dim_brand автоматически применяется во всех местах.
SELECT
d.year,
c.category_name,
b.brand_name,
SUM(f.revenue) AS total_revenue
FROM fact_sales f
JOIN dim_date d ON d.date_key = f.date_key
JOIN dim_product p ON p.product_key = f.product_key
JOIN dim_category c ON c.category_key = p.category_key
JOIN dim_brand b ON b.brand_key = p.brand_key
WHERE d.year = 2024
GROUP BY d.year, c.category_name, b.brand_name
ORDER BY total_revenue DESC;Суррогатные ключи и естественные ключи
Таблицы измерений обычно используют суррогатный ключ — целое число, сгенерированное хранилищем (например, SERIAL), — а не естественный ключ из исходной системы.
Суррогатные ключи остаются стабильными даже при изменениях в источнике, занимают мало места в больших таблицах фактов и поддерживают медленно изменяющиеся измерения, для которых необходимо отслеживать историю.
-- Surrogate key (product_key) vs natural key (sku)
INSERT INTO dim_product (product_name, category_key, brand_key, unit_price)
VALUES ('Wireless Headphones', 3, 7, 89.99);
-- product_key is assigned by SERIAL -- the natural key (SKU) lives elsewhere
-- Natural key would be:
-- INSERT INTO dim_product (sku, product_name, ...)
-- VALUES ('WH-1000XM5', 'Wireless Headphones', ...);
-- Risky: SKU can be reused or reassigned by the source systemИзмерение даты
Измерение даты особенное: оно почти всегда присутствует и обычно предварительно заполняется датами на много лет вперёд. Хранение производных атрибутов (года, квартала, названия месяца, финансового периода, признака праздника) в таблице измерений избавляет от необходимости вычислять их во время выполнения запроса.
-- Populate dim_date for one year using generate_series
INSERT INTO dim_date (date_key, full_date, year, quarter, month, month_name, week, day_of_week)
SELECT
TO_CHAR(d, 'YYYYMMDD')::INT AS date_key,
d AS full_date,
EXTRACT(YEAR FROM d)::INT AS year,
EXTRACT(QUARTER FROM d)::INT AS quarter,
EXTRACT(MONTH FROM d)::INT AS month,
TO_CHAR(d, 'Month') AS month_name,
EXTRACT(WEEK FROM d)::INT AS week,
TO_CHAR(d, 'Day') AS day_of_week
FROM generate_series('2024-01-01'::DATE, '2024-12-31'::DATE, '1 day') AS d;Медленно изменяющиеся измерения (SCD типа 2)
Что происходит, когда клиент переезжает в другой город или товар меняет категорию? Необходимо отслеживать историю. SCD типа 2 добавляет новую строку измерения при каждом изменении, закрывая предыдущую строку датой окончания. Строка таблицы фактов по-прежнему ссылается на старый ключ измерения, сохраняя историческую точность.
-- SCD Type 2 customer dimension
CREATE TABLE dim_customer (
customer_key SERIAL PRIMARY KEY,
customer_id INT NOT NULL,
customer_name VARCHAR(200),
city VARCHAR(100),
country VARCHAR(100),
valid_from DATE NOT NULL,
valid_to DATE,
is_current BOOLEAN DEFAULT TRUE
);
-- When a customer moves, close old row and insert new one:
UPDATE dim_customer
SET valid_to = CURRENT_DATE - 1, is_current = FALSE
WHERE customer_id = 42 AND is_current = TRUE;
INSERT INTO dim_customer (customer_id, customer_name, city, country, valid_from, is_current)
VALUES (42, 'Alice Muller', 'Berlin', 'Germany', CURRENT_DATE, TRUE);Звёздная схема и схема «снежинка» — компромиссы
Ни одна схема не лучше во всех случаях. Выбирайте схему с учётом своих приоритетов:
- Звёздная схема — меньше соединений, более быстрые запросы, более простой ETL, более высокие затраты на хранение. Лучше всего подходит для инструментов аналитики с преобладанием чтения (Tableau, Power BI).
- Схема «снежинка» — нормализованные измерения, меньше избыточности, более удобное обновление измерений, но больше соединений. Лучше подходит, когда измерения велики или используются совместно несколькими таблицами фактов.
-- Checking how much storage the denormalized category column costs
-- in a large dim_product (star schema) vs a separate dim_category (snowflake)
SELECT
COUNT(*) AS total_products,
COUNT(DISTINCT category_name) AS unique_categories,
pg_size_pretty(
SUM(pg_column_size(category_name))
) AS category_storage
FROM dim_product;Схема «созвездие» (созвездие фактов)
Когда в хранилище есть несколько таблиц фактов, использующих общие таблицы измерений, результат называют схемой «созвездие» (или созвездием фактов). Например, в хранилище розничных данных могут быть отдельные таблицы фактов для продаж и возвратов, обе ссылающиеся на одни и те же dim_product и dim_date.
Общие измерения обеспечивают согласованную фильтрацию и упрощают сравнение данных разных таблиц фактов.
CREATE TABLE fact_returns (
return_id SERIAL PRIMARY KEY,
date_key INT NOT NULL REFERENCES dim_date(date_key),
product_key INT NOT NULL REFERENCES dim_product(product_key),
customer_key INT NOT NULL,
quantity INT NOT NULL,
refund_amount NUMERIC(12, 2) NOT NULL
);
-- Cross-fact query: net revenue = sales - refunds
SELECT
d.year,
d.month,
SUM(s.revenue) AS gross_revenue,
SUM(r.refund_amount) AS total_refunds,
SUM(s.revenue) - COALESCE(SUM(r.refund_amount), 0) AS net_revenue
FROM dim_date d
LEFT JOIN fact_sales s ON s.date_key = d.date_key
LEFT JOIN fact_returns r ON r.date_key = d.date_key
WHERE d.year = 2024
GROUP BY d.year, d.month
ORDER BY d.month;Звёздная схема и схема «снежинка»
Проверьте свои знания о звёздных схемах и схемах «снежинка».
Итоги урока
В этом уроке вы изучили два базовых шаблона проектирования хранилищ данных:
- Звёздная схема — центральная таблица фактов, окружённая плоскими денормализованными таблицами измерений. Меньше соединений, более быстрые запросы, немного больше места для хранения.
- Схема «снежинка» — таблицы измерений дополнительно нормализованы в подизмерения. Меньше избыточности, более удобные обновления, но требуется больше соединений.
- Таблицы фактов хранят измеримые события; таблицы измерений предоставляют контекст (кто, что, когда, где).
- Суррогатные ключи сохраняют историческую точность и отделяют хранилище от изменений в исходной системе.
- SCD типа 2 отслеживает историю измерений, добавляя новые строки с датами действия вместо перезаписи старых.
- Когда несколько таблиц фактов используют общие измерения, получается схема «созвездие» (созвездие фактов).
Выбирайте звёздную схему ради простоты и скорости, а схему «снежинка» — когда измерения велики, часто обновляются или используются совместно многими таблицами фактов.
Часто задаваемые вопросы
Урок «Звёздная и снежинка-схема» бесплатный?
Да — полный текст урока «Звёздная и снежинка-схема» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс SQL Academy, подпишись на CoddyKit PRO. Курс SQL Academy содержит 4 уроков всего.
Чему я научусь в уроке «Звёздная и снежинка-схема»?
Моделируйте данные для быстрой аналитики Ты практикуешь SQL Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать SQL Academy?
Предыдущий опыт не требуется. SQL Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Звёздная и снежинка-схема»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке SQL Academy?
Да. Каждый урок SQL Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- OLTP и OLAP
- Таблицы фактов и измерений
- Звёздная и снежинка-схема
- Написание аналитических запросов