Database triggers are special types of objects. They look like database procedures and functions but they are only executed in response to a certain event. The procedural code in the body is "triggered" by the specified DML or DDL operations on the database. The permitted operations are INSERT
, UPDATE
, DELETE
(DML) and TRUNCATE
(DDL) operations and the execution of the procedural code attached to the trigger could be BEFORE
, AFTER
or INSTEAD OF
the operations, but exactly once per SQL statement or per modified row.
This blog post uses Postgres and PGSQL as the underlying technology
Therefore, there are multiple ways to classify triggers. These are
- Based on WHEN you want the trigger function to be executed: These are
BEFORE
,AFTER
orINSTEAD OF
triggers. - Per-row or Per-statement triggers: The keywords that represent per-row triggers are
FOR EACH ROW
while the per-statement keyword isFOR EACH STATEMENT
.
Note:
FOR EACH STATEMENT
is the default, if it is not declared.
A combination of both classes is required when building the trigger. That is, the trigger must have the WHEN clause (before, after or instead of) and must also either be per row or per statement.
The comprehensive documentation on how to combine both classifications can be found here.
Before we dive into the syntax and how to create a trigger in Postgres, let's consider the use cases for database triggers, its advantages and disadvantages
Use cases for database triggers
- Auditing changes on DML events
- Enforcing data and referential integrity that cannot be easily defined using constraints.
- Enhancing security. For instance, preventing DML operations on a table after regular business hours
- Gathering Statistics
Advantages
- Triggers are efficient when used appropriately. For instance, to carry out an automated action, eliminating the need for manual intervention.
- The functions attached to the trigger can be called in other code or attached to other objects. Therefore, potentially saving development time. A good example would be a trigger function to log all insertions on a table. This sort of function could be table-agnostic and highly reusable.
- Triggers can offer a high level of control.
- Just like other database objects, triggers can improve overall performance by moving workloads from the application layer to the database layer.
Disadvantages
- Triggers can be complex to write and troubleshoot.
- There is a performance overhead by introducing an additional workload when triggered by the DML operation.
- They are programmatic and easy to alter or disable. Therefore, they can not be fully relied on as security mechanisms and must be used with caution in this case.
- When used as constraints, they are more error-prone because they have to be developed.
Syntax
In Postgres, a trigger is made up of 2 separately defined parts. The function/procedure (basically the block of code that will be executed) and the trigger definition itself. We will get into the syntax shortly using code snippets, but before that, I will describe both the trigger function and the trigger specification.
The Trigger Function
The function must be defined before the trigger definition. it does not differ from the typical database function. However, the return type in the function specification must be TRIGGER
. Below is an abridged version of a function specification
CREATE [ OR REPLACE ] FUNCTION
name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] )
[ RETURNS rettype ]
{ LANGUAGE lang_name
| sql_body
}
Where the rettype
is replaced with the keyword TRIGGER
The Trigger Specification
To define the trigger, we use the CREATE TRIGGER
keywords alongside other options. It is also here that the function created earlier is executed.
Trigger Syntax
CREATE [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] }
ON table_name
[ FROM referenced_table_name ]
[ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
[ FOR [ EACH ] { ROW | STATEMENT } ]
[ WHEN ( condition ) ]
EXECUTE PROCEDURE function_name ( arguments )
where the event can be one of:
INSERT
UPDATE [ OF column_name [, ... ] ]
DELETE
TRUNCATE
Example: Audit use case
Let's create a trigger for one of my favourite use cases for triggers, which is auditing operations on a certain table.
Here, we will act as data professionals, setting up a trigger to audit changes to employee details
First, we create 2 tables, one to house employee details employee
and another to capture relevant audit details employee_audit
. All tables will be under the hr
schema.
-- Create demo tables
CREATE table IF NOT EXISTS hr.employees
(
employee_id INTEGER CONSTRAINT emp_emp_id_pk PRIMARY KEY,
first_name VARCHAR(20),
last_name VARCHAR(25) CONSTRAINT emp_last_name_nn NOT NULL,
email VARCHAR(25) CONSTRAINT emp_email_nn NOT NULL, CONSTRAINT emp_email_uk UNIQUE (email),
phone_number VARCHAR(20),
hire_date DATE CONSTRAINT emp_hire_date_nn NOT NULL,
job_id VARCHAR(10) CONSTRAINT emp_job_nn NOT NULL,
salary NUMERIC(8,2) CONSTRAINT emp_salary_min CHECK (salary > 0),
commission_pct NUMERIC(4,2),
manager_id INTEGER CONSTRAINT emp_manager_fk REFERENCES employees(employee_id),
department_id INTEGER
);
CREATE table IF NOT EXISTS hr.employees_audit
(
employee_id integer,
first_name VARCHAR(20),
last_name VARCHAR(25),
email VARCHAR(25),
phone_number VARCHAR(20),
hire_date DATE,
job_id VARCHAR(10),
salary NUMERIC(8,2),
commission_pct NUMERIC(4,2),
manager_id INTEGER,
department_id integer,
date_changed DATE constraint emp_aud_date_change not null,
client_ip VARCHAR(25),
client_host_name VARCHAR(25),
client_db_username VARCHAR(30),
client_application varchar(80)
);
Remember! To create a trigger in Postgres, we must create the function that is going to be executed by the trigger. If you need a refresher on writing database functions, please visit the functions post in this series. Here we go!
CREATE OR REPLACE FUNCTION hr.log_employee_changes_function()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
declare
date_of_change date := current_date;
client_ip varchar(25);
hostname varchar(25);
client_db_user varchar(25);
client_application varchar(80);
begin
IF new <> old THEN
select pg_catalog.inet_client_addr()
into client_ip;
select client_hostname
into hostname
from pg_catalog.pg_stat_activity
where pid = pg_backend_pid();
select usename
into client_db_user
from pg_catalog.pg_stat_activity
where pid = pg_backend_pid();
select application_name
into client_application
from pg_catalog.pg_stat_activity
where pid = pg_backend_pid();
insert
into
hr.employees_audit
values(
old.employee_id,
old.first_name,
old.last_name,
old.email,
old.phone_number,
old.hire_date,
old.job_id,
old.salary,
old.commission_pct,
old.manager_id,
old.department_id,
date_of_change,
client_ip,
hostname,
client_db_user,
client_application
) ;
END IF;
RETURN NEW;
END;
$$
The above function returns a TRIGGER
. In its body, it compares the new and old values to see if there is a change. If there is, it collects some additional information specific to that session such as the database user, IP address of the client, as well as the client application name and computes the date of that operation using the CURRENT_DATE
built-in function.
All these are inserted into the employee_audit
table. But when is it inserted? Is it before or after a change to the employee data? The only way we can find out is when we define our trigger using the CREATE TRIGGER
keywords.
CREATE OR REPLACE TRIGGER log_employee_changes_trigger
BEFORE UPDATE
ON hr.employees
FOR EACH ROW
EXECUTE PROCEDURE hr.log_employee_changes_function();
One thing to notice, the trigger name does not carry the HR schema as a prefix when being defined. This is because the trigger inherits the schema of the table and this is specified in the Postgres doc here.
TLDR
Triggers are special objects in a database that are executed when certain events happen. There are several cases when a database trigger could be useful such as enforcing constraints, tracking changes on tables that closely relate to business processes, and more.
Although useful, they have their pros and cons. Some advantages are that they provide reusable code, and can be highly efficient, while on the flip side, they cannot be relied on as a security mechanism and may be complex to troubleshoot.
Overall, we have created a fairly simple database trigger and we can be proud of ourselves for understanding the syntax. Please leave a comment on what you think database triggers can be used for in your organization, project etc.
Top comments (0)