SlideShare a Scribd company logo
Introduction to
Triggers
Who am I?
• Jim Mlodgenski
– jimm@openscg.com
– @jim_mlodgenski
• Director
– United States PostgreSQL (www.postgresql.us)
• Co-organizer of
– Philly PUG (www.phlpug.org)
– NYC PUG (www.nycpug.org)
• CTO, OpenSCG
– www.openscg.com
An Introduction To PostgreSQL Triggers
Triggers
• Code that gets executed when an
event happens in the database
– INSERT, UPDATE, DELETE
• Event Triggers fire on DDL
– CREATE, DROP, ALTER
– Still not mature yet
●
(limited functionaity)
Use Cases
• Table Partitioning
• Automatically generate derived column
values
• Enforce complex constraints
• Enforce referential integrity across nodes in a
distributed database
• Provide transparent event logging
• Provide auditing
• Invalidate cache entries
Structure
● Trigger
● Trigger Function
CREATE TRIGGER foo_trg
BEFORE UPDATE ON foo
FOR EACH ROW
EXECUTE PROCEDURE
foo_update();
Trigger Events
● Insert
● Update
● Delete
● Truncate
Trigger Timing
● Before
– The trigger is fired before the change is made
to the table
● After
– The trigger is fired after the change is made to
the table
Trigger Frequency
● For Each Row
– The trigger is fired once each time a row is
affected
● For Each Statement
– The trigger is fired once each time a statement
is executed
Trigger Overhead
CREATE UNLOGGED TABLE trigger_test (
key serial primary key,
value varchar,
insert_ts timestamp,
update_ts timestamp
);
INSERT INTO trigger_test (value) VALUES
(‘hello’);
set keys :scale
setrandom key 1 :keys
UPDATE trigger_test SET value = 'HELLO' WHERE
key = :key;
Trigger Overhead
pgbench -n -t 100000
-f INSERTS.pgbench postgres
pgbench -n -s 100000 -t 10000
-f UPDATES.pgbench postgres
Inserts: 4510 tps
Updates: 4349 tps
Trigger Overhead
CREATE FUNCTION empty_trigger()
RETURNS trigger AS $$
BEGIN
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER empty_trigger BEFORE
INSERT OR UPDATE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE
empty_trigger();
Trigger Overhead
pgbench -n -t 100000
-f INSERTS.pgbench postgres
pgbench -n -s 100000 -t 10000
-f UPDATES.pgbench postgres
Inserts: 4296 tps (4.8% overhead)
Updates: 3988 tps (8.3% overhead)
Trigger Arguments
● NEW
– Variable holding the new row for
INSERT/UPDATE operations in row-level triggers
● OLD
– Variable holding the old row for UPDATE/DELETE
operations in row-level triggers
NEW vs OLD
CREATE TABLE audit (
event_time timestamp NOT NULL,
user_name varchar NOT NULL,
old_row json,
new_row json
);
NEW vs OLD (cont.)
CREATE OR REPLACE FUNCTION audit_trigger()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit
VALUES (CURRENT_TIMESTAMP,
CURRENT_USER,
row_to_json(OLD),
row_to_json(NEW));
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
NEW vs OLD (cont.)
CREATE TRIGGER audit_trigger
AFTER UPDATE ON pgbench_branches
FOR EACH ROW
EXECUTE PROCEDURE audit_trigger();
Trigger Arguments (cont.)
● TG_OP
– A string of INSERT, UPDATE, DELETE, or TRUNCATE
telling for which operation the trigger was fired
● TG_NAME
– Variable that contains the name of the trigger actually
fired
● TG_WHEN
– A string of BEFORE, AFTER, or INSTEAD OF, depending
on the trigger's definition
● TG_LEVEL
– A string of either ROW or STATEMENT depending on the
trigger's definition
TG_OP
CREATE TABLE audit (
event_time timestamp NOT NULL,
user_name varchar NOT NULL,
operation varchar NOT NULL,
old_row json,
new_row json
);
TG_OP (cont.)
CREATE OR REPLACE FUNCTION audit_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
INSERT INTO audit
VALUES (CURRENT_TIMESTAMP, CURRENT_USER,TG_OP,
row_to_json(OLD), null);
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO audit
VALUES (CURRENT_TIMESTAMP, CURRENT_USER,TG_OP,
row_to_json(OLD), row_to_json(NEW));
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO audit
VALUES (CURRENT_TIMESTAMP, CURRENT_USER,TG_OP,
null, row_to_json(NEW));
RETURN NEW;
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
TG_OP (cont.)
CREATE TRIGGER audit_trigger
AFTER UPDATE OR INSERT OR DELETE
ON pgbench_branches
FOR EACH ROW
EXECUTE PROCEDURE audit_trigger();
Trigger Arguments (cont.)
● TG_TABLE_NAME
– The name of the table that caused the trigger
invocation.
● TG_RELNAME
– The name of the table that caused the trigger
invocation
● TG_RELID
– The object ID of the table that caused the trigger
invocation
● TG_TABLE_SCHEMA
– The name of the schema of the table that caused the
trigger invocation
TG_TABLE_NAME
CREATE TABLE audit (
event_time timestamp NOT NULL,
user_name varchar NOT NULL,
operation varchar NOT NULL,
table_name varchar NOT NULL,
old_row json,
new_row json
);
TG_TABLE_NAME (cont.)
CREATE OR REPLACE FUNCTION audit_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
INSERT INTO audit
VALUES (CURRENT_TIMESTAMP, CURRENT_USER,TG_OP,
TG_TABLE_NAME, row_to_json(OLD), null);
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO audit
VALUES (CURRENT_TIMESTAMP, CURRENT_USER,TG_OP,
TG_TABLE_NAME, row_to_json(OLD), row_to_json(NEW));
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO audit
VALUES (CURRENT_TIMESTAMP, CURRENT_USER,TG_OP,
TG_TABLE_NAME, null, row_to_json(NEW));
RETURN NEW;
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
TG_TABLE_NAME (cont.)
CREATE TRIGGER audit_trigger
AFTER UPDATE OR INSERT OR DELETE
ON pgbench_branches
FOR EACH ROW
EXECUTE PROCEDURE audit_trigger();
CREATE TRIGGER audit_trigger
AFTER UPDATE OR INSERT OR DELETE
ON pgbench_tellers
FOR EACH ROW
EXECUTE PROCEDURE audit_trigger();
Trigger Arguments (cont.)
● TG_NARGS
– The number of arguments given to the trigger
procedure in the CREATE TRIGGER statement
● TG_ARGV[]
– The arguments from the CREATE TRIGGER
statement
Trigger Use Cases
● Table Partitioning
– Splitting what is logically one large table into
smaller physical pieces
– Used to:
● Increase performance
● Archive data
● Storage tiering
Table Partitioning
CREATE TABLE audit_2014 (
CHECK ( event_time >= DATE '2014-01-01'
AND event_time < DATE '2015-01-01')
) INHERITS (audit);
CREATE TABLE audit_2015 (
CHECK ( event_time >= DATE '2015-01-01'
AND event_time < DATE '2016-01-01')
) INHERITS (audit);
Table Partitioning (cont.)
CREATE OR REPLACE FUNCTION partition_audit_trigger()
RETURNS TRIGGER AS $$
BEGIN
EXECUTE 'INSERT INTO audit_' ||
to_char(NEW.event_time, 'YYYY') ||
' VALUES ($1, $2, $3, $4, $5, $6)'
USING NEW.event_time, NEW.user_name, NEW.operation,
NEW.table_name, NEW.old_row, NEW.new_row;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
Table Partitioning (cont.)
CREATE TRIGGER partition_audit_trigger
BEFORE INSERT ON audit
FOR EACH ROW
EXECUTE PROCEDURE
partition_audit_trigger();
Partitioning Performance
CREATE OR REPLACE FUNCTION partition_audit_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF ( NEW.event_time >= DATE '2015-01-01' AND
NEW.event_time < DATE '2016-01-01' ) THEN
INSERT INTO audit_2015 VALUES (NEW.*);
ELSIF ( NEW.event_time >= DATE '2014-01-01' AND
NEW.event_time < DATE '2015-01-01' ) THEN
INSERT INTO audit_2014 VALUES (NEW.*);
ELSE
RAISE EXCEPTION 'Date out of range. Fix
partition_audit_trigger() function!';
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
Moving Partitions
CREATE TRIGGER move_partition_audit_trigger
BEFORE UPDATE
ON audit_2014
FOR EACH ROW EXECUTE PROCEDURE
move_partition_audit_trigger('2014-01-01', '2015-01-01');
CREATE TRIGGER move_partition_audit_trigger
BEFORE UPDATE
ON audit_2015
FOR EACH ROW EXECUTE PROCEDURE
move_partition_audit_trigger('2015-01-01', '2016-01-01');
Moving Partitions (cont.)
CREATE OR REPLACE FUNCTION move_partition_audit_trigger()
RETURNS TRIGGER AS $$
DECLARE
start_date DATE;
end_date DATE;
BEGIN
start_date := TG_ARGV[0];
end_date := TG_ARGV[1];
IF ( NEW.event_time IS DISTINCT FROM OLD.event_time ) THEN
IF (NEW.event_time < start_date OR NEW.event_time >= end_date) THEN
EXECUTE 'DELETE FROM ' || TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME ||
' WHERE ctid = $1'
USING OLD.ctid;
INSERT INTO audit VALUES (NEW.*);
RETURN null;
END IF;
END IF;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Moving Partitions (cont.)
CREATE TRIGGER move_partition_audit_trigger
BEFORE UPDATE
ON audit_2014
FOR EACH ROW
WHEN (NEW.event_time IS DISTINCT FROM OLD.event_time)
EXECUTE PROCEDURE
move_partition_audit_trigger('2014-01-01', '2015-01-01');
CREATE TRIGGER move_partition_audit_trigger
BEFORE UPDATE
ON audit_2015
FOR EACH ROW
WHEN (NEW.event_time IS DISTINCT FROM OLD.event_time)
EXECUTE PROCEDURE
move_partition_audit_trigger('2015-01-01', '2016-01-01');
Trigger Use Cases
● Calculate columns
– Calculate complex values
– Extract values from complex structures
– Used to:
● Increase performance
● Simplify queries
Extract JSON
$ head -n 5 zips.json
{ "_id" : "01001", "city" : "AGAWAM",
"loc" : [ -72.622739, 42.070206 ], "pop" : 15338, "state" : "MA" }
{ "_id" : "01002", "city" : "CUSHMAN",
"loc" : [ -72.51564999999999, 42.377017 ], "pop" : 36963, "state" : "MA" }
{ "_id" : "01005", "city" : "BARRE",
"loc" : [ -72.10835400000001, 42.409698 ], "pop" : 4546, "state" : "MA" }
{ "_id" : "01007", "city" : "BELCHERTOWN",
"loc" : [ -72.41095300000001, 42.275103 ], "pop" : 10579, "state" : "MA" }
{ "_id" : "01008", "city" : "BLANDFORD",
"loc" : [ -72.936114, 42.182949 ], "pop" : 1240, "state" : "MA" }
CREATE TABLE zips (
zip_code varchar PRIMARY KEY,
state varchar,
data json
);
Extract JSON
CREATE OR REPLACE FUNCTION extract_data_trigger()
RETURNS TRIGGER AS $$
BEGIN
NEW.zip_code := NEW.data->>'_id';
NEW.state := NEW.data->>'state';
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER extract_data_trigger
BEFORE UPDATE OR INSERT
ON zips
FOR EACH ROW EXECUTE PROCEDURE extract_data_trigger();
Trigger Use Cases
● Cache invalidation
– Remove stale entries from a cache
– The database tracks all data so is the single
source of truth
– Used to:
● Simplify cache management
● Remove application complexity
Note: Foreign Data Wrappers simplify this
process significantly
Cache Invalidation
CREATE TABLE users (
id serial PRIMARY KEY,
first_name varchar,
last_name varchar,
email_address varchar NOT NULL,
password_md5 varchar NOT NULL
);
CREATE OR REPLACE FUNCTION remove_cache_trigger()
RETURNS TRIGGER AS $$
BEGIN
DELETE from myredis_cache
WHERE key = OLD.id::varchar;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER remove_cache_trigger
AFTER UPDATE OR DELETE
ON users
FOR EACH ROW EXECUTE PROCEDURE remove_cache_trigger();
Cache Invalidation - Async
CREATE OR REPLACE FUNCTION remove_cache_trigger()
RETURNS TRIGGER AS $$
BEGIN
PERFORM pg_notify(TG_TABLE_NAME, OLD.id::varchar);
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Things to Remember
● Triggers are part of the parent
transaction
– The trigger fails, the main transaction fails
– If the trigger takes a long time, the whole
transaction timing is affected
● Triggers can be difficult to debug
– Especially cascaded triggers
Trigger Function Language
Questions?

