在不保留历史记录的情

时间:2012-02-15 21:42:09

标签: sql-server sql-server-2005 database-design

我有一个名为Books的表,其中包含一些列。

ColumnNames: BookId, BookName, BookDesc, xxx

我想跟踪某些列的更改。 我不需要保留旧价值和新价值的历史。我只想跟踪该值是否已更改。

实现这一目标的最佳方法是什么?

1)创建Books表格为:

ColumnNames: BookId, BookName, BookName_Changed_Flag, BookDesc, BookDesc_Changed_Flag, 
xxx, xxx_Changed_Flag?

2)创建一个单独的表Books_Change_Log,与Books表完全相同,但只有轨道更改列为:

ColumnNames: BookId, BookName_Changed_Flag, BookDesc_Changed_Flag, xxx_Changed_Flag?

请告知。

- 更新 -

每张表中有超过20列。每列代表UI中的某个元素。如果列值从其原始记录中更改,我需要显示以不同样式表示列值的UI元素。其余的元素应该看起来很正常。

5 个答案:

答案 0 :(得分:2)

如何在TSQL中使用位域(用于更新和读取)

在开始时将位域默认设置为0(意味着没有变化),您应该使用int类型最多32位数据,bigint最多64位数据。

要在位字段中设置位,请使用update语句中的|(位OR运算符),例如

UPDATE table 
SET field1 = 'new value', bitfield = bitfield | 1

UPDATE table 
SET field2 = 'new value', bitfield = bitfield | 2
对于每个字段的

等,使用2到N-1的幂为|

之后的值

要读取位字段,请使用&(位AND运算符)并查看它是否为真,例如

SELECT field1, field2,
       CASE WHEN (bitfield & 1) = 1 THEN 'field1 mod' ELSE 'field1 same' END,
       CASE WHEN (bitfield & 2) = 2 THEN 'field2 mod' ELSE 'field2 same' END
FROM table

注意我可能不会使用文本,因为这将由应用程序使用,类似这样的工作

SELECT field1, field2,
        CASE WHEN (bitfield & 1) = 1 THEN 1 ELSE 0 END AS [field1flag],
        CASE WHEN (bitfield & 2) = 2 THEN 1 ELSE 0 END AS [field2flag]
FROM table

或者您可以使用上面的!= 0来简化,就像我在下面的测试中所做的那样


Have to actually test to not have errors, click for the test script


原始答案:

如果表中的列少于16列,则可以将“flags”存储为整数,然后使用bit flag方法指示已更改的列。只是忽略或不打扰标记你不关心的那些。

因此,如果flagfield BOOLEAN AND 2 ^ N为真,则表示第N个字段已更改。

或者最大N = 2

的示例

0 - 没有任何改变(所有位0)

1 - 字段1已更改(第1位)

2 - 字段2已更改(第二位1)

3 - 字段1 + 2已更改(第一位和第二位1)

请参阅此链接以获得更好的定义:http://en.wikipedia.org/wiki/Bit_field

答案 1 :(得分:1)

你应该有一个存储BookId和更改日期的日志表(你不需要那些其他列 - 如你所说,你不需要旧的和新的值,你可以随时获得Books表中的名称,描述等的当前值,没有理由将其存储两次)。除非你只对最后一次改变感兴趣。您可以使用books表上的简单更新触发器填充日志表。例如,使用您提供的新信息:

CREATE TABLE dbo.BookLog
(
  BookID INT PRIMARY KEY,
  NameHasChanged BIT NOT NULL DEFAULT 0,
  DescriptionHasChanged BIT NOT NULL DEFAULT 0
  --, ... 18 more columns
);

CREATE TRIGGER dbo.CreateBook
ON dbo.Books FOR INSERT
AS
BEGIN
    SET NOCOUNT ON;

    INSERT dbo.BookLog(BookID) SELECT BookID FROM inserted;
END
GO

CREATE TRIGGER dbo.ModifyBook
ON dbo.Books FOR UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE t SET 
        t.NameChanged = CASE WHEN i.name <> d.name 
          THEN 1 ELSE t.NameChanged END,
        t.DescriptionChanged = CASE WHEN i.description <> d.description 
          THEN 1 ELSE t.DescriptionChanged END,
           --, 18 more of these assuming all can be compared with simple <> ...
    FROM dbo.BookLog AS t
    INNER JOIN inserted AS i ON i.BookID = t.BookID
    INNER JOIN deleted AS d  ON d.BookID = i.BookID;
END
GO

答案 2 :(得分:1)

我知道你说你不需要它,但有时它更容易使用现成的东西来完成所有事情,例如:http://autoaudit.codeplex.com/

这只会在您的表中添加几列,并且几乎不会像您提出的任何模式那样具有侵入性,并且该工具也会生成跟踪更改所需的触发器。

答案 3 :(得分:1)

我可以向您保证,在您提供此解决方案后,下一个请求之一就是“告诉我之前的情况”。请继续,并有一个历史表。这将解决您当前的问题和您未来的问题。它是非平凡系统的标准设计。

答案 4 :(得分:0)

在表格中放置两个日期时间列,“created_at”和“updated_at”。默认为current_timestamp。如果要更改行中的数据,则只设置updated_at的值。您可以使用表上的触发器来强制执行此操作,该触发器检查是否有任何列值发生更改,然后更新“updated_at”(如果是)。

如果您想检查行是否有变化,只需检查updated_at&gt; created_at。