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

Объясните концепцию нормализации данных и нормальные формы.

2.3 Middle🔥 151 комментариев
#SQL и базы данных

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

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

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

Нормализация Данных и Нормальные Формы

Нормализация — это процесс структурирования реляционной базы данных для минимизации дублирования и повышения целостности данных. Она основана на теории нормальных форм (Normal Forms), каждая из которых имеет всё более строгие требования.

Зачем Нормализация Нужна

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

  • Дублирование данных — одна информация хранится в нескольких местах
  • Аномалии обновления — обновляешь один источник, забываешь другой
  • Аномалии удаления — удаляешь строку и теряешь важную информацию
  • Аномалии вставки — не можешь добавить данные без полной информации
-- ❌ ПЛОХО: Денормализованная таблица
CREATE TABLE orders_bad (
    order_id INT,
    customer_name VARCHAR(100),
    customer_email VARCHAR(100),
    customer_phone VARCHAR(20),
    customer_address VARCHAR(200),
    product_name VARCHAR(100),
    product_price DECIMAL,
    product_category VARCHAR(50),
    order_date DATE
);

-- Проблемы:
-- 1. Если Алиса сделает 5 заказов, её данные повторяются 5 раз
-- 2. Если меняется цена товара, нужно обновить везде
-- 3. Не можешь добавить продукт без заказа

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

Правило 1NF: Атомарность

  • Все значения должны быть атомарными (неделимыми)
  • Нет повторяющихся групп
  • Каждый столбец содержит только один тип данных
-- ❌ Нарушает 1NF: множественные значения в одном столбце
CREATE TABLE students_bad (
    student_id INT PRIMARY KEY,
    name VARCHAR(100),
    phone_numbers VARCHAR(100)  -- '555-1234, 555-5678, 555-9999'
);

-- ✅ Соответствует 1NF: атомарные значения
CREATE TABLE students (
    student_id INT PRIMARY KEY,
    name VARCHAR(100)
);

CREATE TABLE student_phones (
    phone_id INT PRIMARY KEY,
    student_id INT,
    phone_number VARCHAR(20),
    FOREIGN KEY (student_id) REFERENCES students(student_id)
);

Также не соответствует 1NF:

-- ❌ Повторяющиеся колонки
CREATE TABLE projects_bad (
    project_id INT,
    employee_1 VARCHAR(100),
    employee_2 VARCHAR(100),
    employee_3 VARCHAR(100)
);

-- ✅ Соответствует 1NF
CREATE TABLE projects (
    project_id INT,
    project_name VARCHAR(100)
);

CREATE TABLE project_employees (
    project_id INT,
    employee_id INT,
    FOREIGN KEY (project_id) REFERENCES projects(project_id)
);

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

Правило 2NF:

  • Должна быть в 1NF
  • Все неключевые атрибуты должны быть функционально зависимы от всего первичного ключа, а не от его части
-- ❌ Нарушает 2NF: student_name зависит только от student_id, не от (student_id, course_id)
CREATE TABLE enrollments_bad (
    student_id INT,
    course_id INT,
    student_name VARCHAR(100),  -- Зависит только от student_id!
    course_name VARCHAR(100),
    grade CHAR(1),
    PRIMARY KEY (student_id, course_id)
);

-- ✅ Соответствует 2NF: разделили на отдельные таблицы
CREATE TABLE students (
    student_id INT PRIMARY KEY,
    student_name VARCHAR(100)
);

CREATE TABLE courses (
    course_id INT PRIMARY KEY,
    course_name VARCHAR(100)
);

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

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

Правило 3NF:

  • Должна быть в 2NF
  • Неключевые атрибуты не должны зависеть друг от друга (транзитивная зависимость)
-- ❌ Нарушает 3NF: city_name зависит от country_id, а не от employee_id
CREATE TABLE employees_bad (
    employee_id INT PRIMARY KEY,
    employee_name VARCHAR(100),
    department_id INT,
    department_name VARCHAR(100),  -- Зависит от department_id!
    department_head INT
);

-- ✅ Соответствует 3NF: выделили department в отдельную таблицу
CREATE TABLE employees (
    employee_id INT PRIMARY KEY,
    employee_name VARCHAR(100),
    department_id INT,
    FOREIGN KEY (department_id) REFERENCES departments(department_id)
);

CREATE TABLE departments (
    department_id INT PRIMARY KEY,
    department_name VARCHAR(100),
    department_head INT,
    FOREIGN KEY (department_head) REFERENCES employees(employee_id)
);

Высшие Нормальные Формы

Boyce-Codd Normal Form (BCNF)

Если есть множественные кандидаты на первичный ключ, то каждый детерминант должен быть кандидатом на первичный ключ.

-- ❌ Нарушает BCNF
CREATE TABLE professor_course_time (
    professor_id INT,
    course_id INT,
    time TIMESTAMP,
    location VARCHAR(100),
    PRIMARY KEY (professor_id, course_id)
);
-- Проблема: location зависит от course_id, но course_id не является первичным ключом

