Restricción única de Postgres vs índice

157

Como puedo entender la documentación, las siguientes definiciones son equivalentes:

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

Sin embargo, una nota en el manual de Postgres 9.4 dice:

La forma preferida de agregar una restricción única a una tabla es ALTER TABLE ... ADD CONSTRAINT. El uso de índices para imponer restricciones únicas podría considerarse un detalle de implementación al que no se debe acceder directamente.

(Editar: esta nota se eliminó del manual con Postgres 9.5).

¿Es solo una cuestión de buen estilo? ¿Cuáles son las consecuencias prácticas de elegir una de estas variantes (por ejemplo, en el rendimiento)?

Adam Piotrowski
fuente
23
La (única) diferencia práctica es que puede crear una clave foránea para una restricción única pero no para un índice único.
a_horse_with_no_name
29
Una ventaja al revés ( como surgió en otra pregunta recientemente ) es que puede tener un índice único parcial , como "Unique (foo) Where bar Is Null". AFAIK, no hay forma de hacerlo con una restricción.
IMSoP
3
@a_horse_with_no_name No estoy seguro de cuándo sucedió esto, pero esto ya no parece ser cierto. Este violín de SQL permite referencias de clave externa a un índice único: sqlfiddle.com/#!17/20ee9 ; EDITAR: agregar un 'filtro' al índice único hace que esto deje de funcionar (como se esperaba)
user1935361
1
de la documentación de postgres: PostgreSQL crea automáticamente un índice único cuando se define una restricción única o clave primaria para una tabla. postgresql.org/docs/9.4/static/indexes-unique.html
maggu
Estoy de acuerdo con @ user1935361, si no fuera posible crear una clave foránea para un índice único (con PG 10 al menos) me habría encontrado con este problema hace mucho tiempo.
Andy

Respuestas:

132

Tenía algunas dudas sobre este tema básico pero importante, así que decidí aprender con el ejemplo.

Creemos la tabla maestra de prueba con dos columnas, con_id con restricción única e ind_id indexada por índice único.

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)

En la descripción de la tabla (\ d en psql) puede distinguir la restricción única del índice único.

Unicidad

Verifiquemos la unicidad, por si acaso.

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

Funciona como se esperaba!

Llaves extranjeras

Ahora definiremos la tabla de detalles con dos claves foráneas que hacen referencia a nuestras dos columnas en 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)

Bueno, no hay errores. Asegurémonos de que funcione.

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

Se puede hacer referencia a ambas columnas en claves foráneas.

Restricción usando índice

Puede agregar restricciones de tabla utilizando un índice único existente.

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)

Ahora no hay diferencia entre la descripción de restricciones de columna.

Índices parciales

En la declaración de restricción de tabla no puede crear índices parciales. Viene directamente de la definición de create table .... En la declaración de índice único, puede configurar WHERE clausepara crear un índice parcial. También puede crear un índice en la expresión (no solo en la columna) y definir algunos otros parámetros (clasificación, orden de clasificación, colocación de NULL).

No puede agregar restricción de tabla utilizando índice parcial.

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.
klin
fuente
¿Es información real? especialmente sobre índices parciales
anatol
1
@anatol: sí, lo es.
klin
30

Una ventaja más de usar UNIQUE INDEXvs. UNIQUE CONSTRAINTes que puedes fácilmente DROP/ CREATEun índice CONCURRENTLY, mientras que con una restricción no puedes.

Vadim Zingertal
fuente
44
AFAIK no es posible soltar simultáneamente un índice único. postgresql.org/docs/9.3/static/sql-dropindex.html "Hay varias advertencias a tener en cuenta al usar esta opción. Solo se puede especificar un nombre de índice, y la opción CASCADE no es compatible. (Por lo tanto, un índice que admite una restricción de CLAVE ÚNICA o PRIMARIA no se puede eliminar de esta manera. ""
Rafał Cieślak
15

La unicidad es una restricción. Se implementa mediante la creación de un índice único, ya que un índice puede buscar rápidamente todos los valores existentes para determinar si ya existe un valor dado.

Conceptualmente, el índice es un detalle de implementación y la unicidad debe asociarse solo con restricciones.

El texto completo

Entonces el rendimiento de la velocidad debería ser el mismo

Eugen Konkov
fuente
4

