Vincolo univoco di Postgres contro indice


157

Come posso capire la documentazione le seguenti definizioni sono equivalenti:

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);    

Tuttavia, una nota nel manuale di Postgres 9.4 dice:

Il modo preferito per aggiungere un vincolo univoco a una tabella è ALTER TABLE ... ADD CONSTRAINT. L'uso di indici per imporre vincoli univoci potrebbe essere considerato un dettaglio di implementazione a cui non è possibile accedere direttamente.

(Modifica: questa nota è stata rimossa dal manuale con Postgres 9.5.)

È solo una questione di buon stile? Quali sono le conseguenze pratiche della scelta di una di queste varianti (ad esempio in termini di prestazioni)?


23
La (unica) differenza pratica è che è possibile creare una chiave esterna per un vincolo univoco ma non per un indice univoco.
a_horse_with_no_name

29
Un altro vantaggio ( come è emerso in un'altra domanda di recente ) è che puoi avere un indice univoco parziale , come "Unico (foo) Where bar Is Null". AFAIK, non c'è modo di farlo con un vincolo.
IMSoP

3
@a_horse_with_no_name Non sono sicuro quando sia successo, ma questo non sembra più essere vero. Questo violino SQL consente riferimenti di chiave esterna a un indice univoco: sqlfiddle.com/#!17/20ee9 ; EDIT: l'aggiunta di un 'filtro' all'indice univoco fa sì che questo smetta di funzionare (come previsto)
user1935361

1
dalla documentazione di postgres: PostgreSQL crea automaticamente un indice univoco quando viene definito un vincolo univoco o chiave primaria per una tabella. postgresql.org/docs/9.4/static/indexes-unique.html
maggu

Concordo con @ user1935361, se non fosse possibile creare una chiave esterna per un indice univoco (almeno con PG 10) avrei riscontrato questo problema molto tempo fa.
Andy,

Risposte:


132

Avevo dei dubbi su questo problema fondamentale ma importante, quindi ho deciso di imparare con l'esempio.

Creiamo il master della tabella di test con due colonne, con_id con vincolo univoco e ind_id indicizzato da un indice univoco.

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)

Nella descrizione della tabella (\ d in psql) puoi dire un vincolo univoco da un indice univoco.

Unicità

Controlliamo l'unicità, per ogni evenienza.

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=#

Funziona come previsto!

Chiavi esterne

Ora definiremo la tabella dei dettagli con due chiavi esterne che fanno riferimento alle nostre due colonne nel 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)

Bene, nessun errore. Facciamo in modo che funzioni.

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=#

È possibile fare riferimento a entrambe le colonne in chiavi esterne.

Vincolo mediante indice

È possibile aggiungere un vincolo di tabella utilizzando l'indice univoco esistente.

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)

Ora non c'è differenza tra la descrizione dei vincoli di colonna.

Indici parziali

Nella dichiarazione del vincolo della tabella non è possibile creare indici parziali. Viene direttamente dalla definizione di create table .... Nella dichiarazione dell'indice univoca è possibile impostare la WHERE clausecreazione di un indice parziale. Puoi anche creare un indice sull'espressione (non solo sulla colonna) e definire alcuni altri parametri (regole di confronto, ordinamento, posizionamento NULL).

Non è possibile aggiungere un vincolo di tabella utilizzando l'indice parziale.

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.

sono informazioni effettive? specialmente sugli indici parziali
anatol

1
@anatol - sì, lo è.
klin,

30

Un altro vantaggio dell'utilizzo di UNIQUE INDEXvs. UNIQUE CONSTRAINTè che puoi facilmente DROP/ CREATEun indice CONCURRENTLY, mentre con un vincolo non puoi.


4
AFAIK non è possibile rilasciare contemporaneamente un indice univoco. postgresql.org/docs/9.3/static/sql-dropindex.html "Esistono diversi avvertimenti da tenere presente quando si utilizza questa opzione. È possibile specificare un solo nome indice e l'opzione CASCADE non è supportata. (Pertanto, un indice che supporta un vincolo UNIQUE o PRIMARY KEY non può essere eliminato in questo modo. ""
Rafał Cieślak,

15

L'unicità è un vincolo. Capita di essere implementato tramite la creazione di un indice univoco poiché un indice è in grado di cercare rapidamente tutti i valori esistenti al fine di determinare se un determinato valore esiste già.

Concettualmente l'indice è un dettaglio di implementazione e l'unicità dovrebbe essere associata solo ai vincoli.

Il testo completo

Quindi le prestazioni di velocità dovrebbero essere le stesse


4

Un'altra cosa che ho riscontrato è che puoi usare espressioni sql in indici univoci ma non in vincoli.

Quindi, questo non funziona:

CREATE TABLE users (
    name text,
    UNIQUE (lower(name))
);

ma a seguito funziona.

