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

Приведи пример, когда нужна нормализация в БД

1.0 Junior🔥 251 комментариев
#Python Core

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

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

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

# Нормализация базы данных: примеры и использование

Нормализация — это процесс структурирования данных в БД для устранения дублирования и обеспечения целостности. Давайте рассмотрим практические примеры проблем и их решений.

1. Проблема: Денормализованные данные

Плохой пример (ненормализованная таблица)

-- Таблица students с дублированием данных курса
CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    course_name VARCHAR(100),
    course_teacher VARCHAR(100),
    course_description TEXT
);

-- Данные
INSERT INTO students VALUES
    (1, 'John', 'Python', 'John Doe', 'Learn Python programming'),
    (2, 'Jane', 'Python', 'John Doe', 'Learn Python programming'),
    (3, 'Bob', 'Python', 'John Doe', 'Learn Python programming'),
    (4, 'Alice', 'JavaScript', 'Jane Smith', 'Learn JavaScript');

Проблемы:

  1. Дублирование данных — информация о курсе повторяется для каждого студента
  2. Аномалия обновления — если изменить учителя курса, нужно обновить все строки
  3. Аномалия удаления — если удалить последнего студента Python, потеряем информацию о курсе
  4. Потеря места — тратим дополнительное место на хранение

2. Решение: Нормализация до 3NF

Нормализованная схема

-- 1. Таблица курсов
CREATE TABLE courses (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) UNIQUE,
    teacher VARCHAR(100),
    description TEXT
);

-- 2. Таблица студентов
CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    course_id INTEGER NOT NULL,
    FOREIGN KEY (course_id) REFERENCES courses(id)
);

-- Данные
INSERT INTO courses VALUES
    (1, 'Python', 'John Doe', 'Learn Python programming'),
    (2, 'JavaScript', 'Jane Smith', 'Learn JavaScript');

INSERT INTO students VALUES
    (1, 'John', 1),
    (2, 'Jane', 1),
    (3, 'Bob', 1),
    (4, 'Alice', 2);

Преимущества:

Нет дублирования — данные о курсе хранятся один раз ✓ Безопасность обновлений — изменение курса в одном месте ✓ Целостность данных — FOREIGN KEY гарантирует связь ✓ Экономия места — меньше дублирования

-- SQL запрос с нормализованной схемой
SELECT s.name, c.name as course, c.teacher
FROM students s
JOIN courses c ON s.course_id = c.id
WHERE c.name = 'Python';

3. Форма нормализации (Normal Forms)

1NF (First Normal Form)

Правило: Все значения должны быть атомарными (неделимыми)

-- ❌ Плохо: Список телефонов в одном поле
CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    phones VARCHAR(255)  -- '123-4567, 234-5678'
);

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

CREATE TABLE customer_phones (
    id SERIAL PRIMARY KEY,
    customer_id INTEGER,
    phone VARCHAR(20),
    FOREIGN KEY (customer_id) REFERENCES customers(id)
);

2NF (Second Normal Form)

Правило: Должна быть 1NF + все неключевые атрибуты зависят от PRIMARY KEY

-- ❌ Плохо: Город зависит от курса, а не от студента
CREATE TABLE enrollments (
    student_id INTEGER,
    course_id INTEGER,
    course_name VARCHAR(100),  -- зависит от course_id
    course_city VARCHAR(100),  -- зависит от course_id
    PRIMARY KEY (student_id, course_id)
);

-- ✓ Хорошо: Выносим информацию о курсе в отдельную таблицу
CREATE TABLE courses (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    city VARCHAR(100)
);

CREATE TABLE enrollments (
    student_id INTEGER,
    course_id INTEGER,
    PRIMARY KEY (student_id, course_id),
    FOREIGN KEY (course_id) REFERENCES courses(id)
);

3NF (Third Normal Form)

Правило: Должна быть 2NF + никакой неключевой атрибут не зависит от другого неключевого атрибута

-- ❌ Плохо: city_code зависит от city (неключевые атрибуты зависят друг от друга)
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    city VARCHAR(100),
    city_code VARCHAR(10)  -- зависит от city, не от id
);

-- ✓ Хорошо: Выносим города в отдельную таблицу
CREATE TABLE cities (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    code VARCHAR(10)
);

CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    city_id INTEGER,
    FOREIGN KEY (city_id) REFERENCES cities(id)
);

4. Реальный пример: E-commerce система

Плохой дизайн (ненормализованный)

-- Одна таблица с дублированием
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_name VARCHAR(100),
    customer_email VARCHAR(100),
    customer_phone VARCHAR(20),
    product_name VARCHAR(100),
    product_price DECIMAL(10,2),
    product_category VARCHAR(100),
    product_description TEXT,
    quantity INTEGER,
    order_date TIMESTAMP
);