Otra cosa que he encontrado es que puedes usar expresiones sql en índices únicos pero no en restricciones.

Entonces, esto no funciona:

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

pero siguiendo obras.

CREATE TABLE users (
    name text
);
CREATE UNIQUE INDEX uq_name on users (lower(name));
김민준
fuente
Yo usaría la citextextensión.
ceving
@ceving depende del caso de uso. a veces desea preservar la carcasa al tiempo que garantiza la unicidad entre mayúsculas y minúsculas
Sampson Crowley
2

Dado que varias personas han proporcionado ventajas de índices únicos sobre restricciones únicas, aquí hay un inconveniente: una restricción única puede diferirse (solo se verifica al final de la transacción), un índice único no puede serlo.

Masklinn
fuente
¿Cómo puede ser esto, dado que todas las restricciones únicas tienen un índice único?
Chris
1
Debido a que los índices no tienen una API para diferir, solo las restricciones sí, así que si bien la maquinaria de aplazamiento existe bajo la cubierta para admitir restricciones únicas, no hay forma de declarar un índice como diferible o diferirlo.
Masklinn
0

Leí esto en el documento:

AGREGAR table_constraint [NO VÁLIDO]

Este formulario agrega una nueva restricción a una tabla con la misma sintaxis que CREATE TABLE, además de la opción NOT VALID, que actualmente solo está permitida para restricciones de clave externa. Si la restricción está marcada NOT VALID, se omite la comprobación inicial potencialmente larga para verificar que todas las filas de la tabla satisfagan la restricción . La restricción aún se aplicará a las inserciones o actualizaciones posteriores (es decir, fallarán a menos que haya una fila coincidente en la tabla referenciada). Pero la base de datos no asumirá que la restricción se cumple para todas las filas de la tabla, hasta que se valide utilizando la opción VALIDATE CONSTRAINT.

Así que creo que es lo que ustedes llaman "unicidad parcial" al agregar una restricción.

Y, sobre cómo garantizar la singularidad:

Agregar una restricción única creará automáticamente un índice de árbol B único en la columna o grupo de columnas enumeradas en la restricción. Una restricción de unicidad que cubre solo algunas filas no puede escribirse como una restricción única, pero es posible hacer cumplir dicha restricción creando un índice parcial único.

Nota: La forma preferida de agregar una restricción única a una tabla es ALTER TABLE ... AGREGAR RESTRICCIÓN. El uso de índices para imponer restricciones únicas podría considerarse un detalle de implementación al que no se debe acceder directamente. Sin embargo, se debe tener en cuenta que no es necesario crear índices manualmente en columnas únicas; hacerlo simplemente duplicaría el índice creado automáticamente.

Por lo tanto, debemos agregar una restricción, que crea un índice, para garantizar la unicidad.

¿Cómo veo este problema?

Una "restricción" tiene como objetivo asegurar de manera gramatical que esta columna sea única, establece una ley, una regla; mientras que "index" es semántico , acerca de "cómo implementar, cómo lograr la unicidad, qué significa unico cuando se trata de implementación". Entonces, la forma en que Postgresql lo implementa es muy lógica: primero, declara que una columna debe ser única, luego Postgresql agrega la implementación de agregar un índice único para usted .

WesternGun
fuente
1
"Así que creo que es lo que llamas" unicidad parcial "al agregar una restricción". los índices pueden aplicarse solo a un subconjunto bien definido de los registros a través de la wherecláusula, por lo que puede definir que los registros son IFF únicos que satisfacen algunos criterios. Esto simplemente deshabilita las restricciones para un conjunto indefinido de registros que son anteriores a la restricción que se está creando. Es completamente diferente, y este último es significativamente menos útil, aunque es conveniente para migraciones progresivas, supongo.
Masklinn
0

Hay una diferencia en el bloqueo.
Agregar un índice no bloquea el acceso de lectura a la tabla.
Agregar una restricción pone un bloqueo de tabla (por lo que todas las selecciones están bloqueadas) ya que se agrega a través de ALTER TABLE .

Bax
fuente
0

Algo muy pequeño que se puede hacer solo con restricciones y no con índices es usar la ON CONFLICT ON CONSTRAINTcláusula ( ver también esta pregunta ).

Esto no funciona:

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

Convierta el índice en una restricción:

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

Y la INSERTdeclaración ahora funciona.

Lukas Eder
fuente