Restricción de exclusión
Le sugiero que use una restricción de exclusión en su lugar, que es mucho más simple, más segura y más rápida:
Debe instalar el módulo adicional btree_gist
primero. Consulte las instrucciones y la explicación en esta respuesta relacionada:
Y debe incluir "ParentID"
en la tabla "Bar"
redundantemente, lo que será un pequeño precio a pagar. Las definiciones de las tablas podrían tener este aspecto:
CREATE TABLE "Foo" (
"FooID" serial PRIMARY KEY
"ParentID" int4 NOT NULL REFERENCES "Parent"
"Details1" varchar
CONSTRAINT foo_parent_foo_uni UNIQUE ("ParentID", "FooID") -- required for FK
);
CREATE TABLE "Bar" (
"ParentID" int4 NOT NULL,
"FooID" int4 NOT NULL REFERENCES "Foo" ("FooID"),
"Timerange" tstzrange NOT NULL,
"Detail1" varchar,
"Detail2" varchar,
CONSTRAINT "Bar_pkey" PRIMARY KEY ("FooID", "Timerange"),
CONSTRAINT bar_foo_fk
FOREIGN KEY ("ParentID", "FooID") REFERENCES "Foo" ("ParentID", "FooID"),
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist ("ParentID" WITH =, "Timerange" WITH &&)
);
También cambié el tipo de datos para "Bar"."FooID"
de a int8
int4
. Hace referencia a "Foo"."FooID"
, que es un serial
, es decir, int4
. Utilice el tipo de coincidencia int4
(o simplemente integer
) por varias razones, una de ellas es el rendimiento.
Ya no necesita un activador (al menos no para esta tarea), y no crea el índice más, ya que se crea implícitamente por la restricción de exclusión."Bar_FooID_Timerange_idx"
Un índice btree en ("ParentID", "FooID")
aunque lo más probable es que sea útil:
CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");
Relacionado:
Elegí UNIQUE ("ParentID", "FooID")
y no al revés por una razón, ya que hay otro índice con "FooID"
inicial en cualquier tabla:
Aparte:Nunca uso CaMeL entre comillas dobles -identificadores de casos en Postgres. Solo lo hago aquí para cumplir con su diseño.
Evite la columna redundante
Si no puede o no quiere incluir "Bar"."ParentID"
redundantemente, hay otro pícaro - con la condición de que "Foo"."ParentID"
nunca se actualiza . Asegúrate de eso, con un activador, por ejemplo.
Puedes falsificar un IMMUTABLE
función:
CREATE OR REPLACE FUNCTION f_parent_of_foo(int)
RETURNS int AS
'SELECT "ParentID" FROM public."Foo" WHERE "FooID" = $1'
LANGUAGE sql IMMUTABLE;
Califiqué el esquema del nombre de la tabla para asegurarme, asumiendo public
. Adaptarse a su esquema.
Más:
- RESTRICCIÓN para comprobar los valores de una tabla relacionada de forma remota (mediante unión, etc.)
- ¿Admite PostgreSQL "insensible al acento " colaciones?
Luego utilícelo en la restricción de exclusión:
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&)
Al guardar un int4
redundante columna, la restricción será más costosa de verificar y toda la solución depende de más condiciones previas.
Manejar conflictos
Podría envolver INSERT
y UPDATE
en una función plpgsql y atrape posibles excepciones de la restricción de exclusión (23P01 exclusion_violation
) para manejarlo de alguna manera.
INSERT ...
EXCEPTION
WHEN exclusion_violation
THEN -- handle conflict
Ejemplo de código completo:
Manejar conflictos en Postgres 9.5
En Postgres 9.5 puedes manejar INSERT
directamente con la nueva implementación "UPSERT". La documentación:
Sin embargo:
Pero aún puedes usar ON CONFLICT DO NOTHING
, evitando así posibles exclusion_violation
excepciones Simplemente verifique si alguna fila se actualizó realmente, lo cual es más económico:
INSERT ...
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;
IF NOT FOUND THEN
-- handle conflict
END IF;
Este ejemplo restringe la verificación a la restricción de exclusión dada. (Nombré la restricción explícitamente para este propósito en la definición de la tabla anterior). No se detectan otras posibles excepciones.