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

Что такое вторая нормальная форма базы данных?

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

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

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

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

Вторая нормальная форма базы данных (2NF)

Нормализация базы данных — это фундаментальный concept, который я считаю must-know для любого Java Developer. Вторая нормальная форма (2NF) часто вызывает confusion, потому что её определение звучит complexity. За 10+ лет я видел how bad database design убивает приложения. Давайте разберёмся

Что такое 1NF (First Normal Form)?

Прежде чем говорить о 2NF, нужно понимать 1NF:

1NF требует:

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

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

Таблица: Orders (НЕПРАВИЛЬНО)
┌──────────┬──────────────┐
│ order_id │ product_ids  │
├──────────┼──────────────┤
│ 1        │ "1, 2, 5"    │  ← Строка содержит список!
│ 2        │ "3"          │
└──────────┴──────────────┘

Это нарушает 1NF, потому что product_ids не atomic.

Правильно (1NF):

Таблица: Orders
┌──────────┬──────────┐
│ order_id │ order_no │
├──────────┼──────────┤
│ 1        │ ORD-001  │
│ 2        │ ORD-002  │
└──────────┴──────────┘

Таблица: Order_Items
┌──────────┬──────────────┐
│ order_id │ product_id   │
├──────────┼──────────────┤
│ 1        │ 1            │  ← Atomic!
│ 1        │ 2            │
│ 1        │ 5            │
│ 2        │ 3            │
└──────────┴──────────────┘

Что такое 2NF (Second Normal Form)?

2NF требует:

  1. Таблица должна быть в 1NF
  2. Все non-key атрибуты должны зависеть от PRIMARY KEY полностью (full functional dependency)
  3. Нет partial dependencies (зависимости от части составного ключа)

Проще: Если у вас composite primary key (из нескольких колонок), все остальные колонки должны зависеть от ВСЕГО ключа, не от его части.

Пример нарушения 2NF

Таблица: Student_Courses (НЕПРАВИЛЬНО - нарушает 2NF)
┌────────────┬──────────────┬─────────────┬──────────────┐
│ student_id │ course_id    │ student_name│ instructor   │
├────────────┼──────────────┼─────────────┼──────────────┤
│ 1          │ JAVA-101     │ John Smith  │ Dr. Anderson │
│ 1          │ JAVA-101     │ John Smith  │ Dr. Anderson │  ← Duplicate!
│ 1          │ PYTHON-201   │ John Smith  │ Dr. Brown    │
│ 2          │ JAVA-101     │ Jane Doe    │ Dr. Anderson │
└────────────┴──────────────┴─────────────┴──────────────┘

Проблемы:
- Primary key: (student_id, course_id)
- student_name зависит ТОЛЬКО от student_id (не от course_id)
- instructor зависит ТОЛЬКО от course_id (не от student_id)
- Это partial dependencies!

Почему это плохо?

  1. Redundancy: John Smith повторяется в каждой его строке
  2. Update anomaly: Если John Smith переехал и изменился адрес, нужно обновить все строки
  3. Insert anomaly: Не можно добавить нового студента без курса
  4. Delete anomaly: Если удалить последний курс студента, удалится и студент

Как привести к 2NF

Разбиваем одну таблицу на три:

Таблица: Students (2NF)
┌────────────┬──────────────┐
│ student_id │ student_name │
├────────────┼──────────────┤
│ 1          │ John Smith   │
│ 2          │ Jane Doe     │
└────────────┴──────────────┘

Таблица: Courses (2NF)
┌────────────┬──────────────┐
│ course_id  │ instructor   │
├────────────┼──────────────┤
│ JAVA-101   │ Dr. Anderson │
│ PYTHON-201 │ Dr. Brown    │
└────────────┴──────────────┘

Таблица: Student_Enrollments (2NF) - junction table
┌────────────┬────────────┐
│ student_id │ course_id  │  ← Primary key: (student_id, course_id)
├────────────┼────────────┤
│ 1          │ JAVA-101   │
│ 1          │ PYTHON-201 │
│ 2          │ JAVA-101   │
└────────────┴────────────┘

Теперь:

  • Каждый атрибут зависит от полного primary key
  • Нет дублирования
  • Updates, inserts, deletes работают без problems

Как это выглядит в JPA/Hibernate

// Сущность Student
@Entity
@Table(name = "students")
public class Student {
    @Id
    private Long studentId;
    
    private String studentName;
    
    @ManyToMany
    @JoinTable(
        name = "student_enrollments",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private Set<Course> courses = new HashSet<>();
}

// Сущность Course
@Entity
@Table(name = "courses")
public class Course {
    @Id
    private String courseId;
    
    private String instructor;
    
    @ManyToMany(mappedBy = "courses")
    private Set<Student> students = new HashSet<>();
}

// Использование
Student student = new Student();
student.setStudentId(1L);
student.setStudentName("John Smith");

Course javaCourse = new Course();
javaCourse.setCourseId("JAVA-101");
javaCourse.setInstructor("Dr. Anderson");

student.getCourses().add(javaCourse);
studentRepository.save(student);

2NF vs 3NF vs BCNF

2NF: All non-key attributes depend on the whole key, not on part of it
     (Removes partial dependencies)
     
3NF: In addition to 2NF, non-key attributes must depend DIRECTLY on key
     (Removes transitive dependencies)
     Example: student_id → student_name; student_name → advisor_id
     This is transitive! 3NF требует это разделить
     
BCNF: Every determinant must be a candidate key
      (Strictest form of normalization)

Реальный пример из моего опыта

Я работал над системой управления хранилищем товаров:

НЕЦ (нарушение 2NF):
Inventory_Bad:
┌──────────┬──────────────┬──────────┬─────────────┐
│warehouse_id│product_id   │quantity  │warehouse_city
├──────────┼──────────────┼──────────┼─────────────┤
│ W1       │ P001         │ 100      │ Moscow      │
│ W1       │ P002         │ 50       │ Moscow      │  ← Redundant!
│ W2       │ P001         │ 75       │ St.Petersburg│
└──────────┴──────────────┴──────────┴─────────────┘

Проблема: warehouse_city зависит ТОЛЬКО от warehouse_id

Правильно:

Warehouses (2NF):
┌──────────┬─────────────┐
│warehouse_id│warehouse_city
├──────────┼─────────────┤
│ W1       │ Moscow      │
│ W2       │ St.Petersburg
└──────────┴─────────────┘

Inventory (2NF):
┌──────────┬──────────────┬──────────┐
│warehouse_id│product_id   │quantity  │
├──────────┼──────────────┼──────────┤
│ W1       │ P001         │ 100      │
│ W1       │ P002         │ 50       │
│ W2       │ P001         │ 75       │
└──────────┴──────────────┴──────────┘

Результат: Сняли 30% дублирования, тесты быстрее, обновления проще.

Практические советы

1. Всегда спрашивайте себя при составном ключе:

Есть ли столбцы, которые зависят только от ЧАСТИ ключа?
Если да — разделите таблицу.

2. Инструменты для анализа:

-- Посмотреть все foreign keys
SELECT constraint_name, table_name 
FROM information_schema.table_constraints 
WHERE constraint_type = 'PRIMARY KEY';

3. Не переусложняйте: 2NF достаточна для большинства приложений. 3NF для аналитики. BCNF редко нужна.

Вывод

2NF — это второй уровень нормализации, который удаляет partial dependencies. Если у вас composite primary key, убедитесь что ВСЕ остальные атрибуты зависят от ВСЕГО ключа, не от его части. Это prevent data anomalies и делает базу данных чище.

Что такое вторая нормальная форма базы данных? | PrepBro