copy rows before updating them to preserve archive in Postgres

Posted by punkish on Stack Overflow See other posts from Stack Overflow or by punkish
Published on 2012-03-30T03:06:15Z Indexed on 2012/03/30 11:29 UTC
Read the original article Hit count: 233

Filed under:

I am experimenting with creating a table that keeps a version of every row. The idea is to be able to query for how the rows were at any point in time even if the query has JOINs. Consider a system where the primary resource is books, that is, books are queried for, and author info comes along for the ride

CREATE TABLE authors (
    author_id INTEGER NOT NULL,
    version INTEGER NOT NULL CHECK (version > 0),
    author_name TEXT,
    is_active BOOLEAN DEFAULT '1',
    modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (author_id, version)
)

INSERT INTO authors (author_id, version, author_name) 
VALUES  (1, 1, 'John'),
        (2, 1, 'Jack'),
        (3, 1, 'Ernest');

I would like to be able to update the above like so

UPDATE authors SET author_name = 'Jack K' WHERE author_id = 1;

and end up with

2, 1, Jack, t, 2012-03-29 21:35:00
2, 2, Jack K, t, 2012-03-29 21:37:40

which I can then query with

SELECT author_name, modified_on 
FROM authors
WHERE 
author_id = 2 AND 
modified_on < '2012-03-29 21:37:00' 
ORDER BY version DESC 
LIMIT 1;

to get

2, 1, Jack, t, 2012-03-29 21:35:00

Something like the following doesn't really work

CREATE OR REPLACE FUNCTION archive_authors() RETURNS TRIGGER AS $archive_author$
    BEGIN
        IF (TG_OP = 'UPDATE') THEN

            -- The following fails because author_id,version PK already exists
            INSERT INTO authors (author_id, version, author_name)
            VALUES (OLD.author_id, OLD.version, OLD.author_name);

            UPDATE authors 
            SET version = OLD.version + 1
            WHERE 
                author_id = OLD.author_id AND 
                version = OLD.version;
            RETURN NEW;
        END IF;
        RETURN NULL; -- result is ignored since this is an AFTER trigger
    END;
$archive_author$ LANGUAGE plpgsql;

CREATE TRIGGER archive_author
AFTER UPDATE OR DELETE ON authors
    FOR EACH ROW EXECUTE PROCEDURE archive_authors();

How can I achieve the above? Or, is there a better way to accomplish this? Ideally, I would prefer to not create a shadow table to store the archived rows.

© Stack Overflow or respective owner

Related posts about postgresql