提问人:MAK 提问时间:2/11/2011 最后编辑:Marco EcksteinMAK 更新时间:11/2/2020 访问量:13025
将版本/历史记录系统添加到数据库表 [已关闭]
Adding a version/history system to database table [closed]
问:
我正在做一个项目,我必须向每个表添加一种形式的数据版本控制或历史记录功能。基本上,我们必须跟踪数据库中的每个插入或更改,以便轻松回滚或查看每个表中以前版本的数据。
我的项目经理设想的方法是向每个表添加一些新的列。主要功能是一个名为“version”的列。每次进行更新时,都不会真正更新任何内容,旧行会保留,但会向表中添加一个新行,其值为“version”。
为了显示当前数据,我们只使用一个视图,该视图仅显示每种类型中版本号最高的行。
虽然这在不同版本之间来回移动方面效果很好,但我遇到了这种方法的问题。对于表之间存在的任何关系,我们需要定义外键,而外键只能引用另一个表中的唯一字段。现在我们保留了同一行的多个版本(具有相同的“Id”,因为就我们的应用程序而言,它基本上是相同的数据),我们不能再使用另一个表的“Id”作为表的外键。
我们为每一行使用唯一的主键字段,但这作为标识符是无用的,因为几行基本上是同一事物的不同版本。我们可以手动跟踪每种条目的最新版本,并在每次更改某些内容时更新相应的外键关系,但这看起来需要做很多工作,我不确定这是否总是有效(例如,恢复到条目的先前版本可能会导致外键引用其他表中另一个条目的旧版本和不可用版本。
我知道还有其他方法可以保留数据库更新的历史记录(例如,为每个表使用单独的历史记录表),但我在这个项目中坚持这种方法。 有没有一些更明显的方法来处理我缺少的这样的表之间的关系?
注意:我使用的是MS SQL Server 2008 R2。
答:
你说不想要一个“单独的修订表”,不投票给 FractalizeR 的解决方案,因为这个。好的,这里有一个“一张桌子的解决方案”......但是,请简化/概括您的问题,以便所有访问者获得更好的答案并更好地使用此页面:我认为您的问题与SQL表的“版本控制”有关。
“ISO 2008 SQL”的解决方案,那么我认为它也适用于Microsoft SQL-Server。我在 PostgreSQL 9.1 上测试了它。
在此类问题中,我们可以使用 SQL 视图来“模拟”原始表,并将“版本化表”作为新表,并具有更多属性:
* 用于对修订进行排序(排序)和时间登记的新属性;
* “可追溯性”的新属性(并非真正必要)。moment
cmd
假设您的原始(和常规)表是 。对于版本控制,您必须添加新属性,但其他程序员不需要看到这个新属性...解决方案是将表重命名为 SQL VIEW,并向其他程序员提供 SQL VIEW(作为查询)。t
t
t_hist
t
t_hist
t
是用于显示常规表的 VIEW:仅显示“当前元组”。 是带有“历史元组”的新表。t_hist
假设属性为 a,b。
PS:在我添加以获得更好的性能。t
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;
PS:我建议不要尝试将此解决方案用于一个没有视图的表,您的触发器会非常复杂;既不尝试适应继承,其中插入的所有内容都会复制到 .t_hist
t
t_hist
t
评论
上一个:线程似乎按顺序运行线程
下一个:为同一对象定义多个哈希函数
评论