Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Ограничение UNIQUE в SQL
OGRANICHENIE UNIQUE (уникальное ограничение) — это правило базы данных, которое гарантирует, что все значения в одном или нескольких столбцах уникальны. Никакие две строки не могут иметь одинаковые значения в столбцах, отмеченных как UNIQUE.
Базовое определение
OGRANICHENIE UNIQUE очень похоже на PRIMARY KEY, но есть отличия:
| Свойство | PRIMARY KEY | UNIQUE |
|---|---|---|
| Уникальность | Да | Да |
| Может быть 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;
Важные моменты
-
NULL не равен NULL — поэтому несколько NULL разрешены в UNIQUE столбцах
-- Оба OK INSERT INTO users (username, phone) VALUES ('user1', NULL); INSERT INTO users (username, phone) VALUES ('user2', NULL); -
Индекс создаётся автоматически — не нужно создавать отдельно
-
На производительность влияет — UNIQUE проверяется при каждой вставке/обновлении
-
Миграция может быть долгой — если добавить UNIQUE на большую таблицу, БД проверит все строки
-
Составные 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 — простой но эффективный способ гарантировать целостность данных на уровне БД, а не в коде приложения.