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

Что такое вторая нормальная форма в БД?

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

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

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

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

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

Вторая нормальная форма — это требование к проектированию реляционных баз данных. Она базируется на понятии функциональной зависимости и полностью решает проблемы аномалий частичной зависимости.

1. Определение 2NF

Таблица находится во второй нормальной форме, если:

  1. Она находится в первой нормальной форме (1NF)
  2. Все неключевые атрибуты полностью функционально зависимы от первичного ключа

Полная функциональная зависимость означает:

  • Атрибут зависит от всего первичного ключа, а не от части ключа
# Пример: Таблица Orders (не в 2NF)
Orders = {
    "order_id": 1,      # Первичный ключ (часть)
    "product_id": 100,  # Первичный ключ (часть)
    "quantity": 5,      # Зависит от ВСЕГО (order_id, product_id) ✓
    "product_name": "Laptop",  # Зависит ТОЛЬКО от product_id ✗
    "product_price": 999.99    # Зависит ТОЛЬКО от product_id ✗
}

# Проблема: product_name и product_price зависят только от product_id,
# а не от составного ключа (order_id, product_id)

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

# ❌ Плохо: нарушает 2NF

# Таблица: StudentCourses
StudentCourses = [
    {
        "student_id": 1,      # Первичный ключ (часть)
        "course_id": 101,     # Первичный ключ (часть)
        "grade": "A",         # Полностью зависит от (student_id, course_id) ✓
        "professor_name": "Dr. Smith",  # Зависит ТОЛЬКО от course_id ✗
        "professor_office": "Room 301"  # Зависит ТОЛЬКО от course_id ✗
    }
]

# Проблема:
# professor_name и professor_office зависят только от course_id,
# а не от ВСЕГО первичного ключа (student_id, course_id)

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

3. Приведение к 2NF

Решение: разложить таблицу на несколько таблиц.

# ✅ Хорошо: приведено к 2NF

# Таблица 1: StudentCourses (содержит ТОЛЬКО зависимости от всего ключа)
StudentCourses = [
    {
        "student_id": 1,
        "course_id": 101,
        "grade": "A"        # Зависит от ВСЕГО ключа
    }
]

# Таблица 2: Courses (содержит зависимости от course_id)
Courses = [
    {
        "course_id": 101,
        "course_name": "Python Programming",
        "professor_id": 1   # Внешний ключ
    }
]

# Таблица 3: Professors (содержит зависимости от professor_id)
Professors = [
    {
        "professor_id": 1,
        "professor_name": "Dr. Smith",
        "professor_office": "Room 301"
    }
]

4. SQL примеры

-- ❌ Плохо: нарушает 2NF
CREATE TABLE Orders (
    order_id INT,
    product_id INT,
    quantity INT,
    product_name VARCHAR(100),  -- Зависит только от product_id
    product_price DECIMAL(10,2), -- Зависит только от product_id
    PRIMARY KEY (order_id, product_id)
);

-- ✅ Хорошо: приведено к 2NF
CREATE TABLE Orders (
    order_id INT,
    product_id INT,
    quantity INT,
    PRIMARY KEY (order_id, product_id),
    FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

CREATE TABLE Products (
    product_id INT PRIMARY KEY,
    product_name VARCHAR(100),
    product_price DECIMAL(10,2)
);

5. Частичная зависимость vs Полная зависимость

# Первичный ключ: (student_id, semester)

# ✓ Полная зависимость (все атрибуты в 2NF)
# - grade зависит от (student_id, semester) целиком
# - attendance зависит от (student_id, semester) целиком
# - Нельзя узнать grade зная только student_id или только semester

# ✗ Частичная зависимость (нарушение 2NF)
# - student_name зависит только от student_id (часть ключа)
# - student_email зависит только от student_id (часть ключа)
# - semester_start_date зависит только от semester (часть ключа)
# - semester_end_date зависит только от semester (часть ключа)

# Решение: создать отдельные таблицы
# Students (student_id, student_name, student_email)
# Semesters (semester, semester_start_date, semester_end_date)
# Enrollments (student_id, semester, grade, attendance)

6. Python пример: проверка 2NF

from typing import List, Dict, Set, Tuple

def check_2nf(table_name: str, data: List[Dict], 
              primary_key: List[str], 
              non_key_attributes: List[str]) -> bool:
    """
    Проверяет, находится ли таблица во второй нормальной форме.
    
    2NF нарушается если:
    - Неключевой атрибут зависит только от части первичного ключа
    """
    
    # Для каждого подмножества первичного ключа
    for key_part_size in range(1, len(primary_key)):
        key_subsets = get_all_subsets(primary_key, key_part_size)
        
        for key_subset in key_subsets:
            # Проверяем, зависит ли какой-то неключевой атрибут ТОЛЬКО от этого подмножества
            for record in data:
                key_values = {k: record[k] for k in key_subset}
                
                for attr in non_key_attributes:
                    # Если два записи имеют одинаковые значения части ключа
                    # и разные значения атрибута, то нарушение 2NF
                    matching_records = [
                        r for r in data 
                        if all(r[k] == key_values[k] for k in key_subset)
                    ]
                    
                    attr_values = set(r[attr] for r in matching_records)
                    if len(attr_values) > 1:
                        # Атрибут зависит от всего ключа, не от части
                        continue
                    
            return False
    
    return True

def get_all_subsets(elements: List, size: int) -> List[Tuple]:
    """Получить все подмножества размера size"""
    from itertools import combinations
    return list(combinations(elements, size))

# Использование
students_courses = [
    {"student_id": 1, "course_id": 101, "grade": "A", "professor": "Dr. Smith"},
    {"student_id": 2, "course_id": 101, "grade": "B", "professor": "Dr. Smith"},
]

is_2nf = check_2nf(
    table_name="StudentCourses",
    data=students_courses,
    primary_key=["student_id", "course_id"],
    non_key_attributes=["grade", "professor"]
)

print(f"Is in 2NF: {is_2nf}")  # False, потому что professor зависит только от course_id

7. Практическое влияние

ПроблемаПричинаРешение
Аномалия обновленияОдин атрибут хранится несколько разНормализация (разложить таблицу)
Аномалия удаленияУдаление одной записи потеряет другие данныеНормализация
Аномалия вставкиНельзя вставить данные без всего ключаНормализация
ИзбыточностьПовторяющиеся данные занимают местоНормализация

Соотношение нормальных форм

1NF ← базовое требование
  ↓
2NF ← нет частичных зависимостей
  ↓
3NF ← нет транзитивных зависимостей
  ↓
BCNF ← все функциональные зависимости от ключей
  ↓
4NF ← разделены многозначные зависимости
  ↓
5NF ← разложение без потерь

Каждая форма более строгая, чем предыдущая.

Выводы

  • 2NF требует: каждый неключевой атрибут зависит от ВСЕГО первичного ключа
  • Решение: разложить таблицы по функциональным зависимостям
  • Преимущества: меньше аномалий обновления, меньше избыточности
  • Применение: практически все modern БД используют минимум 3NF