Det här är ett knepigt problem. Men det kan göras med utlösare per kolumn och exekvering av villkorad utlösare som introduceras i PostgreSQL 9.0 .
Du behöver en "uppdaterad" flagga per rad för denna lösning. Använd en boolean
kolumn i samma tabell för enkelhets skull. Men det kan vara i en annan tabell eller till och med en tillfällig tabell per transaktion.
Den dyra nyttolasten exekveras en gång per rad där räknaren uppdateras (en eller flera gånger).
Detta bör också prestera ja, för att ...
- ... det undviker flera anrop av triggers vid roten (skalar väl)
- ... ändrar inte ytterligare rader (minimera tabelluppsvällning)
- ... kräver ingen dyr undantagshantering.
Tänk på följande
Demo
Testad i PostgreSQL 9.1 med ett separat schema x
som testmiljö.
Tabell och dummyrader
-- DROP SCHEMA x;
CREATE SCHEMA x;
CREATE TABLE x.tbl (
id int
,counter int
,trig_exec_count integer -- for monitoring payload execution.
,updated bool);
Infoga två rader för att visa att det fungerar med flera rader:
INSERT INTO x.tbl VALUES
(1, 0, 0, NULL)
,(2, 0, 0, NULL);
Triggerfunktioner och triggers
1.) Utför dyr nyttolast
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.) Flagga raden som uppdaterad.
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.) Återställ "uppdaterad" flagga.
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;
Triggernamn är relevanta! Kallas för samma händelse de körs i alfabetisk ordning.
1.) Nyttolast, endast om den inte är "uppdaterad" ännu:
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.) Flagga raden som uppdaterad, bara om den inte är "uppdaterad" ännu:
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.) Återställ flagga. Ingen ändlös loop på grund av triggertillstånd.
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();
Testa
Kör UPDATE
&SELECT
separat för att se den uppskjutna effekten. Om den körs tillsammans (i en transaktion) kommer SELECT att visa den nya tbl.counter
men den gamla tbl2.trig_exec_count
.
UPDATE x.tbl SET counter = counter + 1;
SELECT * FROM x.tbl;
Uppdatera nu räknaren flera gånger (i en transaktion). Nyttolasten kommer bara att exekveras en gång. 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;