Eksklusionsbegrænsning
Jeg foreslår, at du i stedet bruger en udelukkelsesbegrænsning, som er meget enklere, sikrere og hurtigere:
Du skal installere det ekstra modul btree_gist
først. Se instruktioner og forklaring i dette relaterede svar:
Og du skal inkludere "Forældre-ID"
i tabellen "Bar"
overflødigt, hvilket vil være en lille pris at betale. Tabeldefinitioner kunne se sådan ud:
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 &&)
);
Jeg har også ændret datatypen for "Bar"."FooID"
fra int8
til int4
. Den refererer til "Foo"."FooID"
, som er en serie
, dvs. int4
. Brug matchningstypen int4
(eller bare heltal
) af flere årsager, en af dem er ydeevne.
Du behøver ikke længere en trigger (i hvert fald ikke til denne opgave), og du opretter ikke indekset "Bar_FooID_Timerange_idx"
mere, da det er skabt implicit af udelukkelsesbegrænsningen.
Et btree-indeks på ("ParentID", "FooID")
vil dog højst sandsynligt være nyttig:
CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");
Relateret:
Jeg valgte UNIQUE ("Forældre-ID", "FooID")
og ikke omvendt af en grund, da der er et andet indeks med førende "FooID"
i begge tabeller:
Bortset fra:Jeg bruger aldrig CaMeL i dobbelte citater -case-id'er
i Postgres. Jeg gør det kun her for at overholde dit layout.
Undgå overflødig kolonne
Hvis du ikke kan eller vil inkludere "Bar"."Forældre-ID"
overflødigt er der en anden slyngel måde - på den betingelse, at "Foo"."Forældre-ID"
er aldrig opdateret . Sørg for det, med for eksempel en trigger.
Du kan forfalske en IMMUTABLE
funktion:
CREATE OR REPLACE FUNCTION f_parent_of_foo(int)
RETURNS int AS
'SELECT "ParentID" FROM public."Foo" WHERE "FooID" = $1'
LANGUAGE sql IMMUTABLE;
Jeg skemakvalificerede tabelnavnet for at være sikker, forudsat offentlig
. Tilpas til dit skema.
Mere:
- BEGRÆNSNING for at kontrollere værdier fra en eksternt relateret tabel (via join osv.)
- Understøtter PostgreSQL "accent insensitive" "sammenstillinger?
Brug det derefter i ekskluderingsbegrænsningen:
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&)
Mens du gemmer en redundant int4
kolonne, vil begrænsningen være dyrere at verificere, og hele løsningen afhænger af flere forudsætninger.
Håndter konflikter
Du kan ombryde INSERT
og OPDATERING
ind i en plpgsql-funktion og fange mulige undtagelser fra ekskluderingsbegrænsningen (23P01 exclusion_violation
) for at håndtere det på en eller anden måde.
INSERT ...
EXCEPTION
WHEN exclusion_violation
THEN -- handle conflict
Komplet kodeeksempel:
Håndter konflikt i Postgres 9.5
I Postgres 9.5 du kan håndtere INSERT
direkte med den nye "UPSERT" implementering. Dokumentationen:
Dog:
Men du kan stadig bruge ON CONFLICT DO NOTTHING
, og dermed undgå mulig exclusion_violation
undtagelser. Bare tjek, om nogle rækker rent faktisk blev opdateret, hvilket er billigere:
INSERT ...
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;
IF NOT FOUND THEN
-- handle conflict
END IF;
Dette eksempel begrænser kontrollen til den givne udelukkelsesbegrænsning. (Jeg navngav begrænsningen eksplicit til dette formål i tabeldefinitionen ovenfor.) Andre mulige undtagelser er ikke fanget.