Skip to content

Instantly share code, notes, and snippets.

@dominicfraser
Last active May 25, 2017 08:21
Show Gist options
  • Save dominicfraser/929579c1274cbdb0b323761bef759560 to your computer and use it in GitHub Desktop.
Save dominicfraser/929579c1274cbdb0b323761bef759560 to your computer and use it in GitHub Desktop.
Creating User Defined Functions and Triggers in PostgreSQL

UDF and Triggers in PostgreSQL

be aware that MySQL, SQL-Server, and SQLite all use different syntax, this is ONLY for PostgreSQL.

Why would you want to create a UDF that links to a Trigger? A trigger allows something to happen automatically, meaning once you have set it up you don't have to think about it again. The following example allows uses a 'games' table that takes p1_id, p2_id, p1_score, and p2_score. It has a winner_id column. This column could be populated via outside logic, but why should it be? It would be simpler, and avoid accidental error if the structure of the outside logic changed, if it was populated using an internal function, that happens whenever a new row is inserted.

The top of the .sql file looks like this:

DROP TABLE IF EXISTS games CASCADE;
DROP FUNCTION IF EXISTS calculate_winner();
DROP TABLE IF EXISTS players CASCADE;

CREATE TABLE players(
  id SERIAL2 PRIMARY KEY,
  p_name VARCHAR(255),
);

The games table snippet looks like this:

CREATE TABLE games(
  id SERIAL2 PRIMARY KEY,
  p1_id INT4 REFERENCES players(id) ON DELETE CASCADE,
  p2_id INT4 REFERENCES players(id) ON DELETE CASCADE,
  p1_score INT4,
  p2_score INT4,
  winner_id INT4,
);

The function like this:

CREATE OR REPLACE FUNCTION calculate_winner()
RETURNS TRIGGER AS $d$
BEGIN

IF NEW.p1_score > NEW.p2_score THEN
   NEW.winner_id = NEW.p1_id;
ELSIF NEW.p1_score < NEW.p2_score THEN
  NEW.winner_id = NEW.p2_id;
ELSE
 NEW.winner_id = 0;
END IF;

RETURN NEW;
END;
$d$ LANGUAGE plpgsql;

Notice that it returns a TRIGGER, not an INT4.

The trigger looks like this:

CREATE TRIGGER determine_winner 
BEFORE INSERT ON games
FOR EACH ROW 
EXECUTE PROCEDURE calculate_winner();

Notice that it happens BEFORE not AFTER insert.
The function is able to reference the values of the inserted row via the NEW.column syntax.

So what if you wanted to reference another table in your trigger function? Say the players table now looks like this:

CREATE TABLE players(
  id SERIAL2 PRIMARY KEY,
  p_name VARCHAR(255),
  primary_org_id INT4 REFERENCES organisations(id) ON DELETE CASCADE
);

And the games table like this:

CREATE TABLE games(
  id SERIAL2 PRIMARY KEY,
  p1_id INT4 REFERENCES players(id) ON DELETE CASCADE,
  p2_id INT4 REFERENCES players(id) ON DELETE CASCADE,
  p1_score INT4,
  p2_score INT4,
  winner_id INT4,
  p1_org_id INT4 REFERENCES organisations(id) ON DELETE CASCADE,
  p2_org_id INT4 REFERENCES organisations(id) ON DELETE CASCADE
);

A trigger can be created like this:

CREATE TRIGGER add_primaries
BEFORE INSERT ON games
FOR EACH ROW
EXECUTE PROCEDURE find_primaries();

That uses this function:

CREATE OR REPLACE FUNCTION find_primaries()
RETURNS TRIGGER AS $d$
DECLARE 
p1_found_org_id INT4 := (
  SELECT primary_org_id 
  FROM players 
  WHERE players.id = NEW.p1_id
);
p2_found_org_id INT4 := (
  SELECT primary_org_id 
  FROM players 
  WHERE players.id = NEW.p2_id
);

BEGIN
NEW.p1_org_id = p1_found_org_id;
NEW.p2_org_id = p2_found_org_id;

RETURN NEW;
END;
$d$ LANGUAGE plpgsql;

This will reference the players table, find the needed linked data, and assign it to the variable in the new game row.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment