Uitsluitingsbeperking
Ik raad u aan in plaats daarvan een uitsluitingsbeperking te gebruiken, die veel eenvoudiger, veiliger en sneller is:
U moet de extra module btree_gist
eerst. Zie instructies en uitleg in dit gerelateerde antwoord:
En je moet "ParentID"
. opnemen in de tabel "balk"
overbodig, wat een kleine prijs zal zijn om te betalen. Tabeldefinities kunnen er als volgt uitzien:
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 &&)
);
Ik heb ook het gegevenstype gewijzigd voor "Bar"."FooID"
van naar int8
int4
. Het verwijst naar "Foo"."FooID"
, wat een serie
. is , d.w.z. int4
. Gebruik het overeenkomende type int4
(of gewoon geheel getal
) om verschillende redenen, waaronder prestatie.
Je hebt geen trigger meer nodig (tenminste niet voor deze taak), en je maakt de index niet meer, omdat het impliciet is gemaakt door de uitsluitingsbeperking."Bar_FooID_Timerange_idx"
niet aan
Een btree-index op ("ParentID", "FooID")
zal echter hoogstwaarschijnlijk nuttig zijn:
CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");
Gerelateerd:
Ik koos UNIEK ("ParentID", "FooID")
en niet omgekeerd met een reden, aangezien er een andere index is met leidende "FooID"
in beide tabellen:
Terzijde:Ik gebruik nooit dubbel geciteerde CaMeL -case-ID's in Postgres. Ik doe het hier alleen om te voldoen aan uw lay-out.
Vermijd overbodige kolom
Als u "Bar"."ParentID"
. niet kunt of wilt opnemen overbodig, er is nog een schurk manier - op voorwaarde dat "Foo"."ParentID"
is nooit bijgewerkt . Zorg daarvoor, bijvoorbeeld met een trigger.
Je kunt een IMMUTABLE
fake faken functie:
CREATE OR REPLACE FUNCTION f_parent_of_foo(int)
RETURNS int AS
'SELECT "ParentID" FROM public."Foo" WHERE "FooID" = $1'
LANGUAGE sql IMMUTABLE;
Ik heb de tabelnaam schematisch gekwalificeerd om er zeker van te zijn, ervan uitgaande dat public
. Aanpassen aan uw schema.
Meer:
- BEPERKING om waarden uit een op afstand gerelateerde tabel te controleren (via join etc.)
- Ondersteunt PostgreSQL "accent ongevoelig " collaties?
Gebruik het dan in de uitsluitingsbeperking:
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&)
Tijdens het opslaan van een overbodige int4
kolom, zal de beperking duurder zijn om te verifiëren en de hele oplossing hangt af van meer randvoorwaarden.
Conflicten afhandelen
Je zou INSERT
kunnen omwikkelen en UPDATE
omzetten in een plpgsql-functie en mogelijke uitzonderingen van de uitsluitingsbeperking opvangen (23P01 exception_violation
) om het op de een of andere manier aan te pakken.
INSERT ...
EXCEPTION
WHEN exclusion_violation
THEN -- handle conflict
Compleet codevoorbeeld:
Conflict in Postgres 9.5 afhandelen
In Postgres 9.5 u kunt omgaan met INSERT
direct met de nieuwe "UPSERT" implementatie. De documentatie:
Maar:
Maar je kunt nog steeds OP CONFLICT NIETS DOEN
. gebruiken , waardoor mogelijke exclusion_violation
. wordt vermeden uitzonderingen. Controleer gewoon of er rijen zijn bijgewerkt, wat goedkoper is:
INSERT ...
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;
IF NOT FOUND THEN
-- handle conflict
END IF;
In dit voorbeeld wordt de controle beperkt tot de gegeven uitsluitingsbeperking. (Ik heb de beperking expliciet voor dit doel genoemd in de bovenstaande tabeldefinitie.) Andere mogelijke uitzonderingen worden niet opgevangen.