我正在开发一个项目,我必须为每个表添加一种数据版本控制或历史记录功能。基本上我们必须跟踪数据库中的每个插入或更改,以便在每个表中回滚或查看以前版本的数据。
我的项目经理设想这样做的方法是为每个表添加一些新的颜色。主要特征是名为“版本”的coloumn。每次更新时,都没有真正更新,旧行仍然存在,但是新行添加到表中,其中“version”的值递增。
要显示当前数据,我们只使用一个视图,该视图仅显示每种类型的版本号最高的行。
虽然这在不同版本之间来回移动时效果很好,但我遇到了这种方法的问题。对于表之间存在的任何关系,我们需要定义外键,外键只能引用另一个表中的唯一字段。既然我们保留了同一行的多个版本(具有相同的'Id',因为它与我们的应用程序有关,它基本上是相同的数据)我们不能再使用另一个表的'Id'作为外键一张桌子。
我们正在为每一行使用唯一的主键字段,但这对于标识符来说是无用的,因为几行基本上是同一事物的不同版本。我们可以手动跟踪每种条目的最新版本,并在每次更改时更新相应的外键关系,但这看起来像很多工作,我不确定它是否总能工作(例如,恢复到以前的版本一个条目可能导致外键引用其他表中另一个条目的旧版本和不可用版本。)
我知道还有其他方法可以保存数据库更新的历史记录(例如,通过为每个表使用单独的历史记录表),但我在这个项目中坚持这种方法。 是否有一些更明显的处理表之间关系的方法,我错过了?
注意:我使用的是MS SQL Server 2008 R2。
答案 0 :(得分:4)
关于MySQL的版本控制有一篇很好的文章:http://www.jasny.net/articles/versioning-mysql-data/
我认为它的基础知识可以轻松应用于任何其他系统
答案 1 :(得分:1)
你说不想要一个“单独的修订表”,因为这不投票给FractalizeR的解决方案。好的,这是一个“一个表解决方案”...但是,请简化/概括您的问题,以获得更好的答案,并为所有访问者更好地使用此页面:我认为您的问题是关于SQL表的“修订控制”。
“ISO 2008 SQL”的解决方案,我认为它也适用于Microsoft SQL-Server。我在PostgreSQL 9.1上测试过它。
在这种问题中,我们可以使用SQL视图来“模拟”原始表,并使用“版本化表”作为新表,具有更多属性:
*用于排序(排序)修订和时间注册的新属性moment
;
*“可追溯性”的新属性cmd
(不是必需的)。
假设您的原始(和传统)表格为t
。对于版本控制,您必须添加新属性,但其他程序员不需要查看此新属性...解决方案是将表t
重命名为t_hist
并向其他程序员提供SQL VIEW {{ 1}}(作为对t
的查询)。
t_hist
是一个用于显示传统表格的视图:仅“当前元组”。 t
是新表,带有“历史元组”。
假设t_hist
具有属性a,b。
PS:在t
上我添加t_hist
以便在isTop
上获得更好的效果。
t
PS:我建议不要在没有视图的情况下尝试将此解决方案用于一个表,您的触发器将非常复杂;既不会尝试适应 -- ....
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;
继承t_hist
,其中t
中插入的所有内容都会复制到t_hist
。