More Related Content

PDF
PostgreSQL Performance Tuning
PPTX
Introduction to PostgreSQL
PDF
MySQL Tutorial For Beginners | Relational Database Management System | MySQL ...
PPT
SQL subquery
PPTX
PostgreSQL Database Slides
PDF
InnoDB MVCC Architecture (by 권건우)
PDF
Sql query patterns, optimized
PostgreSQL Performance Tuning
Introduction to PostgreSQL
MySQL Tutorial For Beginners | Relational Database Management System | MySQL ...
SQL subquery
PostgreSQL Database Slides
InnoDB MVCC Architecture (by 권건우)
Sql query patterns, optimized

What's hot (20)

PDF
The MySQL Query Optimizer Explained Through Optimizer Trace
PPTX
Mongo DB 성능최적화 전략
PPT
MySQL Functions
PDF
MySQL Query And Index Tuning
PDF
MySQL partitions tutorial
PDF
MySQL for beginners
PPT
Sql basics and DDL statements
PPTX
5. stored procedure and functions
PDF
Sql Antipatterns Strike Back
ODP
Ms sql-server
PDF
Oracle SQL Basics
PPTX
PPT
Spring data presentation
PPTX
SQL Queries Information
PDF
Mysql Explain Explained
PPT
MySql slides (ppt)
PPT
SQL Tutorial - Basic Commands
PPTX
Triggers
PPT
Mysql
PPT
SQL select clause
The MySQL Query Optimizer Explained Through Optimizer Trace
Mongo DB 성능최적화 전략
MySQL Functions
MySQL Query And Index Tuning
MySQL partitions tutorial
MySQL for beginners
Sql basics and DDL statements
5. stored procedure and functions
Sql Antipatterns Strike Back
Ms sql-server
Oracle SQL Basics
Spring data presentation
SQL Queries Information
Mysql Explain Explained
MySql slides (ppt)
SQL Tutorial - Basic Commands
Triggers
Mysql
SQL select clause
Ad

