You say you don’t want a “separate revision table” without voting for the FractalizeR solution, because it is. Well, here is a “one table solution” ... But please simplify / summarize your question in order to better answer and better use this page for all visitors. I think your problem is with version control in SQL tables.
The solution is for "ISO 2008 SQL", then I think it is also for Microsoft SQL-Server. I tested it on PostgreSQL 9.1.
In this problem, we can use SQL View to "emulate" the original table, and the "version of the table" as a new one, with a large number of attributes: * New moment attribute for sorting (organizing) changes and for registering time; * New cmd attribute for "traceability" (optional).
Suppose your original (and regular) table is t . For version control, you must add new attributes, but other programmers do not need to see these new attributes ... The solution is to rename table t to t_hist and offer other SQL programmers VIEW t (as a query through t_hist ).
t is a VIEW for displaying a regular table: only "current tuples". t_hist is a new table with "historical tuples."
Suppose t with attributes a, b. PS: on t_hist I added isTop for better performance on t .
-- .... CREATE TABLE t_hist ( -- the old attributes for t: id integer NOT NULL, -- a primary key of t a varchar(10), -- any attribute b integer, -- any attribute -- new attributes for revision control: isTop BOOLEAN NOT NULL DEFAULT true, -- "last version" or "top" indicator cmd varchar(60) DEFAULT 'INSERT', -- for traceability moment timestamp NOT NULL DEFAULT now(), -- for sort revisions UNIQUE(id,moment) ); CREATE VIEW t AS SELECT id,a,b FROM t_hist WHERE isTop; -- same, but better performance, as -- SELECT id,a,b FROM t_hist GROUP BY id,a,b HAVING MAX(moment)=moment -- Verifies consistency in INSERT: CREATE FUNCTION t_hist_uniq_trig() RETURNS TRIGGER AS $$ DECLARE aux BOOLEAN; BEGIN SELECT true INTO aux FROM t_hist WHERE id=NEW.id AND moment>=NEW.moment; IF found THEN -- want removes from top? RAISE EXCEPTION 'TRYING TO INCLUDE (ID=%) PREVIOUS TO %', NEW.id, NEW.moment; END IF; RETURN NEW; END $$ LANGUAGE plpgsql; CREATE TRIGGER uniq_trigs BEFORE INSERT ON t_hist FOR EACH ROW EXECUTE PROCEDURE t_hist_uniq_trig(); CREATE FUNCTION t_reset_top(integer) RETURNS BOOLEAN AS $BODY$ UPDATE t_hist SET isTop=false WHERE isTop=true AND id=$1 RETURNING true; -- null se nao encontrado $BODY$ LANGUAGE sql; -------- -- Implements INSER/UPDATE/DELETE over VIEW t, -- and controls unique id of t: CREATE OR REPLACE FUNCTION t_cmd_trig() RETURNS TRIGGER AS $$ DECLARE aux BOOLEAN; BEGIN aux:=true; IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN aux := t_reset_top(OLD.id); -- rets. true ou NULL ELSE SELECT true INTO aux FROM t_hist WHERE id=NEW.id AND isTop; END IF; IF (TG_OP='INSERT' AND aux IS NULL) OR (TG_OP='UPDATE' AND aux) THEN INSERT INTO t_hist (id,a,b,cmd) VALUES (NEW.id, NEW.a,NEW.b,TG_OP); ELSEIF TG_OP='DELETE' AND aux THEN -- if first delete UPDATE t_hist SET cmd=cmd||' AND DELETE AT '||now() ELSEIF TG_OP='INSERT' THEN -- fails by not-unique(id) RAISE EXCEPTION 'REGISTER ID=% EXIST', NEW.id; ELSEIF TG_OP='UPDATE' THEN -- .. redundance, a trigger not goes here RAISE EXCEPTION 'REGISTER ID=% NOT EXIST', NEW.id; END IF; RETURN NEW; -- discarded END $$ LANGUAGE plpgsql; CREATE TRIGGER ins_trigs INSTEAD OF INSERT OR UPDATE OR DELETE ON t FOR EACH ROW EXECUTE PROCEDURE t_cmd_trig(); -- Examples: INSERT INTO t(id,a,b) VALUES (1,'aaaaaa',3); -- ok INSERT INTO t(id,a,b) VALUES (1,'bbbbbb',3); -- error UPDATE t_hist SET a='teste' WHERE id=1; -- ok -- SELECT * from t; SELECT * from t_hist; INSERT INTO t(id,a,b) VALUES (2,'bbbbbb',22), -- ok (3,'bbbbbb',22), -- ok (4,'aaaaaa',2); -- ok DELETE FROM t WHERE id=3; -- SELECT * from t; SELECT * from t_hist;
PS: I suggest not trying to adapt this solution for one table without presentation, your trigger will be very complicated; do not try to adapt for t_hist inherit t , where all content inserted into t_hist will be copied to t .