Omezení vyloučení
Navrhuji, abyste místo toho použili omezení vyloučení, které je mnohem jednodušší, bezpečnější a rychlejší:
Musíte nainstalovat doplňkový modul btree_gist
První. Viz pokyny a vysvětlení v této související odpovědi:
A musíte zahrnout "ParentID"
v tabulce "Bar"
nadbytečně, což bude malá cena. Definice tabulek mohou vypadat takto:
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 &&)
);
Také jsem změnil typ dat pro "Bar"."FooID"
z na int8
int4
. Odkazuje na "Foo"."FooID"
, což je serial
, tj. int4
. Použijte typ shody int4
(nebo jen integer
) z několika důvodů, jedním z nich je výkon.
Už nepotřebujete spouštěč (alespoň ne pro tento úkol) a nevytváříte index nic víc, protože je vytvořeno implicitně omezením vyloučení."Bar_FooID_Timerange_idx"
Index btree na ("ParentID", "FooID")
s největší pravděpodobností však bude užitečné:
CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");
Související:
Vybral jsem UNIQUE ("ParentID", "FooID")
a ne naopak z nějakého důvodu, protože existuje další index s úvodním "FooID"
v obou tabulkách:
Stranou:Nikdy nepoužívám CaMeL s dvojitými uvozovkami -identifikátory případu v Postgresu. Dělám to jen proto, abych vyhověl vašemu rozvržení.
Vyhněte se nadbytečným sloupcům
Pokud nemůžete nebo nechcete zahrnout "Bar"."ParentID"
nadbytečně je tu další darebák způsobem - za podmínky, že "Foo"."ParentID"
nikdy aktualizováno . Ujistěte se o tom, například pomocí spouštěče.
Můžete předstírat IMMUTABLE
funkce:
CREATE OR REPLACE FUNCTION f_parent_of_foo(int)
RETURNS int AS
'SELECT "ParentID" FROM public."Foo" WHERE "FooID" = $1'
LANGUAGE sql IMMUTABLE;
Pro jistotu jsem název tabulky kvalifikoval podle schématu, za předpokladu public
. Přizpůsobte se svému schématu.
Více:
- OMEZENÍ ke kontrole hodnot ze vzdáleně související tabulky (přes spojení atd.)
- Podporuje PostgreSQL "accent insensitive" " kolace?
Pak jej použijte v omezení vyloučení:
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&)
Při ukládání jednoho nadbytečného int4
sloupec, bude ověření nákladnější a celé řešení závisí na více předpokladech.
Řešení konfliktů
Můžete zalomit INSERT
a UPDATE
do funkce plpgsql a zachytit možné výjimky z omezení vyloučení (23P01 exclusion_violation
), abych to nějak zvládl.
INSERT ...
EXCEPTION
WHEN exclusion_violation
THEN -- handle conflict
Kompletní příklad kódu:
Řešení konfliktů v Postgres 9.5
V Postgres 9.5 můžete zpracovat INSERT
přímo s novou implementací "UPSERT". Dokumentace:
Nicméně:
Stále však můžete použít ON CONFLICT DO NOTHING
, čímž se vyhnete možnému exclusion_violation
výjimky. Stačí zkontrolovat, zda byly nějaké řádky skutečně aktualizovány, což je levnější:
INSERT ...
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;
IF NOT FOUND THEN
-- handle conflict
END IF;
Tento příklad omezuje kontrolu na danou podmínku vyloučení. (Omezení jsem výslovně pojmenoval pro tento účel v definici tabulky výše.) Další možné výjimky nejsou zachyceny.