Sling Academy
Home/PostgreSQL/How to audit data with triggers in PostgreSQL

How to audit data with triggers in PostgreSQL

Last updated: January 06, 2024

Introduction

Auditing data changes can be key for maintaining data integrity and history in database systems. This article walks through the use of triggers in PostgreSQL to create an effective auditing system, providing an extra layer of security and traceability for your data transactions.

What is a Trigger?

In PostgreSQL, a trigger is a function invoked automatically whenever a specified event occurs. Events can be an INSERT, UPDATE, DELETE, or even TRUNCATE action performed on a table. Triggers are critical tools for enforcing complex business rules, maintaining referential data integrity, logging historical changes of data, or auditing user actions.

Setting up the Environment

Before diving into the implementation, we need to set up a sample database and table in PostgreSQL. We’ll be using the psql command-line tool for these operations. Open your terminal and follow these instructions:

-- Connect to your PostgreSQL server
psql -U username -d databasename

-- Create a new table for demonstration
CREATE TABLE employee (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    salary DECIMAL(10, 2) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ
);

Basic Trigger Creation

Our first example involves creating a simple trigger to log every insert operation performed on the employee table. We need to create an AUDIT table to store audit data and a function that the trigger will execute.

-- Create an audit table
CREATE TABLE audit (
    change_id SERIAL PRIMARY KEY,
    table_name VARCHAR(100) NOT NULL,
    operation VARCHAR(10) NOT NULL,
    changed_by TEXT NOT NULL,
    changed_on TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Create an audit function
CREATE OR REPLACE FUNCTION audit_insert()
RETURNS TRIGGER AS $
BEGIN
    INSERT INTO audit(table_name, operation, changed_by, changed_on)
    VALUES (TG_TABLE_NAME, 'INSERT', current_user, now());
    RETURN NEW;
END;
$ LANGUAGE plpgsql;

-- Create a trigger for the employee table
CREATE TRIGGER employee_insert_audit
AFTER INSERT ON employee
FOR EACH ROW EXECUTE FUNCTION audit_insert();

Advanced Trigger for Detailed Auditing

To create a more detailed audit trail, we will log the old and new values for each update operation. We modify our audit table and create a new function:

-- Modify the audit table to store row changes
ALTER TABLE audit ADD COLUMN old_values TEXT;
ALTER TABLE audit ADD COLUMN new_values TEXT;

-- Create a more complex audit function
CREATE OR REPLACE FUNCTION audit_update()
RETURNS TRIGGER AS $
DECLARE
    operation_type TEXT;
    user_name TEXT := current_user;
BEGIN
    IF TG_OP = 'UPDATE' THEN
        operation_type := 'UPDATE';
        INSERT INTO audit(table_name, operation, old_values, new_values, changed_by, changed_on)
        VALUES (TG_TABLE_NAME, operation_type, row_to_json(OLD), row_to_json(NEW), user_name, now());
        RETURN NEW;
    ELSIF TG_OP = 'INSERT' THEN
        operation_type := 'INSERT';
        INSERT INTO audit(table_name, operation, new_values, changed_by, changed_on)
        VALUES (TG_TABLE_NAME, operation_type, row_to_json(NEW), user_name, now());
        RETURN NEW;
    ELSIF TG_OP = 'DELETE' THEN
        operation_type := 'DELETE';
        INSERT INTO audit(table_name, operation, old_values, changed_by, changed_on)
        VALUES (TG_TABLE_NAME, operation_type, row_to_json(OLD), user_name, now());
        RETURN OLD;
    END IF;
END;
$ LANGUAGE plpgsql;

-- Create a trigger for the employee table updates
CREATE TRIGGER employee_update_audit
AFTER UPDATE ON employee
FOR EACH ROW EXECUTE FUNCTION audit_update();

Handling Complex Trigger Logic

When the logic gets more complex, it’s beneficial to refine the triggers accordingly. We might want to check for specific conditions before logging or performing more complex operations based on the change:

-- Create a trigger that only logs updates where the salary is changed
CREATE OR REPLACE FUNCTION audit_salary_update()
RETURNS TRIGGER AS $
BEGIN
    IF OLD.salary <> NEW.salary THEN
        INSERT INTO audit(table_name, operation, old_values, new_values, changed_by, changed_on)
        VALUES (TG_TABLE_NAME, 'UPDATE', hstore(OLD), hstore(NEW), current_user, now());
    END IF;
    RETURN NEW;
END;
$ LANGUAGE plpgsql;

CREATE TRIGGER employee_salary_update_audit
AFTER UPDATE OF salary ON employee
FOR EACH ROW WHEN (OLD.salary <> NEW.salary)
EXECUTE FUNCTION audit_salary_update();

Conclusion

In conclusion, auditing data with triggers in PostgreSQL is a robust method for tracking changes to your database tables. While triggers offer powerful capabilities for auditing, bear in mind that they can add overhead to database operations and should be carefully designed to minimize performance impact. The strategies discussed in this article should equip you with the knowledge to implement an effective audit system in your PostgreSQL database.

Next Article: How to view trigger variables in PostgreSQL

Previous Article: How to create a trigger in PostgreSQL

Series: PostgreSQL Tutorials: From Basic to Advanced

PostgreSQL

You May Also Like

  • PostgreSQL with TimescaleDB: Querying Time-Series Data with SQL
  • PostgreSQL Full-Text Search with Boolean Operators
  • Filtering Stop Words in PostgreSQL Full-Text Search
  • PostgreSQL command-line cheat sheet
  • How to Perform Efficient Rolling Aggregations with TimescaleDB
  • PostgreSQL with TimescaleDB: Migrating from Traditional Relational Models
  • Best Practices for Maintaining PostgreSQL and TimescaleDB Databases
  • PostgreSQL with TimescaleDB: Building a High-Performance Analytics Engine
  • Integrating PostgreSQL and TimescaleDB with Machine Learning Models
  • PostgreSQL with TimescaleDB: Implementing Temporal Data Analysis
  • Combining PostgreSQL, TimescaleDB, and Airflow for Data Workflows
  • PostgreSQL with TimescaleDB: Visualizing Real-Time Data with Superset
  • Using PostgreSQL with TimescaleDB for Energy Consumption Analysis
  • PostgreSQL with TimescaleDB: How to Query Massive Datasets Efficiently
  • Best Practices for Writing Time-Series Queries in PostgreSQL with TimescaleDB
  • PostgreSQL with TimescaleDB: Implementing Batch Data Processing
  • Using PostgreSQL with TimescaleDB for Network Traffic Analysis
  • PostgreSQL with TimescaleDB: Troubleshooting Common Performance Issues
  • Building an IoT Data Pipeline with PostgreSQL and TimescaleDB