-- ✅ Соответствует BCNF
CREATE TABLE courses (
    course_id INT PRIMARY KEY,
    course_name VARCHAR(100),
    location VARCHAR(100)
);

CREATE TABLE professor_assignments (
    professor_id INT,
    course_id INT,
    semester VARCHAR(20),
    PRIMARY KEY (professor_id, course_id, semester),
    FOREIGN KEY (course_id) REFERENCES courses(course_id)
);

4NF и 5NF

Эти формы имеют дело с многозначными зависимостями и join зависимостями. В практике используются редко.

Сравнение Нормальных Форм

┌──────┬─────────────────────────────────────────┬─────────────────────────┐
│ НФ   │ Требование                              │ Типичная Проблема       │
├──────┼─────────────────────────────────────────┼─────────────────────────┤
│ 1NF  │ Атомарные значения                      │ Колонки с массивами     │
│ 2NF  │ Нет частичных зависимостей от ключа    │ Композитные ключи       │
│ 3NF  │ Нет транзитивных зависимостей          │ Зависимость между полями│
│ BCNF │ Все детерминанты — первичные ключи     │ Множественные ключи     │
│ 4NF  │ Нет многозначных зависимостей          │ Независимые данные      │
│ 5NF  │ Нет join зависимостей                   │ Сложные связи           │
└──────┴─────────────────────────────────────────┴─────────────────────────┘

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

-- ❌ Денормализованная версия (много дублирования)
CREATE TABLE orders_denorm (
    order_id INT PRIMARY KEY,
    customer_id INT,
    customer_name VARCHAR(100),
    customer_email VARCHAR(100),
    customer_city VARCHAR(100),
    customer_country VARCHAR(100),
    product_id INT,
    product_name VARCHAR(100),
    product_price DECIMAL,
    product_category VARCHAR(50),
    quantity INT,
    order_date DATE
);

-- ✅ Нормализованная версия (3NF)
CREATE TABLE customers (
    customer_id INT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100),
    city_id INT,
    FOREIGN KEY (city_id) REFERENCES cities(city_id)
);

CREATE TABLE cities (
    city_id INT PRIMARY KEY,
    city_name VARCHAR(100),
    country_id INT,
    FOREIGN KEY (country_id) REFERENCES countries(country_id)
);

CREATE TABLE countries (
    country_id INT PRIMARY KEY,
    country_name VARCHAR(100)
);

CREATE TABLE products (
    product_id INT PRIMARY KEY,
    product_name VARCHAR(100),
    price DECIMAL,
    category_id INT,
    FOREIGN KEY (category_id) REFERENCES categories(category_id)
);

CREATE TABLE categories (
    category_id INT PRIMARY KEY,
    category_name VARCHAR(100)
);

CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    customer_id INT,
    order_date DATE,
    FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);

CREATE TABLE order_items (
    order_id INT,
    product_id INT,
    quantity INT,
    PRIMARY KEY (order_id, product_id),
    FOREIGN KEY (order_id) REFERENCES orders(order_id),
    FOREIGN KEY (product_id) REFERENCES products(product_id)
);

Денормализация: Когда Нарушить Правила

Иногда нормализация не нужна:

-- ✅ Допустимая денормализация для performance
CREATE TABLE orders_denormalized (
    order_id INT PRIMARY KEY,
    customer_id INT,
    customer_name VARCHAR(100),  -- Кэш для быстрого доступа
    total_amount DECIMAL,  -- Вычисленное значение, кэшировано
    order_date DATE,
    -- Другие нормализованные данные...
);

-- Причины денормализации:
-- 1. Production читает customer_name очень часто → кэшируем
-- 2. total_amount вычисляется дорого → храним
-- 3. Аналитические запросы требуют JOINов → денормализуем для speed
-- 4. Data Warehouse часто денормализует (snowflake schema или star schema)

Когда Нарушать Нормализацию

Performance критична — кэширование часто используемых JOIN'ов ✅ Data Warehouse — star/snowflake schemas часто денормализованы ✅ NoSQL базы — вообще могут быть денормализованы ✅ Reporting — исторические данные часто снимки ❌ OLTP системы — используй нормализацию ❌ Первоначальный дизайн — сначала нормализуй, потом оптимизируй

Практические Советы для Data Engineers

  1. Начни с 3NF — это sweet spot для большинства приложений
  2. Проверяй транзитивные зависимости — частая ошибка
  3. Документируй денормализацию — если её делаешь, объясни почему
  4. Используй constraints — FOREIGN KEY для обеспечения целостности
  5. Индексируй внешние ключи — для быстрых JOINов
-- Лучшие практики
CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    department_id INT NOT NULL,
    FOREIGN KEY (department_id) REFERENCES departments(id)
);

-- Индексируй внешние ключи
CREATE INDEX idx_employees_department_id ON employees(department_id);

-- Добавляй constraints для данных
ALTER TABLE employees ADD CONSTRAINT salary_positive CHECK (salary > 0);

Нормализация — это фундамент хорошего дизайна БД, но как и все правила, иногда их нужно нарушать для достижения целей production системы.

Объясните концепцию нормализации данных и нормальные формы. | PrepBro