将版本/历史记录系统添加到数据库表 [已关闭]

Adding a version/history system to database table [closed]

提问人:MAK 提问时间:2/11/2011 最后编辑:Marco EcksteinMAK 更新时间:11/2/2020 访问量:13025

问:


想改进这个问题吗?通过编辑这篇文章添加详细信息并澄清问题。

3年前关闭。

我正在做一个项目,我必须向每个表添加一种形式的数据版本控制或历史记录功能。基本上,我们必须跟踪数据库中的每个插入或更改,以便轻松回滚或查看每个表中以前版本的数据。

我的项目经理设想的方法是向每个表添加一些新的列。主要功能是一个名为“version”的列。每次进行更新时,都不会真正更新任何内容,旧行会保留,但会向表中添加一个新行,其值为“version”。

为了显示当前数据,我们只使用一个视图,该视图仅显示每种类型中版本号最高的行。

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

我们为每一行使用唯一的主键字段,但这作为标识符是无用的,因为几行基本上是同一事物的不同版本。我们可以手动跟踪每种条目的最新版本,并在每次更改某些内容时更新相应的外键关系,但这看起来需要做很多工作,我不确定这是否总是有效(例如,恢复到条目的先前版本可能会导致外键引用其他表中另一个条目的旧版本和不可用版本。

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

注意:我使用的是MS SQL Server 2008 R2。

SQL-Server-2008 数据库设计

评论

0赞 Marco Eckstein 11/2/2020
这回答了你的问题吗?如何对数据库中的记录进行版本控制

答:

1赞 Peter Krauss 4/13/2012 #1

你说不想要一个“单独的修订表”,不投票给 FractalizeR 的解决方案,因为这个。好的,这里有一个“一张桌子的解决方案”......但是,请简化/概括您的问题,以便所有访问者获得更好的答案并更好地使用此页面:我认为您的问题与SQL表的“版本控制”有关。

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

在此类问题中,我们可以使用 SQL 视图来“模拟”原始表,并将“版本化表”作为新表,并具有更多属性: * 用于对修订进行排序(排序)和时间登记的新属性; * “可追溯性”的新属性(并非真正必要)。momentcmd

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

t是用于显示常规表的 VIEW:仅显示“当前元组”。 是带有“历史元组”的新表。t_hist

假设属性为 a,b。 PS:在我添加以获得更好的性能。tt_histisTopt

 -- ....
 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_histtt_histt

评论

0赞 MAK 4/13/2012
在我的问题中,我没有看到任何可以称为“个人详细信息”的内容。我相信那里的信息是陈述我的问题所需的最低限度。也许它太大了——但肯定没有“个人信息”。感谢您抽出宝贵时间回答,但正如我在问题中所说,您提出的实现的问题在于如何在这样的方案中表示外键关系。带有额外信息的表格中的视图,我已经知道该怎么做了。无论如何,这已经有一年多的历史了,我采用了类似 FractalizeR 的方法。但同样感谢。
0赞 Peter Krauss 4/14/2012
您好,我同意“个人”这个词,我删除了它。我最初的评论是改善网站和您的提问率。关于VIEW的使用,它不是另一个表,它只是触发器和外部用户的良好门面。解决方案是“一张桌子的解决方案”(!对于非“纯SQL”解决方案,您可以在此处注释是否对您(作为 MS-SQL 服务器用户)有好处,例如:stackoverflow.com/questions/9481557stackoverflow.com/questions/503472