Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Краткий ответ
Нормализация БД — процесс организации данных в таблицы для уменьшения дублирования и повышения целостности. Есть нормальные формы (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"
Проблемы:
- Дублирование - информация о John повторяется
- Аномалии обновления - обновить email John нужно в нескольких строках
- Потеря данных - если удалить заказ John, потеряется его контакт
- Сложность поиска - сложно найти все товары из товара "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.