CREATE TABLE users (
    name text
);
CREATE UNIQUE INDEX uq_name on users (lower(name));

Vorrei usare l' citextestensione.
ceving l'

@ceving dipende dal caso d'uso. a volte vuoi preservare il case assicurando unicità insensibile alle maiuscole / minuscole
Sampson Crowley

2

Poiché varie persone hanno offerto vantaggi di indici univoci rispetto a vincoli univoci, ecco un inconveniente: un vincolo unico può essere rinviato (verificato solo al termine della transazione), un indice univoco non può essere.


Come può essere, dato che tutti i vincoli univoci hanno un indice univoco?
Chris,

1
Poiché gli indici non dispongono di un'API per il differimento, lo sono solo i vincoli, quindi mentre il meccanismo di differimento esiste sotto la copertura per supportare vincoli univoci, non c'è modo di dichiarare un indice differibile o differirlo.
Masklinn

0

Ho letto questo nel documento:

ADD table_constraint [NOT VALID]

Questo modulo aggiunge un nuovo vincolo a una tabella utilizzando la stessa sintassi di CREATE TABLE, oltre all'opzione NOT VALID, che al momento è consentita solo per vincoli di chiave esterna. Se il vincolo è contrassegnato NOT VALID, viene saltato il controllo iniziale potenzialmente lungo per verificare che tutte le righe nella tabella soddisfino il vincolo . Il vincolo verrà comunque applicato agli inserimenti o agli aggiornamenti successivi (ovvero, non riusciranno a meno che non sia presente una riga corrispondente nella tabella di riferimento). Ma il database non supporrà che il vincolo valga per tutte le righe della tabella, fino a quando non viene convalidato utilizzando l'opzione CONVALIDA VINCITORE.

Quindi penso che sia ciò che chiamate "unicità parziale" aggiungendo un vincolo.

E, su come garantire l'unicità:

L'aggiunta di un vincolo univoco creerà automaticamente un indice albero B unico sulla colonna o sul gruppo di colonne elencate nel vincolo. Una restrizione di unicità che copre solo alcune righe non può essere scritta come un vincolo univoco, ma è possibile applicare tale restrizione creando un indice parziale univoco.

Nota: il modo preferito per aggiungere un vincolo univoco a una tabella è ALTER TABLE ... AGGIUNGI VINCOLO. L'uso di indici per imporre vincoli univoci potrebbe essere considerato un dettaglio di implementazione a cui non è possibile accedere direttamente. Bisogna, tuttavia, essere consapevoli del fatto che non è necessario creare manualmente indici su colonne univoche; in questo modo si duplicherebbe semplicemente l'indice creato automaticamente.

Quindi dovremmo aggiungere un vincolo, che crea un indice, per garantire unicità.

Come vedo questo problema?

Un "vincolo" mira a garantire in modo grammaticale che questa colonna sia unica, stabilisce una legge, una regola; mentre "indice" è semantico , su "come implementare, come raggiungere l'unicità, cosa significa unico quando si tratta di implementazione". Quindi, il modo in cui Postgresql lo implementa, è molto logico: in primo luogo, dichiari che una colonna dovrebbe essere unica, quindi Postgresql aggiunge l'implementazione dell'aggiunta di un indice univoco per te .


1
"Quindi penso che sia ciò che tu chiami" unicità parziale "aggiungendo un vincolo." gli indici possono applicarsi solo a un sottoinsieme ben definito dei record attraverso la whereclausola, quindi è possibile definire che i record sono IFF univoci che soddisfano alcuni criteri. Ciò disabilita semplicemente i vincoli per un insieme indefinito di record che precedono il vincolo che viene creato. È completamente diverso e quest'ultimo è significativamente meno utile, anche se è conveniente per le migrazioni progressive immagino.
Masklinn

0

C'è una differenza nel bloccaggio.
L'aggiunta di un indice non blocca l'accesso in lettura alla tabella.
L'aggiunta di un vincolo comporta un blocco della tabella (quindi tutte le selezioni sono bloccate) poiché viene aggiunto tramite ALTER TABLE .


0

Una cosa molto minore che può essere fatta solo con vincoli e non con indici è l'uso della ON CONFLICT ON CONSTRAINTclausola ( vedi anche questa domanda ).

Questo non funziona:

CREATE TABLE T (a INT PRIMARY KEY, b INT, c INT);
CREATE UNIQUE INDEX u ON t(b);

INSERT INTO T (a, b, c)
VALUES (1, 2, 3)
ON CONFLICT ON CONSTRAINT u
DO UPDATE SET c = 4
RETURNING *;

Produce:

[42704]: ERROR: constraint "u" for table "t" does not exist

Trasforma l'indice in un vincolo:

DROP INDEX u;
ALTER TABLE t ADD CONSTRAINT u UNIQUE (b);

E la INSERTdichiarazione ora funziona.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.