2011-02-11 121 views
6

我正在开发一个项目,我必须为每个表添加一种数据版本或历史记录功能。基本上,我们必须跟踪数据库中的每个插入或更改,以便在每个表中回滚或查看以前版本的数据。将版本/历史记录系统添加到数据库表

我的项目经理设想这样做的方法是为每个表添加一些新的色彩。主要特点是一个名为“版本”的色彩。每次进行更新时,都不会真正更新,旧行仍会保留,但会为表格添加一个新行,并为“版本”增加一个值。

要显示当前数据,我们只使用一个只显示每个类型的版本号最高的行的视图。

尽管在不同版本之间来回移动时效果很好,但我遇到了这种方法的问题。对于表之间存在的任何关系,我们需要定义外键,而外键只能引用另一个表中的唯一字段。现在我们保留了同一行的多个版本(就我们的应用程序而言,它们具有相同的“ID”,因为它基本上是相同的一段数据),所以我们不能再使用另一个表的“Id”作为外键一张桌子。

我们对每一行都使用一个唯一的主键字段,但由于多行基本上是同一事物的不同版本,所以这是无用的标识符。我们可以手动跟踪每种条目的最新版本,并在每次更改时更新相应的外键关系,但是看起来像很多工作,而且我不确定这样做总是能够正常工作(例如,恢复到以前的版本的条目可能会导致外键引用另一个表中的另一个条目的旧版和不可用版本。)

我知道还有其他方法可以保留数据库更新的历史记录(例如,通过使用单独的历史记录表每张桌子),但我在这个项目中坚持采用这种方法。 是否有一些更明显的方式来处理这种我缺少的表之间的关系?

注意:我正在使用MS SQL Server 2008 R2。

回答

4

上有版本的好文章为MySQL:http://www.jasny.net/articles/versioning-mysql-data/

我认为这是基本可以很容易地应用到任何其它系统

+0

谢谢,但这实际上是一种完全不同的版本化数据的方式。该文章中使用的方案使用单独的修订表,而我所说的方式旨在将所有内容保存在同一个表中。无论如何,我们已经采用了不同的版本管理方式(非常类似于文章中的版本)。 – MAK 2011-02-14 13:48:04

1

你说,不希望有一个“独立的修订表”,而不是投票因为这个,FractalizeR的解决方案。好吧,这是一个“一个表格解决方案”...但是,请简化/概括您的问题,以便为所有访问者提供更好的答案和更好地使用此页面:我认为您的问题是关于SQL表格上的“修订控制”。

“ISO 2008 SQL”的解决方案,那么我认为它也适用于Microsoft SQL Server。我在PostgreSQL 9.1上测试了它。

在这样那样的问题,我们可以使用SQL视图来“模拟”原始表,以及“版本表”作为一个新的,更多的属性: *的排序(排序)的新属性moment的修订和时间注册; *“追溯性”的新属性cmd(不是必需的)。

假设您的原始(和传统)表格是t。对于版本控制,你必须添加新的属性,但其他程序员不需要看到这个新属性...解决方法是将表t重命名为t_hist,并向其他程序员提供SQL VIEW t(作为t_hist的查询)。

t是显示传统表的视图:只有“当前元组”。 t_hist是具有“历史元组”的新表。

假设t属性a,b。 PS:t_hist我在t上添加了isTop以获得更好的性能。

-- .... 
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:我建议不要尝试适应没有针对这一解决方案为一个表,你的触发器将是非常复杂的;既不要尝试适应t_hist继承t,其中插入t_hist的所有内容都将复制到t

+0

我的问题中没有看到任何可以称为“个人详细信息”的东西。我相信那里的信息是阐述我的问题所需的最低限度。也许它太大了 - 但肯定没有“个人信息”。我很感谢您花时间回答,但正如我在我的问题中所述,您提出的实施方案的问题是如何在此类方案中表示外键关系。从具有额外信息的表格中查看,我已经知道该怎么做。无论如何,这已经是一年多了,我采用了像FractalizeR的方法。但是,非常感谢。 – MAK 2012-04-13 08:43:31