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.