Meu problema
Considere uma tabela t
com muitas atualizações frequentes de usuários, das quais apenas as últimas são relevantes.
Para manter o tamanho da tabela razoável, sempre que uma nova linha é inserida, as linhas antigas da mesma user_id
são excluídas. Para manter um arquivo, a linha também é gravada em t_history
.
Ambos t
e t_history
têm o mesmo esquema, no qual id
é a bigserial
com uma restrição de chave primária.
Implementação
Procedimento armazenado
CREATE FUNCTION update_t_history()
RETURNS trigger
AS
$$
declare
BEGIN
-- Insert the row to the t_history table. `id` is autoincremented
INSERT INTO t_history (a, b, c, ...)
VALUES (NEW.a, NEW.b, NEW.c, ...);
-- Delete old rows from the t table, keep the newest 10
DELETE FROM t WHERE id IN (
SELECT id FROM t
WHERE user_id = NEW.user_id
ORDER BY id DESC
OFFSET 9);
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Gatilho de inserção correspondente:
CREATE TRIGGER t_insertion_trigger
AFTER INSERT ON t
FOR EACH ROW
EXECUTE PROCEDURE update_t_history();
O erro
O gatilho funciona bem, mas quando executo algumas dezenas de inserções em uma única transação, recebo o seguinte erro:
BEGIN
ERROR: duplicate key value violates unique constraint "t_history_pkey"
DETAIL: Key (id)=(196) already exists.
Atualizações
- O
id
campo em ambas as tabelas (de\d+ t
):id|bigint|not null default nextval('t_id_seq'::regclass)
"t_pkey" PRIMARY KEY, btree (id)
- A versão do PostgreSQL é 9.3.
Alguma ideia de por que o procedimento armazenado quebra a restrição de chave primária nas transações?
Por que é
t_history.id
auto-incrementado em primeiro lugar? Se "ambost
et_history
tiverem o mesmo esquema" et.id
for um PK serial, basta copiar linhas inteiras.Também sugiro que você copie apenas as linhas que realmente exclui de
t
parat_history
- em um CTE de modificação de dados. Dessa forma, você não terá linhas sobrepostas (o que pode ser parte do problema).A nova linha já está visível em um
AFTER
gatilho.