mirror of
https://github.com/jbranchaud/til
synced 2026-01-02 22:58:01 +00:00
47 lines
1.9 KiB
Markdown
47 lines
1.9 KiB
Markdown
# Put Unique Constraint On Generated Column
|
|
|
|
You cannot apply a _unique constraint_ to an expression over a column, e.g.
|
|
`lower(email)`. You can, however, create a [generated
|
|
column](https://www.postgresql.org/docs/current/ddl-generated-columns.html) for
|
|
that expression and then apply the unique constraint to that generated column.
|
|
|
|
Here is what that could look like:
|
|
|
|
```sql
|
|
> create table users (
|
|
id integer generated always as identity primary key,
|
|
name text not null,
|
|
email text not null,
|
|
email_lower text generated always as (lower(email)) stored,
|
|
unique ( email_lower )
|
|
);
|
|
|
|
> \d users
|
|
+-------------+---------+-----------------------------------------------------------------+
|
|
| Column | Type | Modifiers |
|
|
|-------------+---------+-----------------------------------------------------------------|
|
|
| id | integer | not null generated always as identity |
|
|
| name | text | not null |
|
|
| email | text | not null |
|
|
| email_lower | text | default lower(email) generated always as (lower(email)) stored |
|
|
+-------------+---------+-----------------------------------------------------------------+
|
|
Indexes:
|
|
"users_pkey" PRIMARY KEY, btree (id)
|
|
"users_email_lower_key" UNIQUE CONSTRAINT, btree (email_lower)
|
|
```
|
|
|
|
And then an demonstration of violating that constraint:
|
|
|
|
```sql
|
|
|
|
> insert into users (name, email) values ('Bob', 'bob@email.com');
|
|
INSERT 0 1
|
|
|
|
> insert into users (name, email) values ('Bobby', 'BOB@email.com');
|
|
duplicate key value violates unique constraint "users_email_lower_key"
|
|
DETAIL: Key (email_lower)=(bob@email.com) already exists.
|
|
```
|
|
|
|
The main tradeoff here is that you are doubling the amount of storage you need
|
|
for that column. Unless it is a massive table, that is likely not an issue.
|