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.