Similar to An Introduction To PostgreSQL Triggers (20)

PPTX
11 - Trigger mysql advance materi for student.pptx
PDF
Advanced Postgres Monitoring
PPTX
basicofunit-4-250728105436-7464afe1.pptx
PPTX
basicofunit-4-250728105436-7464afe1.pptx
PPTX
Basic information of unit-4 form of ppt.
PPTX
Unit 4
PDF
Keith Fiske - When PostgreSQL Can't, You Can @ Postgres Open
PDF
Lecture Notes Unit5 chapter17 Stored procedures and functions
PDF
Major features postgres 11
 
PPTX
Procedures and triggers in SQL
PDF
prohuddle-utPLSQL v3 - Ultimate unit testing framework for Oracle
PDF
PostgreSQL 9.5 Features
PPTX
Lesson-5-Complex-Queries-Aggregate-Function-Nested-Queries-Triggers.pptx
PPTX
Oracle trigger
PDF
Function Procedure Trigger Partition.pdf
PPT
Tony jambu (obscure) tools of the trade for tuning oracle sq ls
PDF
Database Automation with MySQL Triggers and Event Schedulers
PDF
Changing your huge table's data types in production
PPTX
Oracle_Audit_APEX IOUG Collaborate 14
PDF
Below is the question I need help with. It need to be done in Java. .pdf
11 - Trigger mysql advance materi for student.pptx
Advanced Postgres Monitoring
basicofunit-4-250728105436-7464afe1.pptx
basicofunit-4-250728105436-7464afe1.pptx
Basic information of unit-4 form of ppt.
Unit 4
Keith Fiske - When PostgreSQL Can't, You Can @ Postgres Open
Lecture Notes Unit5 chapter17 Stored procedures and functions
Major features postgres 11
 
Procedures and triggers in SQL
prohuddle-utPLSQL v3 - Ultimate unit testing framework for Oracle
PostgreSQL 9.5 Features
Lesson-5-Complex-Queries-Aggregate-Function-Nested-Queries-Triggers.pptx
Oracle trigger
Function Procedure Trigger Partition.pdf
Tony jambu (obscure) tools of the trade for tuning oracle sq ls
Database Automation with MySQL Triggers and Event Schedulers
Changing your huge table's data types in production
Oracle_Audit_APEX IOUG Collaborate 14
Below is the question I need help with. It need to be done in Java. .pdf
Ad

More from Jim Mlodgenski (12)

PDF
Strategic autovacuum
PDF
Top 10 Mistakes When Migrating From Oracle to PostgreSQL
PDF
Oracle postgre sql-mirgration-top-10-mistakes
PDF
Profiling PL/pgSQL
PDF
Debugging Your PL/pgSQL Code
PDF
PostgreSQL Procedural Languages: Tips, Tricks and Gotchas
ODP
Introduction to PostgreSQL
ODP
Postgresql Federation
PPT
Leveraging Hadoop in your PostgreSQL Environment
PDF
Scaling PostreSQL with Stado
ODP
Multi-Master Replication with Slony
ODP
Scaling PostgreSQL With GridSQL
Strategic autovacuum
Top 10 Mistakes When Migrating From Oracle to PostgreSQL
Oracle postgre sql-mirgration-top-10-mistakes
Profiling PL/pgSQL
Debugging Your PL/pgSQL Code
PostgreSQL Procedural Languages: Tips, Tricks and Gotchas
Introduction to PostgreSQL
Postgresql Federation
Leveraging Hadoop in your PostgreSQL Environment
Scaling PostreSQL with Stado
Multi-Master Replication with Slony
Scaling PostgreSQL With GridSQL