-- Проблемы:
-- - Если изменить цену продукта, нужно обновить все заказы
-- - Если удалить последний заказ продукта, потеряем информацию о продукте
-- - Много дублирования информации о customer

Правильный дизайн (нормализованный)

-- 1. Таблица клиентов
CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,
    phone VARCHAR(20)
);

-- 2. Таблица категорий
CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) UNIQUE NOT NULL
);

-- 3. Таблица продуктов
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    category_id INTEGER NOT NULL,
    description TEXT,
    FOREIGN KEY (category_id) REFERENCES categories(id),
    INDEX idx_category_id (category_id)
);

-- 4. Таблица заказов
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_id INTEGER NOT NULL,
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (customer_id) REFERENCES customers(id),
    INDEX idx_customer_id (customer_id)
);

-- 5. Таблица деталей заказа (связь "много ко многим")
CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INTEGER NOT NULL,
    product_id INTEGER NOT NULL,
    quantity INTEGER NOT NULL CHECK (quantity > 0),
    price_at_purchase DECIMAL(10,2) NOT NULL,  -- Историческая цена
    FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
    FOREIGN KEY (product_id) REFERENCES products(id),
    UNIQUE (order_id, product_id),
    INDEX idx_order_id (order_id),
    INDEX idx_product_id (product_id)
);

-- Вставка данных
INSERT INTO customers (name, email, phone) VALUES
    ('John Doe', 'john@example.com', '123-4567'),
    ('Jane Smith', 'jane@example.com', '234-5678');

INSERT INTO categories (name) VALUES
    ('Electronics'),
    ('Books');

INSERT INTO products (name, price, category_id, description) VALUES
    ('Laptop', 999.99, 1, 'High-performance laptop'),
    ('Mouse', 29.99, 1, 'Wireless mouse'),
    ('Python Book', 39.99, 2, 'Learning Python');

INSERT INTO orders (customer_id) VALUES (1), (2);

INSERT INTO order_items (order_id, product_id, quantity, price_at_purchase) VALUES
    (1, 1, 1, 999.99),  -- Ноутбук
    (1, 2, 2, 29.99),   -- 2 мыши
    (2, 3, 1, 39.99);   -- Книга

5. Запросы на нормализованной схеме

-- Получить все заказы клиента с деталями
SELECT 
    c.name,
    o.order_date,
    p.name as product_name,
    oi.quantity,
    oi.price_at_purchase,
    (oi.quantity * oi.price_at_purchase) as total
FROM customers c
JOIN orders o ON c.id = o.customer_id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE c.id = 1
ORDER BY o.order_date DESC;

-- Получить сумму по категориям
SELECT 
    cat.name as category,
    COUNT(p.id) as product_count,
    SUM(oi.price_at_purchase * oi.quantity) as total_sold
FROM categories cat
LEFT JOIN products p ON cat.id = p.category_id
LEFT JOIN order_items oi ON p.id = oi.product_id
GROUP BY cat.name;

6. Когда можно денормализовать

Нормализация хороша, но есть случаи, когда денормализация оправдана:

✓ Денормализуй когда:

-- Запросы очень медленные из-за множества JOIN'ов
-- Есть кеширование данных
-- Нужна максимальная производительность чтения
-- Данные редко изменяются

-- Пример: Добавить кешированное поле
ALTER TABLE orders ADD COLUMN total_amount DECIMAL(10,2);
-- Это денормализация, но может быть полезно для быстрых отчётов

✗ Не денормализуй когда:

-- Данные часто изменяются (сложно поддерживать синхронизацию)
-- Место на диске критично
-- Простые запросы (медленность не критична)
-- Малый объём данных

7. Проверка нормализации

# Python скрипт для анализа нормализации
from sqlalchemy import inspect

def check_normalization(db_engine):
    """Анализирует степень нормализации БД"""
    inspector = inspect(db_engine)
    
    for table_name in inspector.get_table_names():
        columns = inspector.get_columns(table_name)
        primary_keys = inspector.get_pk_constraint(table_name)['constrained_columns']
        foreign_keys = inspector.get_foreign_keys(table_name)
        
        print(f"Table: {table_name}")
        print(f"  Primary Key: {primary_keys}")
        print(f"  Foreign Keys: {[fk['name'] for fk in foreign_keys]}")
        print(f"  Columns: {[c['name'] for c in columns]}")

Итоги

  1. Нормализация устраняет дублирование и аномалии
  2. 3NF — стандарт для большинства приложений
  3. Разделяй данные по отдельным таблицам
  4. Используй FOREIGN KEY для целостности
  5. Денормализуй редко — только когда нужна производительность
  6. Всегда думай о том, как будут изменяться данные

Помни: Правильная нормализация — это инвестиция в качество и поддерживаемость!