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

Ответ

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

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
);

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

Ответ 18+ 🔞

Ну что, дружище, слушай сюда, сейчас я тебе на пальцах объясню про эти ваши нормальные формы, а то народ их как мантру повторяет, а на деле-то нихуя не понимает, зачем они нужны. Это ж просто правила, чтобы твоя база данных не превратилась в такое говно, в котором сам чёрт ногу сломит.

1. Первая нормальная форма (1NF) Суть: Всё должно быть по полочкам, а не в одну кучу.

  • Каждая ячейка — одно значение, а не список через запятую, ёпта.
  • Никаких повторяющихся столбцов типа phone1, phone2, phone3.
  • Все строки уникальные.

Пример, где всё пошло по пизде:

-- Смотри, какой пиздец: телефоны впихнули в одну строку, как попало
CREATE TABLE contacts (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    phones VARCHAR(255) -- Тут может быть "8-800, 8-900, 8-666", и как это искать, а?
);

Представляешь, ищешь номер, а он у тебя приклеен к другим в этой строке? Это же пиздопроебибна полная.

Как надо, чтобы не было стыдно:

-- Нормальные люди так делают: отдельная табличка для телефонов
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) Суть: Если у тебя ключ составной (из нескольких полей), то все остальные поля должны зависеть от ВСЕГО ключа, а не от его кусочка. А то будет хитрая жопа.

Пример нарушения, где мы чувствуем подозрение, ёбать:

-- Составной первичный ключ (order_id, product_id)
CREATE TABLE order_items (
    order_id INT,
    product_id INT,
    product_name VARCHAR(100), -- Стой! Это поле зависит ТОЛЬКО от product_id! От order_id ему похуй!
    quantity INT,
    price DECIMAL(10,2),
    PRIMARY KEY (order_id, product_id)
);

Видишь подвох? product_name привязан только к 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)
);

-- А название товара живёт тут, в одном-единственном месте
CREATE TABLE products (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

Теперь поменял название в одном месте — и на всех заказах оно как бы «историческое», сохранилось старое, и новое везде актуальное. Умно? Умно.

3. Третья нормальная форма (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. Если упрощённо: любое поле, от которого что-то зависит, само должно быть уникальным ключом.

На практике: До 3NF доводить — это святое. А вот BCNF — это уже по обстоятельствам. Иногда погоня за идеальной нормализацией убивает производительность. Тут нужно думать головой, а не правилами слепо руководствоваться.

Когда можно и нужно нарушать эти правила?

  1. Денормализация для скорости. Читать из десяти связанных таблиц — долго. Иногда проще продублировать пару полей в одну таблицу для отчётов, чтобы не делать овердохуища джойнов. Главное — понимать, что теперь за этими дублями нужно следить.
  2. Исторические данные. Если в заказе должна остаться цена товара именно на момент покупки, то ты не можешь просто ссылаться на products.price. Придётся скопировать её в заказ, нарушив 2NF. И это будет правильно.
  3. Аналитические системы (OLAP). Там вообще всё часто в одну огромную плоскую таблицу сваливают, потому что цель — быстро считать агрегаты, а не аккуратно обновлять.

Пример сознательного греха (денормализация):

-- Идеально нормализованные заказы и клиенты
CREATE TABLE orders (...);
CREATE TABLE customers (...);

-- А это — специально денормализованная таблица для быстрых отчётов менеджеру
CREATE TABLE order_reports (
    order_id INT PRIMARY KEY,
    customer_name VARCHAR(100), -- Дублируем из customers, ибо похуй
    customer_city VARCHAR(50),  -- Ещё раз дублируем, ибо запросы должны летать
    order_amount DECIMAL(10,2),
    order_date DATE
);

Менеджеру нужно видеть имя и город клиента сразу в отчёте, а не ждать, пока система десять таблиц соединит. Мы ж не садисты.

Вывод, чувак: Нормальные формы — это как правила дорожного движения. Знать их надо обязательно, чтобы не устроить аварию в своей же базе. Но иногда, чтобы объехать пробку (решить проблему производительности), можно и по обочине проехать (слегка денормализовать). Главное — делать это осознанно, а не потому что ты распиздяй и тебе влом было нормальную схему делать. Понял да?