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

Что такое ограничение unique в SQL?

1.6 Junior🔥 201 комментариев
#Базы данных и SQL

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

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

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

Ограничение UNIQUE в SQL

OGRANICHENIE UNIQUE (уникальное ограничение) — это правило базы данных, которое гарантирует, что все значения в одном или нескольких столбцах уникальны. Никакие две строки не могут иметь одинаковые значения в столбцах, отмеченных как UNIQUE.

Базовое определение

OGRANICHENIE UNIQUE очень похоже на PRIMARY KEY, но есть отличия:

СвойствоPRIMARY KEYUNIQUE
УникальностьДаДа
Может быть NULLНетДа (несколько NULL)
На таблицуТолько 1Много
ИндексАвтоматическийАвтоматический
Foreign Key ссылкаДаНет

Синтаксис в SQL

Вариант 1: На уровне столбца

CREATE TABLE users (
  id BIGINT PRIMARY KEY,
  username VARCHAR(100) UNIQUE,
  email VARCHAR(255) UNIQUE,
  phone VARCHAR(20) UNIQUE
);

Это означает:

  • Каждый username должен быть уникальным
  • Каждый email должен быть уникальным
  • Каждый phone должен быть уникальным
  • id — первичный ключ

Вариант 2: На уровне таблицы

CREATE TABLE users (
  id BIGINT PRIMARY KEY,
  username VARCHAR(100),
  email VARCHAR(255),
  phone VARCHAR(20),
  UNIQUE (username),
  UNIQUE (email),
  UNIQUE (phone)
);

Результат то же самое, но более явно.

Вариант 3: Составной UNIQUE (несколько столбцов)

CREATE TABLE user_roles (
  user_id BIGINT,
  role_id BIGINT,
  assigned_at TIMESTAMP,
  PRIMARY KEY (user_id, role_id),
  UNIQUE (user_id, role_id)
);

Это означает: комбинация (user_id, role_id) должна быть уникальной. Один пользователь не может иметь одну и ту же роль дважды.

Практические примеры

Пример 1: Email уникальность (классический случай)

CREATE TABLE users (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(100) NOT NULL UNIQUE,
  email VARCHAR(255) NOT NULL UNIQUE,
  password_hash VARCHAR(255) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Попытка вставить дублирующийся email
INSERT INTO users (username, email, password_hash) 
VALUES ('user1', 'john@example.com', 'hash1');
-- ОК

INSERT INTO users (username, email, password_hash) 
VALUES ('user2', 'john@example.com', 'hash2');
-- ОШИБКА: Duplicate entry 'john@example.com' for key 'email'

Пример 2: Составной ключ (SKU товара)

CREATE TABLE products (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  sku VARCHAR(50) NOT NULL,
  warehouse_id BIGINT NOT NULL,
  name VARCHAR(255),
  price DECIMAL(10, 2),
  created_at TIMESTAMP,
  UNIQUE (sku, warehouse_id)  -- Один SKU на один склад
);

-- Вставляем одинаковый SKU на разных складах — OK
INSERT INTO products (sku, warehouse_id, name, price) 
VALUES ('SKU-001', 1, 'Laptop', 999.99);

INSERT INTO products (sku, warehouse_id, name, price) 
VALUES ('SKU-001', 2, 'Laptop', 999.99);
-- OK, разные warehouse_id

-- Но если попробовать вставить дублирующуюся комбинацию
INSERT INTO products (sku, warehouse_id, name, price) 
VALUES ('SKU-001', 1, 'Laptop', 1099.99);
-- ОШИБКА: комбинация (SKU-001, 1) уже существует

Пример 3: Допуск NULL значений

CREATE TABLE users (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(100) NOT NULL UNIQUE,
  email VARCHAR(255) UNIQUE,  -- Может быть NULL
  phone VARCHAR(20) UNIQUE    -- Может быть NULL
);

-- Несколько пользователей могут иметь NULL в email
INSERT INTO users (username, email, phone) VALUES ('user1', NULL, '123-4567');
INSERT INTO users (username, email, phone) VALUES ('user2', NULL, '123-4568');
INSERT INTO users (username, email, phone) VALUES ('user3', NULL, '123-4569');
-- ВСЕ OK! В SQL NULL != NULL, поэтому несколько NULL разрешены

-- Но если заполнить конкретный email
INSERT INTO users (username, email, phone) VALUES ('user4', 'john@example.com', '123-4570');
INSERT INTO users (username, email, phone) VALUES ('user5', 'john@example.com', '123-4571');
-- ОШИБКА на второй вставке

Пример 4: Именованное ограничение

CREATE TABLE products (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  code VARCHAR(50),
  name VARCHAR(255),
  CONSTRAINT uk_product_code UNIQUE (code)
);

-- Если вставить дублирующийся code
-- Ошибка будет: Duplicate entry для constraint 'uk_product_code'

Внутри: как это работает

BD автоматически создаёт индекс для UNIQUE ограничения:

CREATE TABLE users (
  id BIGINT PRIMARY KEY,
  email VARCHAR(255) UNIQUE
);

-- За кулисами БД создаёт
CREATE UNIQUE INDEX email_UNIQUE ON users(email);

Этот индекс:

  • Ускоряет проверку уникальности (за O(log n) вместо O(n))
  • Ускоряет поиск по email
  • Проверяет при каждой вставке/обновлении

Разница между UNIQUE и PRIMARY KEY

CREATE TABLE accounts (
  account_id BIGINT PRIMARY KEY,  -- Первичный ключ
  email VARCHAR(255) UNIQUE,     -- Уникальный, но НЕ первичный
  username VARCHAR(100) UNIQUE   -- Уникальный, но НЕ первичный
);

-- PRIMARY KEY (account_id):
-- - Не может быть NULL
-- - Только один на таблицу
-- - Автоматически индексирован
-- - Основной идентификатор записи

-- UNIQUE (email, username):
-- - Могут быть NULL (несколько NULL)
-- - Может быть много на таблицу
-- - Автоматически индексирован
-- - Альтернативные ключи

Обработка нарушений в Java/Spring

@Entity
@Table(name = "users")
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  
  @Column(unique = true, nullable = false)
  private String email;
  
  @Column(unique = true, nullable = false)
  private String username;
}

