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.