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

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

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

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

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

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

Пример нормализации в виде шаблона

Нормализация данных — процесс преобразования данных в стандартный, согласованный формат. Покажу практические примеры как в базах данных, так и в коде Python.

1. Нормализация в реляционной БД

Денормализованная схема (плохо)

-- Проблемы: дублирование, аномалии обновления, избыточность
CREATE TABLE orders (
    id INT PRIMARY KEY,
    customer_name VARCHAR(100),
    customer_email VARCHAR(100),
    customer_phone VARCHAR(20),
    customer_address VARCHAR(255),
    customer_city VARCHAR(50),
    product_name VARCHAR(100),
    product_price DECIMAL(10,2),
    product_category VARCHAR(50),
    order_date DATE
);

-- Данные: много дублирования
id | customer_name | customer_email | ... | product_name | product_price
1  | John Doe      | john@mail.com  | ... | Laptop       | 999.99
2  | John Doe      | john@mail.com  | ... | Mouse        | 29.99
-- John Doe и его email повторяются!

Нормализованная схема (хорошо) — 3NF

-- Таблица 1: Клиенты
CREATE TABLE customers (
    id INT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,
    phone VARCHAR(20),
    created_at TIMESTAMP
);

-- Таблица 2: Адреса (связь многие-ко-многим)
CREATE TABLE addresses (
    id INT PRIMARY KEY,
    customer_id INT NOT NULL,
    city VARCHAR(50),
    street VARCHAR(100),
    postal_code VARCHAR(10),
    FOREIGN KEY (customer_id) REFERENCES customers(id)
);

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

-- Таблица 4: Заказы
CREATE TABLE orders (
    id INT PRIMARY KEY,
    customer_id INT NOT NULL,
    order_date DATE,
    FOREIGN KEY (customer_id) REFERENCES customers(id)
);

-- Таблица 5: Позиции заказа
CREATE TABLE order_items (
    id INT PRIMARY KEY,
    order_id INT NOT NULL,
    product_id INT NOT NULL,
    quantity INT,
    unit_price DECIMAL(10,2),
    FOREIGN KEY (order_id) REFERENCES orders(id),
    FOREIGN KEY (product_id) REFERENCES products(id)
);

-- Таблица 6: Категории
CREATE TABLE categories (
    id INT PRIMARY KEY,
    name VARCHAR(50) UNIQUE
);

-- Теперь данные не дублируются
-- John Doe хранится один раз в customers
-- Его заказы ссылаются на одну запись

2. Нормализация в Python коде

Денормализованная структура (плохо)

from typing import List, Dict

def process_user_data_bad():
    """Данные распределены везде, сложно обновлять"""
    
    user = {
        "id": 1,
        "name": "John Doe",
        "email": "john@mail.com",
        "address": {
            "street": "123 Main St",
            "city": "New York",
            "zip": "10001"
        },
        "orders": [
            {
                "id": 1,
                "user_name": "John Doe",  # Дублирование!
                "user_email": "john@mail.com",  # Дублирование!
                "items": [
                    {
                        "product_id": 1,
                        "product_name": "Laptop",
                        "category_name": "Electronics",  # Дублируется
                        "price": 999.99
                    }
                ]
            }
        ]
    }
    
    # Проблема: если изменить email, нужно обновить везде
    # user["email"] = "newemail@mail.com"
    # user["orders"][0]["user_email"] = "newemail@mail.com"
    # Может рассинхронизироваться!
    
    return user

Нормализованная структура (хорошо)

from dataclasses import dataclass
from typing import List
from datetime import datetime

@dataclass
class Category:
    id: int
    name: str

@dataclass
class Product:
    id: int
    name: str
    price: float
    category_id: int  # Ссылка, не дублирование!

@dataclass
class Address:
    id: int
    user_id: int
    street: str
    city: str
    zip_code: str

@dataclass
class Customer:
    id: int
    name: str
    email: str
    address_id: int  # Ссылка
    created_at: datetime

@dataclass
class OrderItem:
    id: int
    order_id: int
    product_id: int  # Ссылка
    quantity: int
    unit_price: float

@dataclass
class Order:
    id: int
    customer_id: int  # Ссылка, не дублируем customer_name!
    order_date: datetime
    items: List[OrderItem] = None  # Загружаем отдельно

