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 til int8
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 mere, da det er skabt implicit af udelukkelsesbegrænsningen."Bar_FooID_Timerange_idx"
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.