Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Ограничения (Constraints) для таблиц БД
Ограничения — это правила целостности данных, которые БД автоматически проверяет и применяет. Это критично для качества данных.
1. PRIMARY KEY (Первичный ключ)
Гарантирует: каждая строка уникальна и идентифицируется однозначно
-- SQL определение
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
-- Или для существующей таблицы
ALTER TABLE users ADD CONSTRAINT pk_users PRIMARY KEY (id);
# SQLAlchemy
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True) # Автоматический PRIMARY KEY
name = Column(String(255), nullable=False)
Ограничения:
- Можно только один PRIMARY KEY на таблицу
- NULL не допускается
- Автоматически создаёт индекс
2. UNIQUE (Уникальность)
Гарантирует: все значения в колонке уникальны (может быть NULL)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
username VARCHAR(255) UNIQUE
);
-- Или именованное ограничение
ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE (email);
-- Составной UNIQUE
ALTER TABLE user_roles ADD CONSTRAINT uq_user_roles
UNIQUE (user_id, role_id);
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
email = Column(String(255), unique=True, nullable=False)
username = Column(String(255), unique=True) # Может быть NULL
3. NOT NULL (Обязательное значение)
Гарантирует: колонка всегда содержит значение
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
age INTEGER NOT NULL,
bio TEXT -- Может быть NULL
);
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
bio = Column(Text, nullable=True) # По умолчанию True
4. CHECK (Проверка значения)
Гарантирует: значение удовлетворяет условию
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) CHECK (price > 0),
discount DECIMAL(5, 2) CHECK (discount >= 0 AND discount <= 100)
);
-- Или именованное
ALTER TABLE users ADD CONSTRAINT ck_users_age
CHECK (age >= 18 AND age <= 150);
from sqlalchemy import CheckConstraint
class Product(Base):
__tablename__ = 'products'
id = Column(Integer, primary_key=True)
price = Column(Numeric(10, 2), nullable=False)
discount = Column(Numeric(5, 2))
__table_args__ = (
CheckConstraint('price > 0', name='ck_products_price'),
CheckConstraint('discount >= 0 AND discount <= 100'),
)
5. FOREIGN KEY (Внешний ключ)
Гарантирует: значение ссылается на существующую запись в другой таблице
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
title VARCHAR(255),
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
posts = relationship('Post', back_populates='user', cascade='all, delete-orphan')
class Post(Base):
__tablename__ = 'posts'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
title = Column(String(255))
user = relationship('User', back_populates='posts')
ON DELETE опции:
- CASCADE: удалить при удалении родителя
- SET NULL: установить NULL
- RESTRICT: запретить удаление
- SET DEFAULT: установить значение по умолчанию
6. DEFAULT (Значение по умолчанию)
Гарантирует: если значение не указано, используется default
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(50) DEFAULT 'draft',
view_count INTEGER DEFAULT 0
);
from datetime import datetime
class Post(Base):
__tablename__ = 'posts'
id = Column(Integer, primary_key=True)
title = Column(String(255))
created_at = Column(DateTime, default=datetime.utcnow)
status = Column(String(50), default='draft')
view_count = Column(Integer, default=0)
7. Примеры комбинирования ограничений
-- Полный пример с несколькими ограничениями
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
username VARCHAR(50) NOT NULL UNIQUE,
age INTEGER CHECK (age >= 18),
status VARCHAR(50) DEFAULT 'active' NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total DECIMAL(10, 2) NOT NULL CHECK (total > 0),
discount DECIMAL(5, 2) CHECK (discount >= 0 AND discount <= 100),
UNIQUE (user_id, order_date) -- Составной UNIQUE
);
8. Индексные ограничения
Индексы для поиска (не ограничение целостности, но ограничение на производительность)
-- Простой индекс
CREATE INDEX idx_users_email ON users(email);
-- Составной индекс (используется для поиска по нескольким колонкам)
CREATE INDEX idx_orders_user_date ON orders(user_id, order_date);
-- Уникальный индекс (гарантирует уникальность)
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);
-- Частичный индекс (только для активных пользователей)
CREATE INDEX idx_users_active ON users(id) WHERE status = 'active';
9. Практические примеры реальных таблиц
Таблица пользователей:
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
full_name VARCHAR(255) NOT NULL,
age INTEGER CHECK (age >= 0 AND age <= 150),
status VARCHAR(50) DEFAULT 'active' NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT ck_users_email CHECK (email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}$')
);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_status ON users(status);
Таблица заказов:
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
total DECIMAL(10, 2) NOT NULL CHECK (total >= 0),
discount DECIMAL(5, 2) DEFAULT 0 CHECK (discount >= 0 AND discount <= 100),
status VARCHAR(50) DEFAULT 'pending' NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
UNIQUE (user_id, created_at)
);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_created_at ON orders(created_at);
10. Ошибки при работе с ограничениями
# ОШИБКА 1: Нарушение UNIQUE
try:
user1 = User(email='john@example.com', name='John')
user2 = User(email='john@example.com', name='Jane') # Та же email!
db.add_all([user1, user2])
db.commit()
except IntegrityError:
print('Error: Duplicate email')
db.rollback()
# ОШИБКА 2: Нарушение NOT NULL
try:
user = User(email='john@example.com', name=None) # name обязателен!
db.add(user)
db.commit()
except IntegrityError:
print('Error: Name is required')
db.rollback()
# ОШИБКА 3: Нарушение FOREIGN KEY
try:
post = Post(user_id=999, title='Hello') # user_id не существует!
db.add(post)
db.commit()
except IntegrityError:
print('Error: User does not exist')
db.rollback()
# ОШИБКА 4: Нарушение CHECK
try:
product = Product(name='Shirt', price=-10) # price должен быть > 0!
db.add(product)
db.commit()
except IntegrityError:
print('Error: Price must be positive')
db.rollback()
11. Лучшие практики
1. Используй PRIMARY KEY
CREATE TABLE users (
id SERIAL PRIMARY KEY, -- Всегда!
...
);
2. Используй NOT NULL для обязательных полей
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
total DECIMAL(10, 2) NOT NULL CHECK (total > 0)
);
3. Используй UNIQUE для уникальных данных
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
username VARCHAR(100) UNIQUE
);
4. Используй CHECK для валидации
CREATE TABLE users (
age INTEGER CHECK (age >= 18),
discount DECIMAL(5, 2) CHECK (discount BETWEEN 0 AND 100)
);
5. Используй FOREIGN KEY для связей
CREATE TABLE posts (
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE
);
6. Используй DEFAULT для часто используемых значений
CREATE TABLE posts (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(50) DEFAULT 'draft'
);
12. Проверка ограничений
-- PostgreSQL: просмотр ограничений
SELECT constraint_name, constraint_type
FROM information_schema.table_constraints
WHERE table_name = 'users';
-- Просмотр CHECK ограничений
SELECT constraint_name, check_clause
FROM information_schema.check_constraints
WHERE table_name = 'users';
-- Просмотр FOREIGN KEY
SELECT constraint_name, column_name, referenced_table_name
FROM information_schema.key_column_usage
WHERE table_name = 'posts';
Ограничения — это первая линия защиты качества данных. Используй их активно!