将版本/历史记录系统添加到数据库表

MAK*_*MAK 7 database sql-server-2008

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

我的项目经理设想这样做的方法是为每个表添加一些新的颜色.主要特征是名为"版本"的coloumn.每次更新时,都没有真正更新,旧行仍然存在但是新行添加到表中,其中"version"的值递增.

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

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

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

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

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

Pet*_*uss 1

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

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

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

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

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

假设t有属性 a,b。PS:t_hist我添加了isTop以获得更好的性能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;
Run Code Online (Sandbox Code Playgroud)

PS:我建议不要尝试将此解决方案应用于没有视图的一张表,您的触发器将非常复杂;既不尝试适应t_hist继承t,其中插入的所有内容都t_hist将复制到t.