# Использование
def process_orders_normalized():
    # Создаём данные
    category = Category(id=1, name="Electronics")
    product = Product(id=1, name="Laptop", price=999.99, category_id=category.id)
    address = Address(id=1, user_id=1, street="123 Main", city="NYC", zip_code="10001")
    customer = Customer(id=1, name="John Doe", email="john@mail.com", address_id=address.id, created_at=datetime.now())
    
    # Если изменить email, достаточно изменить один объект
    customer.email = "newemail@mail.com"  # Одно место!
    
    return customer, address, product

3. Нормализация данных API

Денормализованный API ответ (плохо)

from fastapi import FastAPI

app = FastAPI()

@app.get("/api/orders/{order_id}")
async def get_order(order_id: int):
    """Ответ с дублированием данных пользователя"""
    return {
        "order": {
            "id": 1,
            "date": "2024-03-23",
            # Дублирование данных пользователя
            "user": {
                "id": 1,
                "name": "John Doe",
                "email": "john@mail.com"
            },
            "items": [
                {
                    "id": 1,
                    "product_id": 1,
                    "product_name": "Laptop",
                    # Дублирование данных продукта
                    "product": {
                        "name": "Laptop",
                        "price": 999.99,
                        "category": "Electronics"
                    }
                }
            ]
        }
    }

Нормализованный API ответ (хорошо)

from typing import Dict, List
from pydantic import BaseModel

class NormalizedResponse(BaseModel):
    """Нормализованный формат: данные и ID"""
    
    # Основные данные
    order: Dict
    
    # Справочники (нормализованные)
    users: Dict[int, Dict]  # ID -> User
    products: Dict[int, Dict]  # ID -> Product
    categories: Dict[int, Dict]  # ID -> Category

@app.get("/api/orders/{order_id}")
async def get_normalized_order(order_id: int):
    """Нормализованный ответ (как Redux/MobX)"""
    return {
        "order": {
            "id": 1,
            "date": "2024-03-23",
            "user_id": 1,  # Только ID
            "item_ids": [1]  # Только IDs
        },
        "order_items": {
            "1": {
                "id": 1,
                "order_id": 1,
                "product_id": 1,
                "quantity": 1
            }
        },
        "users": {
            "1": {
                "id": 1,
                "name": "John Doe",
                "email": "john@mail.com"
            }
        },
        "products": {
            "1": {
                "id": 1,
                "name": "Laptop",
                "price": 999.99,
                "category_id": 1
            }
        },
        "categories": {
            "1": {"id": 1, "name": "Electronics"}
        }
    }

4. Нормализация текстовых данных

import re
from typing import List

def normalize_email(email: str) -> str:
    """Нормализовать email: lowercase, trim"""
    return email.strip().lower()

def normalize_phone(phone: str) -> str:
    """Нормализовать телефон: только цифры"""
    return re.sub(r'\D', '', phone)

def normalize_text(text: str) -> str:
    """Нормализовать текст"""
    # Убрать лишние пробелы
    text = ' '.join(text.split())
    # Привести к нижнему регистру
    text = text.lower()
    # Убрать спецсимволы
    text = re.sub(r'[^a-z0-9\s]', '', text)
    return text

def normalize_address(street: str, city: str, zip_code: str) -> dict:
    """Нормализовать адрес"""
    return {
        "street": street.strip().title(),
        "city": city.strip().upper(),
        "zip_code": re.sub(r'\D', '', zip_code)
    }

# Примеры
print(normalize_email("  John@MAIL.COM  "))  # john@mail.com
print(normalize_phone("+1 (555) 123-4567"))  # 15551234567
print(normalize_text("  Hello   WORLD!!! "))  # hello world

Ключевые принципы нормализации

# 1. Единая источник истины (Single Source of Truth)
# Данные хранятся в одном месте, ссылаются другие

# 2. Избегать дублирования
# ❌ Плохо: customer_name в orders и customers
# ✅ Хорошо: customer_id в orders, данные в customers

# 3. Минимизировать аномалии обновления
# ❌ Проблема: обновить email надо в 5 местах
# ✅ Решение: обновить в 1 месте

# 4. Облегчить поддержку и расширение
# Новое поле добавляется в одно место

# 5. Оптимизировать для запросов
# Индексы на внешних ключах
# Денормализация ТОЛЬКО при доказанных bottleneck

Вывод

Нормализация — это структурирование данных так, чтобы:

  • Не было дублирования
  • Каждый факт хранился в одном месте
  • Обновление было легко и безопасно
  • Связи выражались через ID, не через копирование

Это ключевой принцип проектирования БД (1NF, 2NF, 3NF) и API структур (Redux store, нормализованные API).