Uteslutningsbegränsning
Jag föreslår att du istället använder en uteslutningsbegränsning, som är mycket enklare, säkrare och snabbare:
Du måste installera tilläggsmodulen btree_gist
först. Se instruktioner och förklaring i detta relaterade svar:
Och du måste inkludera "ParentID"
i tabellen "Bar"
överflödigt, vilket kommer att vara ett litet pris att betala. Tabelldefinitioner kan se ut så här:
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 &&)
);
Jag ändrade också datatypen för "Bar"."FooID"
från till int8
int4
. Den refererar till "Foo"."FooID"
, som är en serie
, dvs. int4
. Använd matchningstypen int4
(eller bara heltal
) av flera skäl, en av dem är prestanda.
Du behöver ingen utlösare längre (åtminstone inte för den här uppgiften), och du skapar inte indexet längre, eftersom det skapas implicit av uteslutningsrestriktionen."Bar_FooID_Timerange_idx"
Ett btree-index på ("ParentID", "FooID")
kommer dock med största sannolikhet att vara användbart:
CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");
Relaterat:
Jag valde UNIQUE ("Förälder-ID", "FooID")
och inte tvärtom av en anledning, eftersom det finns ett annat index med ledande "FooID"
i endera tabellen:
Bortsett från:Jag använder aldrig dubbla citerade CaMeL -caseidentifierare i Postgres. Jag gör det bara här för att följa din layout.
Undvik överflödig kolumn
Om du inte kan eller vill inkludera "Bar"."Föräldra-ID"
överflödigt finns det en annan skurk sätt - under förutsättning att "Foo"."Förälder-ID"
är aldrig uppdaterad . Se till det, med till exempel en trigger.
Du kan fejka 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;
Jag schemakvalificerade tabellnamnet för att vara säker, förutsatt att public
är . Anpassa dig till ditt schema.
Mer:
- BEGRÄNSNING för att kontrollera värden från en fjärrrelaterade tabell (via join etc.)
- Stöder PostgreSQL "accent okänslig " sammanställningar?
Använd det sedan i undantagsbegränsningen:
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&)
Samtidigt som du sparar en redundant int4
kolumn blir begränsningen dyrare att verifiera och hela lösningen beror på fler förutsättningar.
Hantera konflikter
Du kan slå in INSERT
och UPPDATERA
till en plpgsql-funktion och fånga möjliga undantag från undantagsbegränsningen (23P01 exclusion_violation
) för att hantera det på något sätt.
INSERT ...
EXCEPTION
WHEN exclusion_violation
THEN -- handle conflict
Komplett kodexempel:
Hantera konflikter i Postgres 9.5
I Postgres 9.5 du kan hantera INSERT
direkt med den nya "UPSERT"-implementeringen. Dokumentationen:
Men:
Men du kan fortfarande använda ON CONFLICT DO INGENTING
, och undviker därmed möjlig exclusion_violation
undantag. Kontrollera bara om några rader faktiskt har uppdaterats, vilket är billigare:
INSERT ...
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;
IF NOT FOUND THEN
-- handle conflict
END IF;
Det här exemplet begränsar kontrollen till den givna uteslutningsrestriktionen. (Jag namngav begränsningen uttryckligen för detta ändamål i tabelldefinitionen ovan.) Andra möjliga undantag fångas inte upp.