Let's consider a database table that stores organizations. Each organization should be uniquely identified by a code, and we want to ensure that no duplicate organizations exist.
A standard way to enforce uniqueness is by creating a unique index on the code column:
CREATE TABLE `organizations` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`code` VARCHAR(8) NOT NULL,
`created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE INDEX `UK_organizations_code` (`code`)
);
To allow soft deletion, we introduce a deleted_at column, which stores the timestamp when a record is marked as deleted. If an organization is active, this column remains NULL.
CREATE TABLE `organizations` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`code` VARCHAR(8) NOT NULL,
`created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `UK_organizations_code` (`code`)
);
Suppose we insert an organization with code = 'ORG1'. Later, this organization is marked as deleted by setting deleted_at to a timestamp. If we then try to insert a new organization with the same code, the unique index constraint will prevent it.
We want to enforce uniqueness only for active (non-deleted) organizations. To achieve this, we modify the unique index to consider both code and deleted_at:
CREATE TABLE `organizations` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`code` VARCHAR(8) NOT NULL,
`created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `UK_organizations_code` (`code`, `deleted_at`)
);
For active organizations, deleted_at is NULL, so the unique constraint ensures that only one active record exists per code.
When an organization is soft-deleted, deleted_at is set to a timestamp, making the (code, deleted_at) pair unique.
This allows a new organization with the same code to be inserted with deleted_at = NULL, since its (code, NULL) pair does not conflict with any existing record.
| id | name | code | deleted_at |
|---|---|---|---|
| 1 | Org A | ORG1 | NULL |
| 2 | Org A (old) | ORG1 | 2024-01-01 10:00:00 |
This approach allows reusing organization codes after deletion while maintaining uniqueness among active records.