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

Приведи пример применения нормализации

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

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

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

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

Краткий ответ

Нормализация БД — процесс организации данных в таблицы для уменьшения дублирования и повышения целостности. Есть нормальные формы (1NF, 2NF, 3NF, BCNF). Приведу примеры преобразования ненормализованной структуры к нормализованной.

Проблема: Ненормализованные данные

Представь, что хранишь информацию о заказах как одну таблицу:

CREATE TABLE orders_denormalized (
    order_id INT,
    customer_name VARCHAR(100),
    customer_email VARCHAR(100),
    customer_phone VARCHAR(20),
    product_names VARCHAR(500),  -- Храним строку вроде "Laptop, Mouse, Monitor"
    product_prices VARCHAR(500),  -- Храним строку вроде "800.00, 25.00, 300.00"
    total_amount DECIMAL(10, 2)
);

ИЗ примере данных:
order_id=1 | customer_name="John" | products="Laptop, Mouse" | prices="800.00, 25.00"
order_id=2 | customer_name="John" | products="Laptop, Mouse" | prices="800.00, 25.00"

Проблемы:

  1. Дублирование - информация о John повторяется
  2. Аномалии обновления - обновить email John нужно в нескольких строках
  3. Потеря данных - если удалить заказ John, потеряется его контакт
  4. Сложность поиска - сложно найти все товары из товара "Laptop"

Шаг 1: Первая нормальная форма (1NF)

Убираем повторяющиеся группы (список товаров в одной колонке):

-- ДО: неатомарные данные
CREATE TABLE orders_before_1nf (
    order_id INT,
    customer_name VARCHAR(100),
    product_names VARCHAR(500)  -- Это список, а не одно значение!
);

-- ПОСЛЕ: каждый товар в отдельной строке
CREATE TABLE orders_1nf (
    order_id INT,
    customer_name VARCHAR(100),
    product_name VARCHAR(100)
);

-- Данные были:
order_id=1 | customer="John" | product="Laptop, Mouse, Monitor"

-- Стали:
order_id=1 | customer="John" | product="Laptop"
order_id=1 | customer="John" | product="Mouse"
order_id=1 | customer="John" | product="Monitor"
order_id=2 | customer="Jane" | product="Laptop"

Шаг 2: Вторая нормальная форма (2NF)

Убираем зависимости неключевых атрибутов от части составного ключа:

-- ДО: customer_name зависит от order_id, но не от product_name
CREATE TABLE orders_1nf (
    order_id INT,
    product_name VARCHAR(100),
    customer_name VARCHAR(100),  -- Информация о customer повторяется!
    PRIMARY KEY (order_id, product_name)
);

-- ПОСЛЕ: разделяем на две таблицы
CREATE TABLE customers (
    customer_id INT PRIMARY KEY,
    customer_name VARCHAR(100)
);

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

CREATE TABLE order_items (
    order_id INT REFERENCES orders(order_id),
    product_name VARCHAR(100),
    PRIMARY KEY (order_id, product_name)
);

Шаг 3: Третья нормальная форма (3NF)

Убираем зависимости между неключевыми атрибутами (транзитивные зависимости):

-- ДО: category_name зависит от product_name (не от ключа)
CREATE TABLE products (
    product_id INT PRIMARY KEY,
    product_name VARCHAR(100),
    category_name VARCHAR(100),  -- Зависит от product_name
    category_description TEXT     -- Зависит от category_name
);

-- Проблема: если "Electronics" переименуется, нужно обновить везде

-- ПОСЛЕ: выносим category в отдельную таблицу
CREATE TABLE categories (
    category_id INT PRIMARY KEY,
    category_name VARCHAR(100),
    category_description TEXT
);

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

Полный пример: Интернет-магазин

ДО нормализации (ПЛОХО)

# Всё в одной таблице - дублирование и аномалии
denormalized_orders = [
    {
        'order_id': 1,
        'customer_name': 'John Doe',
        'customer_email': 'john@example.com',
        'customer_city': 'New York',
        'product_1_name': 'Laptop',
        'product_1_price': 800.00,
        'product_1_category': 'Electronics',
        'product_2_name': 'Mouse',
        'product_2_price': 25.00,
        'product_2_category': 'Electronics',
        'total': 825.00,
        'order_date': '2024-01-15'
    },
    {
        'order_id': 2,
        'customer_name': 'John Doe',  # Дублирование!
        'customer_email': 'john@example.com',  # Дублирование!
        'customer_city': 'New York',  # Дублирование!
        'product_1_name': 'Monitor',
        'product_1_price': 300.00,
        'product_1_category': 'Electronics',  # Дублирование!
        'product_2_name': None,
        'product_2_price': None,
        'product_2_category': None,
        'total': 300.00,
        'order_date': '2024-01-20'
    }
]

ПОСЛЕ нормализации (ХОРОШО)

-- Таблица 1: Покупатели
CREATE TABLE customers (
    customer_id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,
    city VARCHAR(100)
);

-- Таблица 2: Категории
CREATE TABLE categories (
    category_id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL UNIQUE,
    description TEXT
);

-- Таблица 3: Товары
CREATE TABLE products (
    product_id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    category_id INT REFERENCES categories(category_id)
);