Recently uploaded (20)

PPTX
Cloud computing and distributed systems.
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PDF
Encapsulation theory and applications.pdf
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Approach and Philosophy of On baking technology
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
Machine learning based COVID-19 study performance prediction
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
KodekX | Application Modernization Development
PDF
Electronic commerce courselecture one. Pdf
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PDF
Shreyas Phanse Resume: Experienced Backend Engineer | Java • Spring Boot • Ka...
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PPT
Teaching material agriculture food technology
PDF
CIFDAQ's Market Insight: SEC Turns Pro Crypto
PPTX
Big Data Technologies - Introduction.pptx
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
Cloud computing and distributed systems.
Reach Out and Touch Someone: Haptics and Empathic Computing
Encapsulation theory and applications.pdf
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Approach and Philosophy of On baking technology
20250228 LYD VKU AI Blended-Learning.pptx
Machine learning based COVID-19 study performance prediction
Diabetes mellitus diagnosis method based random forest with bat algorithm
KodekX | Application Modernization Development
Electronic commerce courselecture one. Pdf
Understanding_Digital_Forensics_Presentation.pptx
Shreyas Phanse Resume: Experienced Backend Engineer | Java • Spring Boot • Ka...
Network Security Unit 5.pdf for BCA BBA.
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
Teaching material agriculture food technology
CIFDAQ's Market Insight: SEC Turns Pro Crypto
Big Data Technologies - Introduction.pptx
Dropbox Q2 2025 Financial Results & Investor Presentation
Digital-Transformation-Roadmap-for-Companies.pptx

