← Назад к вопросам

Какие знаешь уровни нормализации?

2.0 Middle🔥 62 комментариев
#Базы данных и SQL

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Какие знаешь уровни нормализации?

Нормализация — это процесс структурирования данных в БД для устранения дублирования и обеспечения целостности. Это фундаментальный концепт проектирования реляционных БД.

Проблема без нормализации

-- Денормализованная таблица (ПЛОХО)
CREATE TABLE users_denormalized (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  email VARCHAR(100),
  phone VARCHAR(20),
  address VARCHAR(255),
  city VARCHAR(50),
  country VARCHAR(50),
  department_name VARCHAR(100),  -- дублируется если 100 юзеров в отделе!
  manager_name VARCHAR(100),
  manager_email VARCHAR(100)
);

-- Проблемы:
-- 1. Дублирование данных (department_name повторяется)
-- 2. Аномалии обновления (поменять department → 100 UPDATE)
-- 3. Аномалии удаления (удалить юзера → потеряется department)
-- 4. Много NULL значений
-- 5. Неэффективное использование памяти

Нулевая нормальная форма (Unnormalized)

Данные просто собраны в одну таблицу без структуры.

-- Пример: JSON вместо отдельной таблицы
CREATE TABLE orders (
  id INT PRIMARY KEY,
  customer_name VARCHAR(100),
  items JSON, -- вложенные данные
  total DECIMAL(10,2)
);

INSERT INTO orders VALUES (
  1, 
  'John Doe', 
  '[{"product": "Laptop", "price": 1000}, {"product": "Mouse", "price": 50}]',
  1050
);

-- Проблема: сложно искать по items

Первая нормальная форма (1NF)

Правило: все значения должны быть атомарными (неделимыми).

-- ПЛОХО - нарушение 1NF
CREATE TABLE students (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  courses VARCHAR(255)  -- "Math,Physics,Chemistry" - не атомарно!
);

-- ХОРОШО - соответствие 1NF
CREATE TABLE students (
  id INT PRIMARY KEY,
  name VARCHAR(100)
);

CREATE TABLE student_courses (
  student_id INT REFERENCES students(id),
  course_name VARCHAR(100),
  PRIMARY KEY (student_id, course_name)
);

-- Теперь каждое значение атомарно
INSERT INTO student_courses VALUES (1, 'Math');
INSERT INTO student_courses VALUES (1, 'Physics');
INSERT INTO student_courses VALUES (1, 'Chemistry');

Проверка 1NF:

  • Нет повторяющихся групп
  • Нет массивов или JSON вложенности
  • Каждое поле содержит одно значение

Вторая нормальная форма (2NF)

Правило: удовлетворять 1NF + все неключевые атрибуты должны зависеть от всего первичного ключа, не от его части.

-- ПЛОХО - нарушение 2NF
CREATE TABLE student_courses (
  student_id INT,
  course_id INT,
  course_name VARCHAR(100),  -- зависит только от course_id, не от student_id!
  professor_name VARCHAR(100), -- зависит только от course_id!
  grade CHAR(1),  -- зависит от (student_id, course_id) - ХОРОШО
  PRIMARY KEY (student_id, course_id)
);

-- Проблемы:
-- - Если изменить professor, нужно UPDATE всех студентов курса
-- - Если удалить последнего студента, потеряем информацию о курсе

-- ХОРОШО - соответствие 2NF
CREATE TABLE courses (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  professor_id INT REFERENCES professors(id)
);

CREATE TABLE student_courses (
  student_id INT REFERENCES students(id),
  course_id INT REFERENCES courses(id),
  grade CHAR(1),
  PRIMARY KEY (student_id, course_id)
);

CREATE TABLE professors (
  id INT PRIMARY KEY,
  name VARCHAR(100)
);

-- Теперь:
-- - Каждое неключевое поле зависит от ВСЕГО первичного ключа
-- - Обновление professor = 1 UPDATE в таблице professors

Проверка 2NF:

  • Удовлетворять 1NF
  • Нет частичных зависимостей от первичного ключа
  • Все неключевые поля зависят от полного ключа

Третья нормальная форма (3NF)

Правило: удовлетворять 2NF + неключевые атрибуты не должны зависеть друг от друга (транзитивная зависимость).

-- ПЛОХО - нарушение 3NF
CREATE TABLE employees (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  department_id INT,
  department_name VARCHAR(100),  -- зависит от department_id (транзитив!)
  manager_id INT,
  manager_name VARCHAR(100)      -- зависит от manager_id (транзитив!)
);

-- Проблемы:
-- - Если изменить department_name → UPDATE все записи
-- - Аномалии удаления и обновления

-- ХОРОШО - соответствие 3NF
CREATE TABLE employees (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  department_id INT REFERENCES departments(id),
  manager_id INT REFERENCES employees(id)
);

CREATE TABLE departments (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  location VARCHAR(100)
);

-- Теперь неключевые поля не зависят друг от друга

Проверка 3NF:

  • Удовлетворять 2NF
  • Нет транзитивных зависимостей
  • Неключевые поля зависят только от первичного ключа

