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

Какие знаешь ограничения для таблицы?

1.0 Junior🔥 131 комментариев
#Git и VCS

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

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

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

Ограничения (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';

Ограничения — это первая линия защиты качества данных. Используй их активно!