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

Какие знаешь типы constraint в PostgreSQL?

2.0 Middle🔥 191 комментариев
#Базы данных и SQL

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

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

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

Типы ограничений (Constraints) в PostgreSQL

Constraints в PostgreSQL - это правила, которые определяют допустимые значения данных в таблице. Это основа целостности данных и один из ключевых инструментов проектирования БД. Рассмотрю все основные типы.

1. PRIMARY KEY - Первичный ключ

Уникально идентифицирует каждую строку в таблице:

// SQL
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    name VARCHAR(100)
);

// На уровне entity (JPA)
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String email;
}

Характеристики:

  • Не может быть NULL
  • Уникален для каждой строки
  • Возможен только один на таблицу (составной PRIMARY KEY из нескольких колонок - тоже один)
  • Автоматически создаёт индекс

2. UNIQUE - Уникальность

Гарантирует уникальность значений (но может быть несколько NULL):

@Entity
public class User {
    @Id
    private Long id;
    
    @Column(unique = true, nullable = false)
    private String username;
    
    @Column(unique = true)
    private String email; // может быть NULL, и NULL != NULL
}

// SQL с несколькими колонками
CREATE TABLE user_emails (
    user_id BIGINT,
    email VARCHAR(255),
    UNIQUE(user_id, email)  -- эта комбинация уникальна
);

Отличие от PRIMARY KEY:

  • Может быть несколько UNIQUE constraints на таблицу
  • Может содержать NULL (несколько NULL'ов возможны)
  • Можно удалить через ALTER TABLE

3. FOREIGN KEY - Внешний ключ

Звязывает данные в разных таблицах:

@Entity
public class Order {
    @Id
    private Long id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
}

// SQL
CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE,
    FOREIGN KEY (user_id) REFERENCES users(id) 
        ON DELETE CASCADE           -- если удалить юзера, удалять заказы
        ON UPDATE RESTRICT         -- нельзя обновить id юзера если есть заказы
);

Действия при удалении/обновлении:

  • CASCADE - удалить связанные строки
  • SET NULL - установить NULL
  • SET DEFAULT - установить значение по умолчанию
  • RESTRICT / NO ACTION - запретить

4. NOT NULL - Обязательное значение

Колонка не может содержать NULL:

@Entity
public class User {
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false)
    private LocalDateTime createdAt;
}

// SQL
CREATE TABLE users (
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL
);

5. CHECK - Проверка значений

Данные должны соответствовать условию:

@Entity
public class Order {
    @Id
    private Long id;
    
    @Column(name = "total_amount")
    private BigDecimal totalAmount; // CHECK constraint на БД уровне
    
    @Min(0)
    @Column(name = "discount")
    private Integer discount;
}

// SQL - CHECK constraint на уровне БД
CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    total_amount DECIMAL(10,2),
    discount INTEGER,
    CONSTRAINT check_positive_amount CHECK (total_amount > 0),
    CONSTRAINT check_valid_discount CHECK (discount >= 0 AND discount <= 100)
);

6. DEFAULT - Значение по умолчанию

Автоматически присваивает значение если не указано:

@Entity
public class User {
    @Id
    private Long id;
    
    @Column(columnDefinition = "TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP")
    private LocalDateTime createdAt;
    
    @Column(columnDefinition = "BOOLEAN DEFAULT false")
    private Boolean isActive;
}

// SQL
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    is_active BOOLEAN DEFAULT false,
    status VARCHAR(50) DEFAULT 'PENDING'
);

7. EXCLUDE - Исключение диапазонов

Запрещает пересечение диапазонов (используется с расширением btree_gist):

// SQL - для расписания встреч (нельзя чтобы два совещания пересекались)
CREATE TABLE meetings (
    id BIGSERIAL PRIMARY KEY,
    room_id BIGINT,
    start_time TIMESTAMP,
    end_time TIMESTAMP,
    EXCLUDE USING GIST (
        room_id WITH =,
        tsrange(start_time, end_time) WITH &&
    )
);
// && оператор проверяет пересечение диапазонов

8. Составной (Composite) PRIMARY KEY

Первичный ключ из нескольких колонок:

@Entity
@IdClass(UserRoleId.class)
public class UserRole {
    @Id
    private Long userId;
    
    @Id
    private Long roleId;
    
    @ManyToOne
    @JoinColumn(name = "user_id", insertable = false, updatable = false)
    private User user;
    
    @ManyToOne
    @JoinColumn(name = "role_id", insertable = false, updatable = false)
    private Role role;
}

public class UserRoleId implements Serializable {
    private Long userId;
    private Long roleId;
    // equals, hashCode...
}

// SQL
CREATE TABLE user_roles (
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    PRIMARY KEY (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (role_id) REFERENCES roles(id)
);

Практический пример - полная таблица с constraints:

@Entity
@Table(name = "products", 
    uniqueConstraints = @UniqueConstraint(columnNames = "sku"),
    indexes = @Index(name = "idx_category", columnList = "category_id")
)
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(unique = true, nullable = false)
    private String sku;  // UNIQUE
    
    @Column(nullable = false)
    private String name;  // NOT NULL
    
    @Column(nullable = false)
    private BigDecimal price;  // NOT NULL, CHECK > 0
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id", nullable = false)
    private Category category;  // FOREIGN KEY
    
    @Column(columnDefinition = "TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP")
    private LocalDateTime createdAt;  // DEFAULT
}

Ключевые преимущества constraints:

  • Целостность данных - БД гарантирует валидность на уровне схемы
  • Производительность - индексы на PRIMARY/UNIQUE/FOREIGN KEY ускоряют запросы
  • Меньше кода - не нужно валидировать в приложении
  • Экономия памяти - связанные данные нормализованы
  • Откат транзакций - нарушение constraint откатит всю транзакцию

Применение constraints на уровне БД - это best practice, который обеспечивает надёжность системы независимо от кода приложения.