-- Таблица 4: Заказы
CREATE TABLE orders (
    order_id SERIAL PRIMARY KEY,
    customer_id INT NOT NULL REFERENCES customers(customer_id),
    order_date TIMESTAMP DEFAULT NOW(),
    total DECIMAL(10, 2)
);

-- Таблица 5: Товары в заказе (связующая таблица)
CREATE TABLE order_items (
    order_item_id SERIAL PRIMARY KEY,
    order_id INT NOT NULL REFERENCES orders(order_id),
    product_id INT NOT NULL REFERENCES products(product_id),
    quantity INT DEFAULT 1,
    price_at_purchase DECIMAL(10, 2) NOT NULL,
    UNIQUE(order_id, product_id)
);

Вставка данных

from sqlalchemy import create_engine, Column, Integer, String, DECIMAL, DateTime, ForeignKey
from sqlalchemy.orm import declarative_base, Session, relationship
from datetime import datetime

Base = declarative_base()

class Customer(Base):
    __tablename__ = 'customers'
    customer_id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    email = Column(String(100), unique=True)
    city = Column(String(100))
    orders = relationship('Order', back_populates='customer')

class Category(Base):
    __tablename__ = 'categories'
    category_id = Column(Integer, primary_key=True)
    name = Column(String(100), unique=True)
    products = relationship('Product', back_populates='category')

class Product(Base):
    __tablename__ = 'products'
    product_id = Column(Integer, primary_key=True)
    name = Column(String(100))
    price = Column(DECIMAL(10, 2))
    category_id = Column(Integer, ForeignKey('categories.category_id'))
    category = relationship('Category', back_populates='products')
    order_items = relationship('OrderItem', back_populates='product')

class Order(Base):
    __tablename__ = 'orders'
    order_id = Column(Integer, primary_key=True)
    customer_id = Column(Integer, ForeignKey('customers.customer_id'))
    order_date = Column(DateTime, default=datetime.now)
    customer = relationship('Customer', back_populates='orders')
    items = relationship('OrderItem', back_populates='order')

class OrderItem(Base):
    __tablename__ = 'order_items'
    order_item_id = Column(Integer, primary_key=True)
    order_id = Column(Integer, ForeignKey('orders.order_id'))
    product_id = Column(Integer, ForeignKey('products.product_id'))
    quantity = Column(Integer, default=1)
    price_at_purchase = Column(DECIMAL(10, 2))
    order = relationship('Order', back_populates='items')
    product = relationship('Product', back_populates='order_items')

# Использование
engine = create_engine('postgresql://user:pass@localhost/shop')
Base.metadata.create_all(engine)

with Session(engine) as session:
    # Создаём категорию
    electronics = Category(name='Electronics', description='Electronic devices')
    session.add(electronics)
    session.flush()  # Получить ID
    
    # Создаём товары (без дублирования информации о категории)
    laptop = Product(name='Laptop', price=800.00, category_id=electronics.category_id)
    mouse = Product(name='Mouse', price=25.00, category_id=electronics.category_id)
    monitor = Product(name='Monitor', price=300.00, category_id=electronics.category_id)
    
    session.add_all([laptop, mouse, monitor])
    session.flush()
    
    # Создаём покупателя (информация хранится один раз)
    customer = Customer(name='John Doe', email='john@example.com', city='New York')
    session.add(customer)
    session.flush()
    
    # Создаём заказ
    order1 = Order(customer_id=customer.customer_id)
    order1.items.append(OrderItem(product_id=laptop.product_id, quantity=1, price_at_purchase=800.00))
    order1.items.append(OrderItem(product_id=mouse.product_id, quantity=2, price_at_purchase=25.00))
    session.add(order1)
    
    order2 = Order(customer_id=customer.customer_id)
    order2.items.append(OrderItem(product_id=monitor.product_id, quantity=1, price_at_purchase=300.00))
    session.add(order2)
    
    session.commit()
    
    # Запросы после нормализации
    # Найти все заказы John
    customer = session.query(Customer).filter(Customer.email == 'john@example.com').first()
    for order in customer.orders:
        print(f'Order {order.order_id}:')
        for item in order.items:
            print(f'  - {item.product.name}: {item.quantity} x ${item.price_at_purchase}')
    
    # Найти все Electronics товары
    electronics = session.query(Category).filter(Category.name == 'Electronics').first()
    for product in electronics.products:
        print(f'{product.name}: ${product.price}')

Преимущества нормализации

✅ Нет дублирования данных
✅ Легко обновлять информацию (обновляем одно место)
✅ Защита целостности (foreign keys)
✅ Меньше место на диске
✅ Быстрее запросы поиска
✅ Поддерживать легче

Когда денормализировать (intentional)

-- Иногда денормализируем для производительности
CREATE TABLE order_summaries (
    order_id INT REFERENCES orders(order_id),
    customer_name VARCHAR(100),  -- Копируем для быстрого доступа
    total_amount DECIMAL(10, 2),
    item_count INT
);

-- Это хранилище для отчётов, основной данные остаются нормализованные

Вывод: Нормализация (1NF, 2NF, 3NF) организует данные в разные таблицы, убирая дублирование и аномалии обновления. Используй нормализацию при проектировании БД, денормализируй только если понимаешь причину и готов к consequences.