pg.ddx.io  pgsql-sql@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Tomas Vondra <tv@fuzzy.cz>
To: pgsql-sql@postgresql.org
Subject: implementing (something like) UNIQUE constraint using PL/pgSQL
Date: Fri, 26 Jan 2007 10:41:26 +0100
Message-ID: <45B9CCC6.4000109@fuzzy.cz> (raw)

Hello,

in our application we need to implement a constraint that enforces 'at 
most N rows with this value', that is we have a table with 'flag' column 
and for each value there should be at most 10 rows (for example, the 
exact number does not matter).

I'm trying to implement a PL/pgSQL trigger to enforce this constraint, 
but I'm not sure my solution is 'the best one possible'. The first 
solution I came up with is this:

=====================================================================

CREATE OR REPLACE FUNCTION at_most() RETURNS trigger AS $$
DECLARE
     v_cnt INTEGER;
     p_cnt INTEGER;
BEGIN

    IF TG_NARGS >= 1 THEN
       p_cnt := TG_ARGV[0]::integer;
    ELSE
       p_cnt := 1;
    END IF;

    SELECT COUNT(*) INTO v_cnt FROM my_table WHERE flag = NEW.flag;
    IF v_cnt > p_cnt THEN
        RAISE EXCEPTION 'Too many rows with this flag!'
    END IF;

    RETURN NEW;

END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER at_most AFTER INSERT OR UPDATE ON my_table FOR EACH ROW 
EXECUTE PROCEDURE at_most(10);

=====================================================================

But that obviously does not work as two sessions can reach the SELECT 
INTO statement at the same time (or until one of them commits). Thus 
there could be more than 'cnt' rows with the same value.

Then I used a 'FOR UPDATE' lock on a separate 'lock table' that already 
contains all possible values of 'flag' (in reality the trigger tries to 
insert that value and catches the 'duplicate key' exception but that's 
not important here). The trigger is then

=====================================================================

CREATE OR REPLACE FUNCTION at_most() RETURNS trigger AS $$
DECLARE
     v_cnt INTEGER;
     p_cnt INTEGER;
BEGIN

    IF TG_NARGS >= 1 THEN
       p_cnt := TG_ARGV[0]::integer;
    ELSE
       p_cnt := 1;
    END IF;

    PERFORM flag FROM lock_table WHERE flag = NEW.flag FOR UPDATE;

    SELECT COUNT(*) INTO v_cnt FROM my_table WHERE flag = NEW.flag;
    IF v_cnt > p_cnt THEN
        RAISE EXCEPTION 'Too many rows with this flag!';
    END IF;

    RETURN NEW;

END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER at_most AFTER INSERT OR UPDATE ON my_table FOR EACH ROW 
EXECUTE PROCEDURE at_most(10);

=====================================================================

This works (af far as I know), but I'm not sure it's the best possible 
solution - for example I'd  like to remove the lock table. Is there some 
'more elegant' way to do this?

Tomas

PS: Is there some up to date 'best practices' book related to PL/pgSQL?
     All books I've found on Amazon are pretty old (about 5 years) or are
     related to 'beginners' or different areas of development (PHP, etc.)



view thread (3+ messages)  latest in thread

Message-ID: <45B9CCC6.4000109@fuzzy.cz>
Permalink:  ../45B9CCC6.4000109@fuzzy.cz/
Also on:    postgresql.org/message-id/45B9CCC6.4000109@fuzzy.cz

 · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-sql@postgresql.org
  Cc: tv@fuzzy.cz
  Subject: Re: implementing (something like) UNIQUE constraint using PL/pgSQL
  In-Reply-To: <45B9CCC6.4000109@fuzzy.cz>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox