Что такое снежинка (snowflake) в контексте нормальных форм? Находится ли схема «снежинка» в 3NF?

Ответ

Снежинка (snowflake) — это нормализованная схема хранения данных в хранилище (DWH), которая является расширением схемы "звезда" (star schema). В ней таблицы измерений (dimensions) дополнительно нормализуются, разбиваясь на иерархические связанные таблицы.

Да, схема "снежинка" по определению находится в 3NF (Third Normal Form), а часто и в BCNF. Это ее ключевое отличие от "звезды", где измерения денормализованы.

Условия 3NF:

  1. Таблица находится во 2NF (нет частичных зависимостей от составного ключа).
  2. Нет транзитивных зависимостей (неключевые атрибуты зависят только от первичного ключа, а не от других неключевых атрибутов).

Пример нарушения 3NF и исправления через "снежинку":

-- Таблица 'orders' с нарушением 3NF (customer_city зависит от customer_id, а не от order_id):
CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    customer_id INT,
    customer_name VARCHAR(100),
    customer_city VARCHAR(100), -- Транзитивная зависимость: city зависит от customer_id
    order_date DATE
);

-- Приведение к 3NF / Схема "снежинка":
CREATE TABLE dim_customer (
    customer_id INT PRIMARY KEY,
    customer_name VARCHAR(100),
    city_id INT REFERENCES dim_city(city_id) -- Вынос в отдельную таблицу
);

CREATE TABLE dim_city (
    city_id INT PRIMARY KEY,
    city_name VARCHAR(100),
    region_id INT REFERENCES dim_region(region_id) -- Дальнейшая нормализация
);

CREATE TABLE fact_orders (
    order_id INT PRIMARY KEY,
    customer_id INT REFERENCES dim_customer(customer_id),
    order_date DATE
);

Преимущество снежинки — уменьшение избыточности данных и согласованность. Недостаток — более сложные запросы с большим количеством JOIN, что может влиять на производительность.

Ответ 18+ 🔞

Давай разберём эту вашу снежинку, а то я смотрю, у некоторых уже глаза на лоб лезут от этих нормальных форм. Слушай сюда, история простая, как три копейки.

Представь, что у тебя есть главная таблица с заказами — orders. И ты туда, такой умный, запихнул всё подряд: и номер заказа, и имя клиента, и его город. Ну и что? А то, что этот город привязан не к заказу, а к клиенту! Это ж классическая транзитивная зависимость, ёпта. Получается, если один Вася Пупкин из Москвы сделает сто заказов, ты эту самую Москву в базе продублируешь сто раз. Идиотизм? Да, овердохуища идиотизма! Данные избыточные, место зря жрёшь, а если Москву переименуют в Мухосранск — тебе придётся лазить по всем ста записям и править. Волнение ебать, честное слово.

Вот тут-то и выруливает наша снежинка. Её главный принцип — разнести всё по полочкам. Берёшь эту кучу-малу и начинаешь нормализовать до состояния 3NF.

Что такое 3NF, если по-простому?

  1. Нету такого, чтобы часть данных зависела только от кусочка ключа (это уже 2NF, но проехали).
  2. Самое важное: чтобы неключевые поля (типа города) зависели только от первичного ключа своей таблицы, а не от какого-то другого поля рядом. Как в нашем примере: customer_city зависел не от order_id, а от customer_id. Это нарушение, товарищ!

Как это лечится? Да элементарно, Ватсон! Выносим клиента в отдельную таблицу dim_customer. А город клиента? А город — это вообще отдельная сущность! Выносим и его в таблицу dim_city. Получается такая разветвлённая, иерархическая хуйня, где всё связано ссылками. Смотри на код, тут всё наглядно:

-- Было плохо (город прилип к заказу через клиента):
CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    customer_id INT,
    customer_name VARCHAR(100),
    customer_city VARCHAR(100), -- Нарушение! Город зависит от клиента, а не от заказа.
    order_date DATE
);

-- Стало хорошо (снежинка в 3NF):
-- Сначала справочник городов
CREATE TABLE dim_city (
    city_id INT PRIMARY KEY,
    city_name VARCHAR(100)
);

-- Потом справочник клиентов, который ссылается на город
CREATE TABLE dim_customer (
    customer_id INT PRIMARY KEY,
    customer_name VARCHAR(100),
    city_id INT REFERENCES dim_city(city_id) -- Ссылка, а не дубль текста!
);

-- И наконец, главная таблица фактов, чистая и аккуратная
CREATE TABLE fact_orders (
    order_id INT PRIMARY KEY,
    customer_id INT REFERENCES dim_customer(customer_id), -- Ссылка на клиента
    order_date DATE
);

И что мы имеем в сухом остатке?

  • Плюсы: Избыточность данных — ноль ебать. Москва хранится в одном-единственном месте. Консистентность — на высоте. Захотел поменять название региона — поправил в одной записи dim_city, и оно автоматом ко всем клиентам применилось. Удивление пиздец, как удобно.
  • Минусы: Запросы становятся пиздопроебибными. Чтобы получить сводный отчёт, тебе придётся делать JOIN на столько таблиц, что голова кругом пойдёт. fact_orders -> dim_customer -> dim_city... Производительность на больших данных может накрыться медным тазом, если индексы не продуманы.

Так что выбирай, чувак: хочешь целостность данных и экономию места — снежинка твой выбор. Хочешь, чтобы аналитики не матерились, собирая отчёт, — возможно, классическая звёзда с её денормализацией будет практичнее. Всё зависит от задачи, да похуй, в общем-то, на чистоту теории, если на практике всё тормозит.