Restrição de exclusão
Sugiro que você use uma restrição de exclusão, que é muito mais simples, segura e rápida:
Você precisa instalar o módulo adicional
btree_gist
primeiro. Veja instruções e explicações nesta resposta relacionada:E você precisa incluir
"ParentID" na tabela "Barra" redundante, o que será um pequeno preço a pagar. As definições de tabela podem ser assim: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 &&)
); Também alterei o tipo de dados para
"Bar",."FooID" de int8 int4 . Faz referência a "Foo","FooID" , que é um serial , ou seja, int4 . Use o tipo de correspondência int4 (ou apenas inteiro ) por vários motivos, sendo um deles o desempenho. Você não precisa mais de um gatilho (pelo menos não para esta tarefa) e não cria o índice
"Bar_FooID_Timerange_idx" Um índice btree em
("ParentID", "FooID") provavelmente será útil, no entanto:CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");
Relacionado:
Eu escolhi
UNIQUE ("ParentID", "FooID") e não o contrário por um motivo, já que existe outro índice com "FooID" à esquerda em qualquer tabela:Além:Eu nunca uso CaMeL entre aspas duplas -identificadores de caso em Postgres. Eu só faço isso aqui para cumprir o seu layout.
Evite coluna redundante
Se você não puder ou não quiser incluir
"Bar",."ParentID" redundantemente, há outro trapaceiro maneira - com a condição de que "Foo",."ParentID" é nunca atualizado . Certifique-se disso, com um gatilho, por exemplo. Você pode falsificar um
IMMUTABLE função:CREATE OR REPLACE FUNCTION f_parent_of_foo(int)
RETURNS int AS
'SELECT "ParentID" FROM public."Foo" WHERE "FooID" = $1'
LANGUAGE sql IMMUTABLE;
Eu qualifiquei o nome da tabela para ter certeza, assumindo
public . Adapte-se ao seu esquema. Mais:
- CONSTRAINT para verificar valores de uma tabela relacionada remotamente (via junção etc.)
- O PostgreSQL suporta "sotaque insensível " agrupamentos?
Em seguida, use-o na restrição de exclusão:
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&) Ao salvar um
int4 redundante coluna, a restrição será mais cara para verificar e toda a solução depende de mais pré-condições. Lidar com conflitos
Você pode envolver
INSERT e ATUALIZAR em uma função plpgsql e interceptar possíveis exceções da restrição de exclusão (23P01 exclude_violation ) para lidar com isso de alguma forma. INSERT ...
EXCEPTION
WHEN exclusion_violation
THEN -- handle conflict
Exemplo de código completo:
Lidar com conflitos no Postgres 9.5
No Postgres 9.5 você pode lidar com
INSERT diretamente com a nova implementação "UPSERT". A documentação:
No entanto:
Mas você ainda pode usar
ON CONFLICT DO NOTHING , evitando assim possíveis exclusion_violation exceções. Basta verificar se alguma linha foi realmente atualizada, o que é mais barato:INSERT ...
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;
IF NOT FOUND THEN
-- handle conflict
END IF;
Este exemplo restringe a verificação à restrição de exclusão fornecida. (Eu nomeei a restrição explicitamente para este propósito na definição da tabela acima.) Outras exceções possíveis não são capturadas.