Dit is een lastig probleem. Maar het kan worden gedaan met triggers per kolom en voorwaardelijke triggeruitvoering geïntroduceerd in PostgreSQL 9.0 .
Je hebt een "bijgewerkte" vlag per rij . nodig voor deze oplossing. Gebruik een boolean
voor de eenvoud in dezelfde tabel. Maar het kan in een andere tafel zijn of zelfs een tijdelijke tafel per transactie.
De dure payload wordt eenmaal per rij . uitgevoerd waar de teller wordt bijgewerkt (eenmalig of meerdere keren).
Dit moet ook presteren wel, omdat ...
- ... het vermijdt meerdere aanroepen van triggers bij de root (schaalt goed)
- ... verandert geen extra rijen (minimaliseer de opgeblazenheid van de tabel)
- ... heeft geen dure exception handling nodig.
Overweeg het volgende
Demo
Getest in PostgreSQL 9.1 met een apart schema x
als testomgeving.
Tabellen en dummyrijen
-- DROP SCHEMA x;
CREATE SCHEMA x;
CREATE TABLE x.tbl (
id int
,counter int
,trig_exec_count integer -- for monitoring payload execution.
,updated bool);
Voeg twee rijen in om te laten zien dat het werkt met meerdere rijen:
INSERT INTO x.tbl VALUES
(1, 0, 0, NULL)
,(2, 0, 0, NULL);
Triggerfuncties en triggers
1.) Voer dure payload uit
CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_1()
RETURNS trigger AS
$BODY$
BEGIN
-- PERFORM some_expensive_procedure(NEW.id);
-- Update trig_exec_count to count execution of expensive payload.
-- Could be in another table, for simplicity, I use the same:
UPDATE x.tbl t
SET trig_exec_count = trig_exec_count + 1
WHERE t.id = NEW.id;
RETURN NULL; -- RETURN value of AFTER trigger is ignored anyway
END;
$BODY$ LANGUAGE plpgsql;
2.) Markeer rij als bijgewerkt.
CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_2()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE x.tbl
SET updated = TRUE
WHERE id = NEW.id;
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
3.) Reset de "bijgewerkte" vlag.
CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_3()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE x.tbl
SET updated = NULL
WHERE id = NEW.id;
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
Triggernamen zijn relevant! Opgeroepen voor hetzelfde evenement worden ze in alfabetische volgorde uitgevoerd.
1.) Payload, alleen als deze nog niet "bijgewerkt" is:
CREATE CONSTRAINT TRIGGER upaft_counter_change_1
AFTER UPDATE OF counter ON x.tbl
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
WHEN (NEW.updated IS NULL)
EXECUTE PROCEDURE x.trg_upaft_counter_change_1();
2.) Markeer rij als bijgewerkt, alleen als deze nog niet "bijgewerkt" is:
CREATE TRIGGER upaft_counter_change_2 -- not deferred!
AFTER UPDATE OF counter ON x.tbl
FOR EACH ROW
WHEN (NEW.updated IS NULL)
EXECUTE PROCEDURE x.trg_upaft_counter_change_2();
3.) Vlag opnieuw instellen. Geen eindeloze lus vanwege triggerconditie.
CREATE CONSTRAINT TRIGGER upaft_counter_change_3
AFTER UPDATE OF updated ON x.tbl
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
WHEN (NEW.updated) --
EXECUTE PROCEDURE x.trg_upaft_counter_change_3();
Test
Voer UPDATE
uit &SELECT
apart om het uitgestelde effect te zien. Als ze samen worden uitgevoerd (in één transactie), toont de SELECT de nieuwe tbl.counter
maar de oude tbl2.trig_exec_count
.
UPDATE x.tbl SET counter = counter + 1;
SELECT * FROM x.tbl;
Werk de teller nu meerdere keren bij (in één transactie). De payload wordt maar één keer uitgevoerd. Voilá!
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
UPDATE x.tbl SET counter = counter + 1;
SELECT * FROM x.tbl;