An Introduction To PostgreSQL Triggers

  • 2. Who am I? • Jim Mlodgenski – jimm@openscg.com – @jim_mlodgenski • Director – United States PostgreSQL (www.postgresql.us) • Co-organizer of – Philly PUG (www.phlpug.org) – NYC PUG (www.nycpug.org) • CTO, OpenSCG – www.openscg.com
  • 4. Triggers • Code that gets executed when an event happens in the database – INSERT, UPDATE, DELETE • Event Triggers fire on DDL – CREATE, DROP, ALTER – Still not mature yet ● (limited functionaity)
  • 5. Use Cases • Table Partitioning • Automatically generate derived column values • Enforce complex constraints • Enforce referential integrity across nodes in a distributed database • Provide transparent event logging • Provide auditing • Invalidate cache entries
  • 6. Structure ● Trigger ● Trigger Function CREATE TRIGGER foo_trg BEFORE UPDATE ON foo FOR EACH ROW EXECUTE PROCEDURE foo_update();
  • 7. Trigger Events ● Insert ● Update ● Delete ● Truncate
  • 8. Trigger Timing ● Before – The trigger is fired before the change is made to the table ● After – The trigger is fired after the change is made to the table
  • 9. Trigger Frequency ● For Each Row – The trigger is fired once each time a row is affected ● For Each Statement – The trigger is fired once each time a statement is executed
  • 10. Trigger Overhead CREATE UNLOGGED TABLE trigger_test ( key serial primary key, value varchar, insert_ts timestamp, update_ts timestamp ); INSERT INTO trigger_test (value) VALUES (‘hello’); set keys :scale setrandom key 1 :keys UPDATE trigger_test SET value = 'HELLO' WHERE key = :key;
  • 11. Trigger Overhead pgbench -n -t 100000 -f INSERTS.pgbench postgres pgbench -n -s 100000 -t 10000 -f UPDATES.pgbench postgres Inserts: 4510 tps Updates: 4349 tps
  • 12. Trigger Overhead CREATE FUNCTION empty_trigger() RETURNS trigger AS $$ BEGIN RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER empty_trigger BEFORE INSERT OR UPDATE ON trigger_test FOR EACH ROW EXECUTE PROCEDURE empty_trigger();
  • 13. Trigger Overhead pgbench -n -t 100000 -f INSERTS.pgbench postgres pgbench -n -s 100000 -t 10000 -f UPDATES.pgbench postgres Inserts: 4296 tps (4.8% overhead) Updates: 3988 tps (8.3% overhead)
  • 14. Trigger Arguments ● NEW – Variable holding the new row for INSERT/UPDATE operations in row-level triggers ● OLD – Variable holding the old row for UPDATE/DELETE operations in row-level triggers
  • 15. NEW vs OLD CREATE TABLE audit ( event_time timestamp NOT NULL, user_name varchar NOT NULL, old_row json, new_row json );
  • 16. NEW vs OLD (cont.) CREATE OR REPLACE FUNCTION audit_trigger() RETURNS TRIGGER AS $$ BEGIN INSERT INTO audit VALUES (CURRENT_TIMESTAMP, CURRENT_USER, row_to_json(OLD), row_to_json(NEW)); RETURN NEW; END; $$ LANGUAGE plpgsql;
  • 17. NEW vs OLD (cont.) CREATE TRIGGER audit_trigger AFTER UPDATE ON pgbench_branches FOR EACH ROW EXECUTE PROCEDURE audit_trigger();
  • 18. Trigger Arguments (cont.) ● TG_OP – A string of INSERT, UPDATE, DELETE, or TRUNCATE telling for which operation the trigger was fired ● TG_NAME – Variable that contains the name of the trigger actually fired ● TG_WHEN – A string of BEFORE, AFTER, or INSTEAD OF, depending on the trigger's definition ● TG_LEVEL – A string of either ROW or STATEMENT depending on the trigger's definition
  • 19. TG_OP CREATE TABLE audit ( event_time timestamp NOT NULL, user_name varchar NOT NULL, operation varchar NOT NULL, old_row json, new_row json );
  • 20. TG_OP (cont.) CREATE OR REPLACE FUNCTION audit_trigger() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'DELETE') THEN INSERT INTO audit VALUES (CURRENT_TIMESTAMP, CURRENT_USER,TG_OP, row_to_json(OLD), null); RETURN OLD; ELSIF (TG_OP = 'UPDATE') THEN INSERT INTO audit VALUES (CURRENT_TIMESTAMP, CURRENT_USER,TG_OP, row_to_json(OLD), row_to_json(NEW)); RETURN NEW; ELSIF (TG_OP = 'INSERT') THEN INSERT INTO audit VALUES (CURRENT_TIMESTAMP, CURRENT_USER,TG_OP, null, row_to_json(NEW)); RETURN NEW; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql;
  • 21. TG_OP (cont.) CREATE TRIGGER audit_trigger AFTER UPDATE OR INSERT OR DELETE ON pgbench_branches FOR EACH ROW EXECUTE PROCEDURE audit_trigger();
  • 22. Trigger Arguments (cont.) ● TG_TABLE_NAME – The name of the table that caused the trigger invocation. ● TG_RELNAME – The name of the table that caused the trigger invocation ● TG_RELID – The object ID of the table that caused the trigger invocation ● TG_TABLE_SCHEMA – The name of the schema of the table that caused the trigger invocation
  • 23. TG_TABLE_NAME CREATE TABLE audit ( event_time timestamp NOT NULL, user_name varchar NOT NULL, operation varchar NOT NULL, table_name varchar NOT NULL, old_row json, new_row json );
  • 24. TG_TABLE_NAME (cont.) CREATE OR REPLACE FUNCTION audit_trigger() RETURNS TRIGGER AS $$ BEGIN IF (TG_OP = 'DELETE') THEN INSERT INTO audit VALUES (CURRENT_TIMESTAMP, CURRENT_USER,TG_OP, TG_TABLE_NAME, row_to_json(OLD), null); RETURN OLD; ELSIF (TG_OP = 'UPDATE') THEN INSERT INTO audit VALUES (CURRENT_TIMESTAMP, CURRENT_USER,TG_OP, TG_TABLE_NAME, row_to_json(OLD), row_to_json(NEW)); RETURN NEW; ELSIF (TG_OP = 'INSERT') THEN INSERT INTO audit VALUES (CURRENT_TIMESTAMP, CURRENT_USER,TG_OP, TG_TABLE_NAME, null, row_to_json(NEW)); RETURN NEW; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql;
  • 25. TG_TABLE_NAME (cont.) CREATE TRIGGER audit_trigger AFTER UPDATE OR INSERT OR DELETE ON pgbench_branches FOR EACH ROW EXECUTE PROCEDURE audit_trigger(); CREATE TRIGGER audit_trigger AFTER UPDATE OR INSERT OR DELETE ON pgbench_tellers FOR EACH ROW EXECUTE PROCEDURE audit_trigger();
  • 26. Trigger Arguments (cont.) ● TG_NARGS – The number of arguments given to the trigger procedure in the CREATE TRIGGER statement ● TG_ARGV[] – The arguments from the CREATE TRIGGER statement
  • 27. Trigger Use Cases ● Table Partitioning – Splitting what is logically one large table into smaller physical pieces – Used to: ● Increase performance ● Archive data ● Storage tiering
  • 28. Table Partitioning CREATE TABLE audit_2014 ( CHECK ( event_time >= DATE '2014-01-01' AND event_time < DATE '2015-01-01') ) INHERITS (audit); CREATE TABLE audit_2015 ( CHECK ( event_time >= DATE '2015-01-01' AND event_time < DATE '2016-01-01') ) INHERITS (audit);
  • 29. Table Partitioning (cont.) CREATE OR REPLACE FUNCTION partition_audit_trigger() RETURNS TRIGGER AS $$ BEGIN EXECUTE 'INSERT INTO audit_' || to_char(NEW.event_time, 'YYYY') || ' VALUES ($1, $2, $3, $4, $5, $6)' USING NEW.event_time, NEW.user_name, NEW.operation, NEW.table_name, NEW.old_row, NEW.new_row; RETURN NULL; END; $$ LANGUAGE plpgsql;
  • 30. Table Partitioning (cont.) CREATE TRIGGER partition_audit_trigger BEFORE INSERT ON audit FOR EACH ROW EXECUTE PROCEDURE partition_audit_trigger();
  • 31. Partitioning Performance CREATE OR REPLACE FUNCTION partition_audit_trigger() RETURNS TRIGGER AS $$ BEGIN IF ( NEW.event_time >= DATE '2015-01-01' AND NEW.event_time < DATE '2016-01-01' ) THEN INSERT INTO audit_2015 VALUES (NEW.*); ELSIF ( NEW.event_time >= DATE '2014-01-01' AND NEW.event_time < DATE '2015-01-01' ) THEN INSERT INTO audit_2014 VALUES (NEW.*); ELSE RAISE EXCEPTION 'Date out of range. Fix partition_audit_trigger() function!'; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql;
  • 32. Moving Partitions CREATE TRIGGER move_partition_audit_trigger BEFORE UPDATE ON audit_2014 FOR EACH ROW EXECUTE PROCEDURE move_partition_audit_trigger('2014-01-01', '2015-01-01'); CREATE TRIGGER move_partition_audit_trigger BEFORE UPDATE ON audit_2015 FOR EACH ROW EXECUTE PROCEDURE move_partition_audit_trigger('2015-01-01', '2016-01-01');
  • 33. Moving Partitions (cont.) CREATE OR REPLACE FUNCTION move_partition_audit_trigger() RETURNS TRIGGER AS $$ DECLARE start_date DATE; end_date DATE; BEGIN start_date := TG_ARGV[0]; end_date := TG_ARGV[1]; IF ( NEW.event_time IS DISTINCT FROM OLD.event_time ) THEN IF (NEW.event_time < start_date OR NEW.event_time >= end_date) THEN EXECUTE 'DELETE FROM ' || TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME || ' WHERE ctid = $1' USING OLD.ctid; INSERT INTO audit VALUES (NEW.*); RETURN null; END IF; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql;
  • 34. Moving Partitions (cont.) CREATE TRIGGER move_partition_audit_trigger BEFORE UPDATE ON audit_2014 FOR EACH ROW WHEN (NEW.event_time IS DISTINCT FROM OLD.event_time) EXECUTE PROCEDURE move_partition_audit_trigger('2014-01-01', '2015-01-01'); CREATE TRIGGER move_partition_audit_trigger BEFORE UPDATE ON audit_2015 FOR EACH ROW WHEN (NEW.event_time IS DISTINCT FROM OLD.event_time) EXECUTE PROCEDURE move_partition_audit_trigger('2015-01-01', '2016-01-01');
  • 35. Trigger Use Cases ● Calculate columns – Calculate complex values – Extract values from complex structures – Used to: ● Increase performance ● Simplify queries
  • 36. Extract JSON $ head -n 5 zips.json { "_id" : "01001", "city" : "AGAWAM", "loc" : [ -72.622739, 42.070206 ], "pop" : 15338, "state" : "MA" } { "_id" : "01002", "city" : "CUSHMAN", "loc" : [ -72.51564999999999, 42.377017 ], "pop" : 36963, "state" : "MA" } { "_id" : "01005", "city" : "BARRE", "loc" : [ -72.10835400000001, 42.409698 ], "pop" : 4546, "state" : "MA" } { "_id" : "01007", "city" : "BELCHERTOWN", "loc" : [ -72.41095300000001, 42.275103 ], "pop" : 10579, "state" : "MA" } { "_id" : "01008", "city" : "BLANDFORD", "loc" : [ -72.936114, 42.182949 ], "pop" : 1240, "state" : "MA" } CREATE TABLE zips ( zip_code varchar PRIMARY KEY, state varchar, data json );
  • 37. Extract JSON CREATE OR REPLACE FUNCTION extract_data_trigger() RETURNS TRIGGER AS $$ BEGIN NEW.zip_code := NEW.data->>'_id'; NEW.state := NEW.data->>'state'; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER extract_data_trigger BEFORE UPDATE OR INSERT ON zips FOR EACH ROW EXECUTE PROCEDURE extract_data_trigger();
  • 38. Trigger Use Cases ● Cache invalidation – Remove stale entries from a cache – The database tracks all data so is the single source of truth – Used to: ● Simplify cache management ● Remove application complexity Note: Foreign Data Wrappers simplify this process significantly
  • 39. Cache Invalidation CREATE TABLE users ( id serial PRIMARY KEY, first_name varchar, last_name varchar, email_address varchar NOT NULL, password_md5 varchar NOT NULL ); CREATE OR REPLACE FUNCTION remove_cache_trigger() RETURNS TRIGGER AS $$ BEGIN DELETE from myredis_cache WHERE key = OLD.id::varchar; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER remove_cache_trigger AFTER UPDATE OR DELETE ON users FOR EACH ROW EXECUTE PROCEDURE remove_cache_trigger();
  • 40. Cache Invalidation - Async CREATE OR REPLACE FUNCTION remove_cache_trigger() RETURNS TRIGGER AS $$ BEGIN PERFORM pg_notify(TG_TABLE_NAME, OLD.id::varchar); RETURN NEW; END; $$ LANGUAGE plpgsql;
  • 41. Things to Remember ● Triggers are part of the parent transaction – The trigger fails, the main transaction fails – If the trigger takes a long time, the whole transaction timing is affected ● Triggers can be difficult to debug – Especially cascaded triggers