views:

947

answers:

4

For the sake of the example, consider a table

create table foo (
  contents text NOT NULL,
  is_active boolean NOT NULL DEFAULT false,
  dt_active date
)

I insert a record:

insert into foo (contents) values ('bar')

So far, so good. Later on, I now want to 'activate' the record:

update foo set is_active = true

What I would like to do when is_active is changed from false to true, is for dt_active is set to now(). For bonus points it would be nice if is_active is changed from true to false, dt_active is set to null, but I can live without that.

I'd really like to push this housekeeping into the database, it would make the client code much cleaner (since many tables (and even column tuples within tables) could benefit from this technique).

I'm stumped as to how to pull out the current record in the database in the trigger (I'm using plpgsql), in order to compare the "then" with the "now". Pointers to code examples or snippets greatly appreciated.

+2  A: 

Have you tried the manual?

Every trigger function has some automatic variables. In case of update triggers the old rows values are accessable via OLD and the changed values via NEW

You can simply check the column value of OLD by OLD.is_active. The same way you can change the values to be put in the database like NEW.dt_active := now();

Hope this hels.

Frank Bollack
+4  A: 

Inside your plpgsql trigger procedure you have access to some special record-type variables called NEW and OLD that are created for you.

In an UPDATE or INSERT trigger , NEW will represent the record of the new database row.

In an UPDATE or DELETE trigger , OLD will represent the value of the original database row.

In other statement contexts, these record variables will be NULL.

Therefore, it seems like you need to create an INSERT OR UPDATE trigger that looks at the values of OLD.is_active and NEW.is_active.

Here's the documentation page - http://www.postgresql.org/docs/8.1/interactive/plpgsql-trigger.html This page contains sample code for plpgsql that uses NEW and OLD

cms
+1  A: 
CREATE FUNCTION actrigger() RETURNS TRIGGER AS $$ BEGIN
    IF TG_OP='UPDATE' THEN
        IF NEW.is_active THEN
            IF NOT OLD.is_active THEN
                NEW.dt_active := current_date;
            END IF;
        ELSIF NEW.dt_active IS NOT NULL
            NEW.dt_active := NULL;
        END IF;
    END IF;
    RETURN NEW;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER foobefore BEFORE UPDATE ON foo FORR EACH ROW
    EXECUTE PROCEDURE actrigger();

And you can take care of INSERT too which would be pretty similar except for it shouldn't refer to OLD.

Michael Krelin - hacker
What is the point of checking TG_OP when your trigger is explicitly set to be "BEFORE UPDATE"?
depesz
And the ELSE - it will set dt_Active to null on every update of row when is_active = false, not only when it is changed from true to false.
depesz
depesz, I thought about extending it to `insert`, but then decided to leave it as an exercise for reader ;-) As for setting dt_active, I thought about it. It should be already NULL if it's not active. Does it mean it will do actual write because of this assignment? It certainly will if you do `UPDATE .. SET dt_active=NULL`, but this assignment looked so harmless to me ;-) Can you confirm it will be an extra write?
Michael Krelin - hacker
write will happen anyway, but we don't know the application fully - perhaps dt_active was to be changed after setting is_active to false?
depesz
Oh right, write will happen anyway if we're in trigger, so it's irrelevant. Perhaps even more cpu-expensive due to extra check. As for changing dt_active at whim, we indeed don't know the application, but I percieved it was an idea to keep it in proper sync with active state.
Michael Krelin - hacker
+1  A: 
CREATE OR REPLACE FUNCTION trg_update_foo() RETURNS TRIGGER AS
$BODY$
BEGIN
    IF OLD.is_active = false AND NEW.is_active = true THEN
        NEW.dt_active := now();
    ELSIF OLD.is_active = true AND NEW.is_active = false THEN
        NEW.dt_active := NULL;
    END IF;
    RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_update_foo BEFORE UPDATE ON foo FOR EACH ROW EXECUTE PROCEDURE trg_update_foo();

Should work. For more information, check pl/PgSQL Triggers manual, and for extra points - whole plPgSQL docs.

depesz
with `CREATE TRIGGER trg_update_foo_t BEFORE UPDATE ON foo FOR EACH ROW EXECUTE PROCEDURE trg_update_foo();` and we're in business.
dland