Question

Can a Check Constraint (or some other technique) be used to prevent a value from being set that contradicts its prior value when its record is updated.

One example would be a NULL timestamp indicating something happened, like "file_exported". Once a file has been exported and has a non-NULL value, it should never be set to NULL again.

Another example would be a hit counter, where an integer is only permitted to increase, but can never decrease.

If it helps I'm using postgresql, but I'd like to see solutions that fit any SQL implementation

Was it helpful?

Solution 3

One example would be a NULL timestamp indicating something happened, like "file_exported". Once a file has been exported and has a non-NULL value, it should never be set to NULL again.

Another example would be a hit counter, where an integer is only permitted to increase, but can never decrease.

In both of these cases, I simply wouldn't record these changes as attributes on the annotated table; the 'exported' or 'hit count' is a distinct idea, representing related but orthogonal real world notions from the objects they relate to:

So they would simply be different relations. Since We only want "file_exported" to occur once:

CREATE TABLE thing_file_exported(
    thing_id INTEGER PRIMARY KEY REFERENCES(thing.id),
    file_name VARCHAR NOT NULL
)

The hit counter is similarly a different table:

CREATE TABLE thing_hits(
    thing_id INTEGER NOT NULL REFERENCES(thing.id),
    hit_date TIMESTAMP NOT NULL,
    PRIMARY KEY (thing_id, hit_date)
)

And you might query with

SELECT thing.col1, thing.col2, tfe.file_name, count(th.thing_id)
FROM thing 
LEFT OUTER JOIN thing_file_exported tfe
    ON (thing.id = tfe.thing_id)
LEFT OUTER JOIN thing_hits th
    ON (thing.id = th.thing_id)
GROUP BY thing.col1, thing.col2, tfe.file_name

OTHER TIPS

Use a trigger. This is a perfect job for a simple PL/PgSQL ON UPDATE ... FOR EACH ROW trigger, which can see both the NEW and OLD values.

See trigger procedures.

lfLoop has the best approach to the question. But to continue Craig Ringer's approach using triggers, here is an example. Essentially, you are setting the value of the column back to the original (old) value before you update.

CREATE OR REPLACE FUNCTION example_trigger()
  RETURNS trigger AS
$BODY$
BEGIN
     new.valuenottochange := old.valuenottochange;
     new.valuenottochange2 := old.valuenottochange2;
     RETURN new;
END
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;



DROP TRIGGER IF EXISTS trigger_name ON tablename;
  CREATE TRIGGER trigger_name BEFORE UPDATE ON tablename
    FOR EACH ROW EXECUTE PROCEDURE example_trigger();

Stored procedures and functions in PostgreSQL have access to both old and new values, and that code can access arbitrary tables and columns. It's not hard to build simple (crude?) finite state machines in stored procedures. You can even build table-driven state machines that way.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top