// Spring Data Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
  Optional<User> findByEmail(String email);
  Optional<User> findByUsername(String username);
}

// Обработка ошибки в сервисе
@Service
public class UserService {
  @Autowired
  private UserRepository userRepository;
  
  public void createUser(String username, String email) {
    try {
      User user = new User();
      user.setUsername(username);
      user.setEmail(email);
      userRepository.save(user);
    } catch (DataIntegrityViolationException e) {
      // UNIQUE constraint нарушен
      if (e.getMessage().contains("email")) {
        throw new EmailAlreadyExistsException("Email уже существует");
      } else if (e.getMessage().contains("username")) {
        throw new UsernameAlreadyExistsException("Username уже существует");
      }
      throw e;
    }
  }
}

Добавление UNIQUE после создания таблицы

-- Если таблица уже существует
ALTER TABLE users 
ADD CONSTRAINT uk_email UNIQUE (email);

-- Или
CREATE UNIQUE INDEX idx_email ON users(email);

-- Удаление UNIQUE ограничения
ALTER TABLE users DROP INDEX uk_email;

Важные моменты

  1. NULL не равен NULL — поэтому несколько NULL разрешены в UNIQUE столбцах

    -- Оба OK
    INSERT INTO users (username, phone) VALUES ('user1', NULL);
    INSERT INTO users (username, phone) VALUES ('user2', NULL);
    
  2. Индекс создаётся автоматически — не нужно создавать отдельно

  3. На производительность влияет — UNIQUE проверяется при каждой вставке/обновлении

  4. Миграция может быть долгой — если добавить UNIQUE на большую таблицу, БД проверит все строки

  5. Составные UNIQUE — часто используются для soft delete

    CREATE TABLE user_sessions (
      user_id BIGINT,
      session_id VARCHAR(255),
      deleted_at TIMESTAMP NULL,
      UNIQUE (user_id, session_id, deleted_at)  -- Один активный session на user
    );
    

OGRANICHENIE UNIQUE — простой но эффективный способ гарантировать целостность данных на уровне БД, а не в коде приложения.

Что такое ограничение unique в SQL? | PrepBro