【问题标题】:Tracking changed fields without maintaining history在不维护历史记录的情况下跟踪更改的字段
【发布时间】:2012-03-07 07:07:37
【问题描述】:

我有一个名为 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 元素。其余元素应该看起来正常。

【问题讨论】:

  • 您是否需要记录每次更改行的时间,还是只需要知道该行历史上的某个时间点已更改?
  • 我需要知道哪些列值发生了变化。我需要知道每一列的内容,以便我可以为该特定字段进行 UI 更改

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


【解决方案1】:

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

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

要在位域中设置位,请在更新语句中使用|(位或运算符),例如

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

UPDATE table 
SET field2 = 'new value', bitfield = bitfield | 2

等对于每个字段,使用 N-1 的 2 次幂作为 | 之后的值

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

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 列,您可以将“标志”存储为整数,然后使用位标志方法来指示已更改的列。忽略或不费心标记您不关心的内容。

因此,如果标志字段 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

【讨论】:

  • 您的解决方案看起来不错,但我不确定每个表中的列数。您还可以指出我可以看到实现并更好地理解它的任何博客条目吗?
  • 我会在几分钟内给你写出来——如果你使用 bitint,你实际上可以写多达 64 列。
  • 另外,如果您需要超过 16 列,则可以有多个标志字段。我没有得到的是“更改”标记。它代表一个字段自创建以来至少更改过一次?或者他们是否需要存储时间戳以便知道每个值何时发生变化?
  • 只需要知道该字段自创建以来是否发生过更改。何时更改了哪个字段在这里并不重要。
  • @Asdfg - 查看我上面的示例代码,我相信这正是您所需要的,而且使用起来超级简单,空间效率很高。
【解决方案2】:

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

这只是在您的表中添加了几列,并不像您提出的任何一种架构那样具有侵入性,并且跟踪更改所需的触发器也由该工具生成。

【讨论】:

    【解决方案3】:

    您应该有一个存储 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
    

    【讨论】:

    • 有人愿意解释一下 DV 吗?
    • 我需要知道名称是否已更改。如果名称未更改且仅更改了描述,我需要在 UI 上通知用户描述已更改但名称未更改。可能会以红色显示该字段,并以默认颜色显示未更改的字段。
    • SQL Server 无法响应数据更改并向应用程序发送颜色标志,除非您手动引发错误并在应用程序中捕获它(或从尝试更新)。我不清楚您是要阻止还是仅审核此类更改。上面的触发器假定后者。
    • 我已经更新了这个问题。您几乎已经理解了,除了每个表中有 20 列,我需要跟踪 column1 中的值是否更改,然后以不同的样式显示相应的 UI 元素。如果 column2 的值发生变化,则以不同的样式显示其他一些 UI 元素。因此,当我执行“选择”时,我可以为每一列获取这些 _Changed_Flag 字段并将业务逻辑应用到它们
    • @Asdfg 请立即尝试。我根据您提供的更多信息再次更新了答案。请注意,在插入触发器到位之前,您需要为所有已经存在的书籍手动填充 booklog 表。我希望您能够使用正确的业务逻辑编写针对 BookLog 表的查询,以在您的 UI 中反映历史状态。
    【解决方案4】:

    我可以向您保证,在您提供此解决方案后,下一个请求将是“向我展示它之前的内容”。只要继续,有一个历史表。这将解决您当前的问题和您未来的问题。这是非常标准的系统设计。

    【讨论】:

      【解决方案5】:

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

      当您想检查某行是否已更改时,只需检查是否 updated_at > created_at。

      【讨论】:

      • 您可能还想设置触发器来强制 created_at 在设置后永远不会更新。您可以从您的应用程序逻辑中执行此操作,但在某些情况下,我发现通过触发器执行这样的规则更容易。
      猜你喜欢
      • 1970-01-01
      • 2015-01-18
      • 2014-01-23
      • 2015-11-15
      • 1970-01-01
      • 1970-01-01
      • 2014-11-05
      • 2012-09-15
      • 1970-01-01
      相关资源
      最近更新 更多