Contrainte d'exclusion
Je vous suggère plutôt d'utiliser une contrainte d'exclusion, qui est beaucoup plus simple, plus sûre et plus rapide :
Vous devez installer le module supplémentaire btree_gist
première. Voir les instructions et l'explication dans cette réponse connexe :
Et vous devez inclure "ParentID"
dans le tableau "Bar"
de manière redondante, ce qui sera un petit prix à payer. Les définitions de tableau pourraient ressembler à ceci :
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 &&)
);
J'ai également changé le type de données pour "Bar"."FooID"
de à int8
int4
. Il fait référence à "Foo"."FooID"
, qui est un serial
, c'est-à-dire int4
. Utilisez le type correspondant int4
(ou simplement integer
) pour plusieurs raisons, dont la performance.
Vous n'avez plus besoin d'un déclencheur (du moins pas pour cette tâche), et vous ne créez pas l'index plus, puisqu'il est créé implicitement par la contrainte d'exclusion."Bar_FooID_Timerange_idx"
Un index btree sur ("ParentID", "FooID")
sera très probablement utile, cependant :
CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");
Connexe :
J'ai choisi UNIQUE ("ParentID", "FooID")
et non l'inverse pour une raison, car il existe un autre index avec "FooID"
en tête dans l'une ou l'autre des tables :
A part :Je n'utilise jamais CaMeL entre guillemets doubles -identifiants de cas dans Postgres. Je ne le fais ici que pour me conformer à votre mise en page.
Éviter les colonnes redondantes
Si vous ne pouvez pas ou ne voulez pas inclure "Bar"."ParentID"
de manière redondante, il y a un autre voyou chemin - à condition que "Foo"."ParentID"
n'est jamais mis à jour . Assurez-vous de cela, avec un déclencheur par exemple.
Vous pouvez simuler un IMMUTABLE
fonction :
CREATE OR REPLACE FUNCTION f_parent_of_foo(int)
RETURNS int AS
'SELECT "ParentID" FROM public."Foo" WHERE "FooID" = $1'
LANGUAGE sql IMMUTABLE;
J'ai qualifié de schéma le nom de la table pour m'en assurer, en supposant que public
. Adaptez-vous à votre schéma.
Plus :
- CONSTRAINT pour vérifier les valeurs d'une table liée à distance (via une jointure, etc.)
- Est-ce que PostgreSQL prend en charge "insensible aux accents " classements ?
Utilisez-le ensuite dans la contrainte d'exclusion :
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&)
Tout en sauvegardant un int4
redondant colonne, la contrainte sera plus coûteuse à vérifier et toute la solution dépend de plus de conditions préalables.
Gérer les conflits
Vous pouvez envelopper INSERT
et UPDATE
dans une fonction plpgsql et piéger les éventuelles exceptions à la contrainte d'exclusion (23P01 exclusion_violation
) pour le gérer d'une manière ou d'une autre.
INSERT ...
EXCEPTION
WHEN exclusion_violation
THEN -- handle conflict
Exemple de code complet :
Gérer les conflits dans Postgres 9.5
Dans Postgres 9.5 vous pouvez gérer INSERT
directement avec la nouvelle implémentation "UPSERT". La documentation :
Cependant :
Mais vous pouvez toujours utiliser ON CONFLICT DO NOTHING
, évitant ainsi une éventuelle exclusion_violation
exceptions. Vérifiez simplement si des lignes ont bien été mises à jour, ce qui revient moins cher :
INSERT ...
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;
IF NOT FOUND THEN
-- handle conflict
END IF;
Cet exemple limite la vérification à la contrainte d'exclusion donnée. (J'ai nommé la contrainte explicitement à cette fin dans la définition du tableau ci-dessus.) Les autres exceptions possibles ne sont pas interceptées.