Ograniczenie wykluczenia
Proponuję zamiast tego użyć ograniczenia wykluczenia, które jest znacznie prostsze, bezpieczniejsze i szybsze:
Musisz zainstalować dodatkowy moduł btree_gist
pierwszy. Zobacz instrukcje i wyjaśnienia w tej powiązanej odpowiedzi:
Musisz dołączyć "Identyfikator rodzica"
w tabeli "Bar"
niepotrzebnie, co będzie niewielką ceną do zapłacenia. Definicje tabel mogą wyglądać tak:
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 &&)
);
Zmieniłem również typ danych dla "Bar"."FooID"
z do int8
int4
. Odwołuje się do "Foo"."FooID"
, który jest seryjnym
, czyli int4
. Użyj pasującego typu int4
(lub po prostu liczba całkowita
) z kilku powodów, jednym z nich jest wydajność.
Nie potrzebujesz już wyzwalacza (przynajmniej nie do tego zadania) i nie tworzysz indeksu więcej, ponieważ jest tworzony niejawnie przez ograniczenie wykluczenia."Bar_FooID_Timerange_idx"
Indeks btree na ("ParentID", "FooID")
najprawdopodobniej jednak się przyda:
CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");
Powiązane:
Wybrałem UNIQUE („ParentID”, „FooID”)
a nie odwrotnie z jakiegoś powodu, ponieważ istnieje inny indeks z wiodącym "FooID"
w dowolnej tabeli:
Na marginesie:Nigdy nie używam podwójnie cudzysłowu CaMeL -identyfikatory wielkości liter w Postgresie. Robię to tylko w celu zachowania zgodności z twoim układem.
Unikaj zbędnych kolumn
Jeśli nie możesz lub nie chcesz dołączyć "Bar"."ParentID"
niepotrzebnie, jest jeszcze jeden łotr sposób - pod warunkiem, że "Foo"."ParentID"
jest nigdy nie aktualizowany . Upewnij się o tym, na przykład za pomocą wyzwalacza.
Możesz sfałszować IMMUTABLE
funkcja:
CREATE OR REPLACE FUNCTION f_parent_of_foo(int)
RETURNS int AS
'SELECT "ParentID" FROM public."Foo" WHERE "FooID" = $1'
LANGUAGE sql IMMUTABLE;
Aby się upewnić, zakwalifikowałem nazwę tabeli według schematu, zakładając public
. Dostosuj się do swojego schematu.
Więcej:
- OGRANICZENIE do sprawdzania wartości ze zdalnie powiązanej tabeli (poprzez dołączenie itp.)
- Czy PostgreSQL obsługuje „niewrażliwe na akcenty” " zestawienia?
Następnie użyj go w ograniczeniu wykluczenia:
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&)
Podczas zapisywania jednego nadmiarowego int4
kolumna, ograniczenie będzie droższe do zweryfikowania, a całe rozwiązanie zależy od większej liczby warunków wstępnych.
Rozwiązywanie konfliktów
Możesz zawinąć INSERT
i AKTUALIZUJ
do funkcji plpgsql i przechwytuje możliwe wyjątki od ograniczenia wykluczenia (23P01 exclude_violation
) aby sobie z tym poradzić.
INSERT ...
EXCEPTION
WHEN exclusion_violation
THEN -- handle conflict
Pełny przykład kodu:
Rozwiąż konflikt w Postgresie 9.5
W Postgresie 9,5 możesz obsłużyć INSERT
bezpośrednio z nową implementacją „UPSERT”. Dokumentacja:
Jednak:
Ale nadal możesz użyć W KONFLIKCIE NIC NIE RÓB
, unikając w ten sposób możliwego exclusion_violation
wyjątki. Po prostu sprawdź, czy jakieś wiersze zostały rzeczywiście zaktualizowane, co jest tańsze:
INSERT ...
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;
IF NOT FOUND THEN
-- handle conflict
END IF;
Ten przykład ogranicza sprawdzanie do podanego ograniczenia wykluczenia. (W tym celu nazwałem ograniczenie wyraźnie w powyższej definicji tabeli.) Inne możliwe wyjątki nie są przechwytywane.