Какие нормальные формы базы данных ты знаешь и зачем они нужны?

«Какие нормальные формы базы данных ты знаешь и зачем они нужны?» — вопрос из категории Базы данных, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нормальные формы (НФ) - это набор правил для проектирования реляционных баз данных, которые минимизируют избыточность данных и аномалии при операциях вставки, обновления и удаления. Вот основные нормальные формы, которые я применяю на практике:

1. Первая нормальная форма (1NF) Требования:

  • Все значения в столбцах атомарны (неделимы)
  • Нет повторяющихся групп или массивов
  • Каждая запись уникальна

Пример нарушения 1NF:

-- Плохо: телефонные номера как строка с разделителями
CREATE TABLE contacts (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    phones VARCHAR(255) -- Может содержать "+7-999-123-45-67, +7-888-987-65-43"
);

Исправление:

-- Хорошо: отдельная таблица для телефонов
CREATE TABLE contacts (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

CREATE TABLE contact_phones (
    id INT PRIMARY KEY,
    contact_id INT,
    phone VARCHAR(20),
    FOREIGN KEY (contact_id) REFERENCES contacts(id)
);

2. Вторая нормальная форма (2NF) Требования:

  • Должна быть 1NF
  • Все неключевые атрибуты полностью зависят от всего первичного ключа (нет частичных зависимостей)

Пример нарушения 2NF (составной первичный ключ):

-- Заказ товаров: (order_id, product_id) - составной PK
CREATE TABLE order_items (
    order_id INT,
    product_id INT,
    product_name VARCHAR(100), -- Зависит только от product_id, а не от всего PK
    quantity INT,
    price DECIMAL(10,2),
    PRIMARY KEY (order_id, product_id)
);

Исправление:

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

-- product_name вынесен в отдельную таблицу
CREATE TABLE products (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    -- другие атрибуты продукта
);

3. Третья нормальная форма (3NF) Требования:

  • Должна быть 2NF
  • Нет транзитивных зависимостей (неключевые атрибуты не зависят от других неключевых атрибутов)

Пример нарушения 3NF:

CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    department_id INT,
    department_name VARCHAR(100), -- Зависит от department_id, а не от id сотрудника
    manager_name VARCHAR(100)
);

Исправление:

CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    department_id INT,
    FOREIGN KEY (department_id) REFERENCES departments(id)
);

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

4. Нормальная форма Бойса-Кодда (BCNF) Требования:

  • Усиленная 3NF
  • Для любой нетривиальной зависимости X → Y, X должен быть суперключом

Пример нарушения BCNF:

-- Курс, преподаватель, аудитория
-- Предположения: один преподаватель ведет курс в одной аудитории
-- В одной аудитории может быть несколько курсов
CREATE TABLE schedule (
    course_id INT,
    teacher_id INT,
    room_id INT,
    PRIMARY KEY (course_id, teacher_id)
);
-- Зависимость: course_id → room_id, но course_id не суперключ

Практическое применение: В реальных проектах я обычно довожу базу до 3NF, так как это оптимальный баланс между нормализацией и производительностью. BCNF и более высокие формы применяю точечно, где это критично для целостности данных.

Когда можно отступать от нормальных форм:

  1. Денормализация для производительности - дублирование данных для ускорения чтения
  2. Исторические данные - когда нужно сохранить "снимок" состояния на момент времени
  3. Reporting/OLAP системы - где важна скорость агрегации, а не обновления

Пример денормализации для отчетов:

-- Нормализованная структура
CREATE TABLE orders (
    id INT PRIMARY KEY,
    customer_id INT,
    total_amount DECIMAL(10,2),
    created_at TIMESTAMP
);

CREATE TABLE customers (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    city VARCHAR(50)
);

-- Денормализованная для отчетов
CREATE TABLE order_reports (
    order_id INT PRIMARY KEY,
    customer_name VARCHAR(100), -- Дублирование из customers
    customer_city VARCHAR(50),  -- Дублирование из customers
    order_amount DECIMAL(10,2),
    order_date DATE
);

Нормальные формы - это инструмент, а не догма. Важно понимать, когда их применять, а когда сознательно отступать для решения конкретных задач.