Ограничение за изключване
Предлагам ви вместо това да използвате ограничение за изключване, което е много по-просто, по-безопасно и по-бързо:
Трябва да инсталирате допълнителния модул btree_gistкод>
първи. Вижте инструкции и обяснение в този свързан отговор:
И трябва да включите "ParentID"
в таблицата "Лента"
излишно, което ще бъде малка цена за плащане. Дефинициите на таблици могат да изглеждат така:
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 &&)
);
Също така промених типа данни за "Bar"."FooID"
от към int8
int4
. Той препраща към "Foo"."FooID"
, което е сериен
, т.е. int4
. Използвайте съвпадащия тип int4
(или просто цяло число
) поради няколко причини, една от които е производителността.
Вече не се нуждаете от тригер (поне не за тази задача) и не създавате индекса вече, тъй като е създаден имплицитно от ограничението за изключване."Bar_FooID_Timerange_idx"
Индекс на btree на ("ParentID", "FooID")
все пак най-вероятно ще бъде полезно:
CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");
Свързани:
Избрах UNIQUE ("ParentID", "FooID")
а не обратното с причина, тъй като има друг индекс с водещ "FooID"
в която и да е таблица:
Освен:Никога не използвам CaMeL в двойни кавички -идентификатори на случай в Postgres. Правя го тук само за да се съобразя с оформлението ви.
Избягвайте излишната колона
Ако не можете или не искате да включите "Bar"."ParentID"
излишно, има още един измамник начин - при условие, че "Foo"."ParentID"
никога не се актуализира . Уверете се в това, например с тригер.
Можете да фалшифицирате IMMUTABLE
функция:
CREATE OR REPLACE FUNCTION f_parent_of_foo(int)
RETURNS int AS
'SELECT "ParentID" FROM public."Foo" WHERE "FooID" = $1'
LANGUAGE sql IMMUTABLE;
Квалифицирах схематично името на таблицата, за да се уверя, че приемам public
. Адаптирайте се към вашата схема.
Още:
- ОГРАНИЧЕНИЕ за проверка на стойности от отдалечено свързана таблица (чрез присъединяване и др.)
- Поддържа ли PostgreSQL "нечувствителен към акцента " съпоставяне?
След това го използвайте в ограничението за изключване:
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&)
Докато запазвате един излишен int4
колона, проверката на ограничението ще бъде по-скъпа и цялото решение зависи от повече предварителни условия.
Обработване на конфликти
Можете да обвиете INSERT
и АКТУАЛИЗИРАНЕ
във функция plpgsql и улавяне на възможни изключения от ограничението за изключване (23P01 exclusion_violation
), за да го обработите по някакъв начин.
INSERT ...
EXCEPTION
WHEN exclusion_violation
THEN -- handle conflict
Пълен примерен код:
Обработване на конфликти в Postgres 9.5
В Postgres 9.5 можете да управлявате INSERT
директно с новата реализация "UPSERT". Документацията:
Въпреки това:
Но все още можете да използвате ПРИ КОНФЛИКТ НЕ ПРАВЕТЕ НИЩО
, като по този начин се избягва възможно exclusion_violation
изключения. Просто проверете дали някои редове са действително актуализирани, което е по-евтино:
INSERT ...
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;
IF NOT FOUND THEN
-- handle conflict
END IF;
Този пример ограничава проверката до даденото ограничение за изключване. (Наименувах ограничението изрично за тази цел в дефиницията на таблицата по-горе.) Други възможни изключения не се улавят.