【问题标题】:T-SQL: what COLUMNS have changed after an update?T-SQL:更新后哪些列发生了变化?
【发布时间】:2011-03-24 18:30:11
【问题描述】:

好的。我正在对表中的单行进行更新。 除主键外,所有字段都将被新数据覆盖。 但是,并非所有值都会更改更新的 b/c。 比如我的表如下:

TABLE (id int ident, foo varchar(50), bar varchar(50))

初始值为:

id   foo   bar
-----------------
1    hi    there

然后我执行UPDATE tbl SET foo = 'hi', bar = 'something else' WHERE id = 1

我想知道的是哪一列的值发生了变化,它的原始值是什么,它的新值是什么。

在上面的示例中,我希望看到“bar”列从“there”更改为“something else”。

可以不进行逐列比较吗?是否有一些优雅的 SQL 语句,例如 EXCEPT,它会比行更细粒度?

谢谢。

【问题讨论】:

    标签: sql-server sql-server-2005 tsql


    【解决方案1】:

    没有可以运行的特殊语句可以准确地告诉您更改了哪些列,但是查询并不难编写:

    DECLARE @Updates TABLE
    (
        OldFoo varchar(50),
        NewFoo varchar(50),
        OldBar varchar(50),
        NewBar varchar(50)
    )
    
    UPDATE FooBars
    SET <some_columns> = <some_values>
    OUTPUT deleted.foo, inserted.foo, deleted.bar, inserted.bar INTO @Updates
    WHERE <some_conditions>
    
    SELECT *
    FROM @Updates
    WHERE OldFoo != NewFoo
    OR OldBar != NewBar
    

    如果您因为这些更改而尝试真正做某事,那么最好编写一个触发器:

    CREATE TRIGGER tr_FooBars_Update
    ON FooBars
    FOR UPDATE AS
    BEGIN
        IF UPDATE(foo) OR UPDATE(bar)
            INSERT FooBarChanges (OldFoo, NewFoo, OldBar, NewBar)
                SELECT d.foo, i.foo, d.bar, i.bar
                FROM inserted i
                INNER JOIN deleted d
                    ON i.id = d.id
                WHERE d.foo <> i.foo
                OR d.bar <> i.bar
    END
    

    (当然,您可能希望在触发器中做更多的事情,但这里有一个非常简单的操作示例)

    您可以使用COLUMNS_UPDATED 而不是UPDATE,但我觉得这很痛苦,而且它仍然不会告诉您哪些列实际上发生了变化,只是哪些列包含在@ 987654325@ 声明。例如,您可以写UPDATE MyTable SET Col1 = Col1,它仍然会告诉您Col1 已更新,即使没有一个值实际更改。在编写触发器时,您需要实际测试各个前后值,以确保您得到真正的更改(如果这是您想要的)。

    附:您也可以像 Rob 所说的那样 UNPIVOT,但您仍然需要在 UNPIVOT 子句中明确指定列,这并不神奇。

    【讨论】:

    • 大家在投反对票时通常会留下便条。别傻了,这个答案到底有什么问题?
    • 感谢您的信息。我一直在寻找一个特殊的声明,但如果没有,我想我将不得不像你上面那样强制它。
    【解决方案2】:

    尝试取消透视插入和删除,然后您可以加入,寻找值发生变化的地方。

    【讨论】:

    • 你能提供一个使用上述案例的例子吗?
    • /* 抱歉格式化,希望它能正常工作/在更新为 select d 后在 dbo.blah 上创建触发器 uBlah。, i.value from (select id, col , 来自已删除 unpivot 的值 (col in ([foo],[bar]) 的值) u ) d join (选择 id, col, 来自插入的 unpivot 的值 (col in ([foo],[bar]) 的值) u ) i on d.id = i.id and d.col = i.col and d.value != i.value ;
    【解决方案3】:

    您可以在触发器中检测到这一点,或在 SQL Server 2008 中使用CDC。

    如果您create a trigger FOR AFTER UPDATE 则inserted 表将包含具有新值的行,deleted 表将包含具有旧值的相应行。

    【讨论】:

    • 使用 SQL Server 2005。确切地说,我将如何在触发器中检测到这一点?
    【解决方案4】:

    跟踪数据更改的另一种选择是将数据写入另一个(可能是临时)表,然后使用 XML 分析差异。更改的数据与列名一起写入审计表。只有一件事是你需要知道表字段来准备临时表。 你可以在这里找到这个解决方案:

    【讨论】:

      【解决方案5】:

      如果您使用的是 SQL Server 2008,您可能应该看看新的变更数据捕获功能。这会做你想做的。

      【讨论】:

      • CDC 并不真正适合该用例。
      • 我使用的是 SQL Server 2005。
      【解决方案6】:
      OUTPUT deleted.bar AS [OLD VALUE], inserted.bar AS [NEW VALUE]
      

      @Calvin 我只是基于 UPDATE 示例。我并不是说这是完整的解决方案。我在暗示您可以在代码中的某处执行此操作;-)

      既然我已经从上面的答案中得到了一个-1,让我把这个放在:

      如果您真的不知道更新了哪个列,我会说创建一个触发器并在该触发器的主体中使用 COLUMNS_UPDATED() 函数(请参阅this)

      我在我的博客中创建了一个Bitmask Reference 用于此 COLUMNS_UPDATED()。如果你决定走这条路,它会让你的生活更轻松(Trigger + Columns_Updated())

      如果您不熟悉触发器,这是我的基本触发器示例http://dbalink.wordpress.com/2008/06/20/how-to-sql-server-trigger-101/

      【讨论】:

      • 这个解决方案的问题是我不知道“bar”是改变的字段。不过还是谢谢。
      • 所以我想我对此唯一的问题是:COLUMNS_UPDATED() 是否具有适当的 power(2,fieldnum-1) 位掩码,实际上是否告诉我值是否更改或仅在,正如 Aaronaught 所说,该字段是否包含在 UPDATE 语句中?
      • @Calvin “COLUMNS_UPDATED() 是否真的告诉我值是否发生了变化,或者仅当如 Aaronaught 所说的那样,该字段是否包含在 UPDATE 语句中?” - 自己编码以查看答案。怎么样?这是另一个学习新事物的机会!现在就抓住这个机会!
      • 在 SQL2005 中,它只是一个位掩码,表示在执行更新的语句中引用了该列。因此,如果程序将所有数据未更改的列发送到更新,则位掩码将反映所有列都已“更新”,即使某些值没有更改。
      • @MarlonRibunal 您提供的链接无效。
      猜你喜欢
      • 1970-01-01
      • 2017-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-10
      • 2012-04-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多