Si je peux comprendre documentation , les définitions suivantes sont équivalentes:
create table foo (
id serial primary key,
code integer,
label text,
constraint foo_uq unique (code, label));
create table foo (
id serial primary key,
code integer,
label text);
create unique index foo_idx on foo using btree (code, label);
Cependant, vous pouvez lire dans la note: Le moyen préféré d'ajouter une contrainte unique à une table est ALTER TABLE ... ADD CONSTRAINT. L'utilisation d'index pour appliquer des contraintes uniques peut être considérée comme un détail d'implémentation auquel il ne faut pas accéder directement.
Est-ce seulement une question de bon style? Quelles sont les conséquences pratiques du choix d’une de ces variantes (par exemple, en termes de performances)?
J'avais des doutes sur cette question fondamentale mais importante, alors j'ai décidé d'apprendre par l'exemple.
Créons la table de test maître avec deux colonnes, con_id avec une contrainte unique et ind_id indexé par un index unique.
create table master (
con_id integer unique,
ind_id integer
);
create unique index master_unique_idx on master (ind_id);
Table "public.master"
Column | Type | Modifiers
--------+---------+-----------
con_id | integer |
ind_id | integer |
Indexes:
"master_con_id_key" UNIQUE CONSTRAINT, btree (con_id)
"master_unique_idx" UNIQUE, btree (ind_id)
Dans la description de la table (\ d dans psql), vous pouvez distinguer une contrainte unique d’un index unique.
Unicité
Vérifions l'unicité, juste au cas où.
test=# insert into master values (0, 0);
INSERT 0 1
test=# insert into master values (0, 1);
ERROR: duplicate key value violates unique constraint "master_con_id_key"
DETAIL: Key (con_id)=(0) already exists.
test=# insert into master values (1, 0);
ERROR: duplicate key value violates unique constraint "master_unique_idx"
DETAIL: Key (ind_id)=(0) already exists.
test=#
Cela fonctionne comme prévu!
Clés étrangères
Nous allons maintenant définir la table detail avec deux clés étrangères référençant nos deux colonnes dans master.
create table detail (
con_id integer,
ind_id integer,
constraint detail_fk1 foreign key (con_id) references master(con_id),
constraint detail_fk2 foreign key (ind_id) references master(ind_id)
);
Table "public.detail"
Column | Type | Modifiers
--------+---------+-----------
con_id | integer |
ind_id | integer |
Foreign-key constraints:
"detail_fk1" FOREIGN KEY (con_id) REFERENCES master(con_id)
"detail_fk2" FOREIGN KEY (ind_id) REFERENCES master(ind_id)
Eh bien, pas d'erreurs. Faisons en sorte que cela fonctionne.
test=# insert into detail values (0, 0);
INSERT 0 1
test=# insert into detail values (1, 0);
ERROR: insert or update on table "detail" violates foreign key constraint "detail_fk1"
DETAIL: Key (con_id)=(1) is not present in table "master".
test=# insert into detail values (0, 1);
ERROR: insert or update on table "detail" violates foreign key constraint "detail_fk2"
DETAIL: Key (ind_id)=(1) is not present in table "master".
test=#
Les deux colonnes peuvent être référencées dans des clés étrangères.
Contrainte utilisant index
Vous pouvez ajouter une contrainte de table en utilisant un index unique existant.
alter table master add constraint master_ind_id_key unique using index master_unique_idx;
Table "public.master"
Column | Type | Modifiers
--------+---------+-----------
con_id | integer |
ind_id | integer |
Indexes:
"master_con_id_key" UNIQUE CONSTRAINT, btree (con_id)
"master_ind_id_key" UNIQUE CONSTRAINT, btree (ind_id)
Referenced by:
TABLE "detail" CONSTRAINT "detail_fk1" FOREIGN KEY (con_id) REFERENCES master(con_id)
TABLE "detail" CONSTRAINT "detail_fk2" FOREIGN KEY (ind_id) REFERENCES master(ind_id)
Maintenant, il n'y a pas de différence entre la description des contraintes de colonne.
Index partiels
Dans la déclaration de contrainte de table, vous ne pouvez pas créer d'index partiels. Il vient directement de la définition de create table ...
. Dans la déclaration d'index unique, vous pouvez définir WHERE clause
pour créer un index partiel. Vous pouvez aussi créer un index sur une expression (pas seulement sur une colonne) et définir certains autres paramètres (classement, ordre de tri, placement de NULL).
Vous ne pouvez pas ajouter de contrainte de table en utilisant un index partiel.
alter table master add column part_id integer;
create unique index master_partial_idx on master (part_id) where part_id is not null;
alter table master add constraint master_part_id_key unique using index master_partial_idx;
ERROR: "master_partial_idx" is a partial index
LINE 1: alter table master add constraint master_part_id_key unique ...
^
DETAIL: Cannot create a primary key or unique constraint using such an index.
Un autre avantage de l'utilisation de UNIQUE INDEX
contre UNIQUE CONSTRAINT
est que vous pouvez facilement DROP
/CREATE
un index CONCURRENTLY
, alors qu'avec une contrainte, vous ne le pouvez pas.
L'unicité est une contrainte. Il arrive à être implémenté via la création d'un index unique puisqu'un index est capable de rechercher rapidement tous les existants valeurs afin de déterminer si une valeur donnée existe déjà.
Conceptuellement, l'index est un détail d'implémentation et l'unicité devrait être associé uniquement à des contraintes.
Donc, les performances de vitesse devraient être les mêmes
Une autre chose que j'ai rencontrée est que vous pouvez utiliser des expressions SQL dans des index uniques mais pas dans des contraintes.
Donc, ça ne marche pas:
CREATE TABLE users (
name text,
UNIQUE (lower(name))
);
mais les travaux suivants.
CREATE TABLE users (
name text
);
CREATE UNIQUE INDEX uq_name on users (lower(name));
Il y a une différence dans le verrouillage.
L'ajout d'un index ne bloque pas l'accès en lecture à la table.
L'ajout d'une contrainte met un verrou de table (donc, toutes les sélections sont bloquées) puisqu'il est ajouté via ALTER TABLE .