Нормальная форма Бойса-Кодда (BCNF)

Правило: более строгая версия 3NF. Каждый детерминант (поле, от которого зависит другое) должен быть кандидатом в первичные ключи.

-- ПЛОХО - в 3NF, но не в BCNF
CREATE TABLE professor_courses (
  professor_id INT,
  course_id INT,
  time_slot INT,
  classroom INT,
  PRIMARY KEY (professor_id, course_id)
);

-- Правило: один professor может вести один course
-- Но classroom зависит от time_slot, не от (professor, course)!
-- Если несколько courses в одном time_slot → конфликт

-- ХОРОШО - соответствие BCNF
CREATE TABLE professor_courses (
  professor_id INT REFERENCES professors(id),
  course_id INT REFERENCES courses(id),
  PRIMARY KEY (professor_id, course_id)
);

CREATE TABLE course_schedule (
  course_id INT REFERENCES courses(id),
  time_slot INT,
  classroom INT,
  PRIMARY KEY (course_id, time_slot)
);

-- Теперь каждый детерминант - кандидат в первичные ключи

4NF - Четвёртая нормальная форма

Правило: BCNF + отсутствие нетривиальных многозначных зависимостей.

Редко нужна в practice.

-- ПЛОХО
CREATE TABLE student_hobbies_languages (
  student_id INT,
  hobby VARCHAR(50),
  language VARCHAR(50),
  PRIMARY KEY (student_id, hobby, language)
);

-- Если Иван говорит на English и French
-- И играет в Football и Basketball
-- Нужно хранить 4 комбинации (2x2) вместо 4

-- ХОРОШО - разделить независимые многозначные свойства
CREATE TABLE student_hobbies (
  student_id INT REFERENCES students(id),
  hobby VARCHAR(50),
  PRIMARY KEY (student_id, hobby)
);

CREATE TABLE student_languages (
  student_id INT REFERENCES students(id),
  language VARCHAR(50),
  PRIMARY KEY (student_id, language)
);

Практический пример: Интернет-магазин

Денормализованная (ПЛОХО):

CREATE TABLE orders (
  id INT,
  customer_name VARCHAR(100),
  customer_email VARCHAR(100),
  product_name VARCHAR(100),
  product_price DECIMAL(10,2),
  quantity INT,
  total DECIMAL(10,2),
  status VARCHAR(50)
);

3NF нормализованная (ХОРОШО):

CREATE TABLE customers (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  email VARCHAR(100) UNIQUE
);

CREATE TABLE products (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  price DECIMAL(10,2)
);

CREATE TABLE orders (
  id INT PRIMARY KEY,
  customer_id INT REFERENCES customers(id),
  status VARCHAR(50),
  created_at TIMESTAMP
);

CREATE TABLE order_items (
  id INT PRIMARY KEY,
  order_id INT REFERENCES orders(id),
  product_id INT REFERENCES products(id),
  quantity INT,
  unit_price DECIMAL(10,2)
);

Когда нарушать нормализацию (денормализация)

Иногда нормализация медленнее. Тогда денормализуют:

-- Часто нужна total сумма заказа
-- Вместо каждый раз считать:
-- SELECT SUM(quantity * unit_price) FROM order_items WHERE order_id = 1

-- Добавляем денормализованное поле:
ALTER TABLE orders ADD COLUMN total DECIMAL(10,2);

-- Обновляем через триггер:
CREATE TRIGGER update_order_total
AFTER INSERT ON order_items
FOR EACH ROW
BEGIN
  UPDATE orders 
  SET total = (SELECT SUM(quantity * unit_price) FROM order_items WHERE order_id = NEW.order_id)
  WHERE id = NEW.order_id;
END;

Но это усложняет поддержку!

Таблица уровней

Уровень    Требование
──────────┼────────────────────────────────────────────────
Unnorm.    Данные в одной таблице
1NF        Атомарные значения, нет повторяющихся групп
2NF        Нет частичных зависимостей от ключа
3NF        Нет транзитивных зависимостей
BCNF       Строже 3NF, детерминанты = ключи
4NF        Нет многозначных зависимостей
5NF        Нет join зависимостей (очень редко)

Мой подход в production

1. Начинаю с 3NF (золотой стандарт)
2. Профилирую запросы
3. Если медленно → добавляю индексы
4. Если всё ещё медленно → денормализую (с триггерами или кэшем)
5. Храню это в документации
// В коде (ORM)
const order = await db.query(`
  SELECT 
    o.id, o.created_at,
    c.name, c.email,
    SUM(oi.quantity * oi.unit_price) as total,
    COUNT(oi.id) as item_count
  FROM orders o
  JOIN customers c ON o.customer_id = c.id
  JOIN order_items oi ON o.id = oi.order_id
  WHERE o.id = $1
  GROUP BY o.id, c.id
`);

Вывод: 3NF — стандарт modern databases. Понимание нормализации критично для дизайна БД, но в practice комбинируют нормализацию с денормализацией для производительности.

Какие знаешь уровни нормализации? | PrepBro