Нормализация баз данных: 1НФ, 2НФ, 3НФ, Бойса–Кодда
Зачем нужна нормализация? 🛠️
Нормализация — это процесс организации данных в базе так, чтобы минимизировать дублирование и аномалии при обновлении. Представьте, что у вас есть таблица Orders с полями:
CREATE TABLE Orders (
order_id INT,
customer_name VARCHAR(100),
customer_phone VARCHAR(20),
product_name VARCHAR(100),
product_price DECIMAL(10, 2),
quantity INT
);
Проблема: если клиент закажет 10 раз, его имя и телефон повторятся 10 раз. При смене номера придётся обновлять все записи!
👉 Цель нормализации: разбить данные на логические таблицы, где каждая хранит только то, что действительно нужно.
Первая нормальная форма (1НФ) ✅
Правило: Все атрибуты должны быть атомарными (неделимыми), а таблица — не содержать повторяющихся групп.
❌ Плохой пример (список товаров в одной ячейке):
| order_id | products |
|---|---|
| 1 | Книга, Футболка |
✅ Исправленный вариант:
CREATE TABLE Orders_1NF (
order_id INT,
product_name VARCHAR(100) -- Один товар на строку
);
⚡ Что проверить:
- Нет столбцов с множественными значениями (например, phone1, phone2).
- Нет повторяющихся групп данных.
Вторая нормальная форма (2НФ) 🔍
Правило: Таблица должна быть в 1НФ + каждый неключевой атрибут зависит от всего первичного ключа.
Пример проблемы в таблице OrderDetails:
CREATE TABLE OrderDetails (
order_id INT,
product_id INT,
product_name VARCHAR(100), -- Зависит только от product_id!
quantity INT,
PRIMARY KEY (order_id, product_id)
);
🚨 product_name зависит только от product_id, а не от всей связки (order_id, product_id).
✅ Решение — разбить на две таблицы:
CREATE TABLE Products (
product_id INT PRIMARY KEY,
product_name VARCHAR(100)
);
CREATE TABLE OrderDetails_2NF (
order_id INT,
product_id INT,
quantity INT,
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);
Третья нормальная форма (3НФ) 🎯
Правило: Таблица должна быть в 2НФ + не должно быть транзитивных зависимостей (когда атрибут зависит от другого неключевого атрибута).
Пример «запаха» в таблице Orders:
CREATE TABLE Orders (
order_id INT PRIMARY KEY,
customer_id INT,
customer_zip_code VARCHAR(10), -- Зависит от customer_id, а не от order_id
order_date DATE
);
💡 customer_zip_code зависит от customer_id, который сам не является первичным ключом.
✅ Нормализованный вариант:
CREATE TABLE Customers (
customer_id INT PRIMARY KEY,
customer_zip_code VARCHAR(10)
);
CREATE TABLE Orders_3NF (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
FOREIGN KEY (customer_id) REFERENCES Customers(customer_id)
);
Нормальная форма Бойса–Кодда (BCNF) 🏆
Правило: Усиленная 3НФ, где любой определяющий атрибут (даже не входящий в первичный ключ) должен быть потенциальным ключом.
Сложный случай — таблица Teaching:
CREATE TABLE Teaching (
professor_id INT,
subject VARCHAR(100),
classroom VARCHAR(50),
PRIMARY KEY (professor_id, subject)
);
Предположим, что:
- Один профессор ведёт один предмет.
- Один предмет может быть только в одной аудитории.
🚨 Проблема: classroom зависит от subject (который не является ключом сам по себе).
✅ Решение по BCNF:
CREATE TABLE Subjects (
subject VARCHAR(100) PRIMARY KEY,
classroom VARCHAR(50)
);
CREATE TABLE Teaching_BCNF (
professor_id INT,
subject VARCHAR(100),
PRIMARY KEY (professor_id, subject),
FOREIGN KEY (subject) REFERENCES Subjects(subject)
);
Когда остановиться? 🛑
Нормализация решает проблемы, но избыточная нормализация усложняет запросы. Иногда денормализация (например, дублирование данных для скорости) — осознанный выбор.
Практический совет:
- Начинайте с 3НФ для большинства проектов.
- Переходите к BCNF, если видите явные аномалии обновления.
- Для аналитики (OLAP) иногда лучше звездообразная схема.