【问题标题】:Compare deleted and inserted table in SQL Server 2008比较 SQL Server 2008 中删除和插入的表
【发布时间】:2014-04-25 07:21:16
【问题描述】:

我是 SQL Server 2008 的新手,我需要大家的建议。我想找出 SQL Server 2008 的 inserteddeleted 表中的更改值,因为我目前正在执行审计跟踪以保留旧值和新值。如何循环所有列以找出删除和插入表中哪个字段的值更改?我曾尝试使用 if else 语句进行比较

例如:

create trigger trg_XXX on dbo.table
after update
as
begin
    declare 
       @oldID varchar(6),
       @newID varchar(6)

    select @oldID = ID from deleted
    select @newID = ID from inserted

    if(@oldID != @newID)
       insert into table (a, b) values (@oldID, @newID)
    else
       print('do nothing')

有没有办法使用游标循环删除和插入的表或任何替代方式?可以举个例子吗?

【问题讨论】:

  • 插入和删除表是什么意思?此代码是否是创建审核的触发器的一部分?
  • inserted and deleted table是sql server 2008中的临时表,可以从deleted表中获取旧值,从inserted表中获取新值。
  • 这些是(“虚拟”)表,仅存在于触发器的上下文中 - 但 在触发器中使用游标!这是扼杀你所有表现的最糟糕的方式.....

标签: sql sql-server sql-server-2008 audit-trail


【解决方案1】:

我不太确定,你的目标是什么,我想可能是这样的。 假设我们有一张这样的表:

CREATE TABLE Product
(
     ID      INT                   PRIMARY KEY,
     Name    NVARCHAR(100)         NOT NULL,
     Price   DECIMAL(10,2)         NOT NULL
);

还有一些像这样的审计表:

CREATE TABLE ProductAudit
(
     AuditID      INT                   IDENTITY PRIMARY KEY, 
     ProductID    INT                   NOT NULL
     OldName      NVARCHAR(100)         NULL,
     OldPrice     DECIMAL(10,2)         NULL,
     NewName      NVARCHAR(100)         NULL,
     NewPrice     DECIMAL(10,2)         NULL
);

然后你创建一个触发器:

CREATE TRIGGER TR_AUDIT
ON Product
FOR INSERT, UPDATE, DELETE
AS
BEGIN
       INSERT INTO ProductAudit (ProductID, OldName, OldPrice, NewName, NewPrice)
       SELECT 
           COALESCE(I.ID, D.ID),
           D.Name,
           D.Price,
           I.Name,
           I.Price
       FROM 
           INSERTED I FULL OUTER JOIN DELETED D ON I.ID = D.ID;
END
GO

你有它。

【讨论】:

  • +1 非常好的答案。在我看来,您应该包含一个用于审计的时间戳,但这是容易的部分
  • @t-clausen.dk 当然,您是对的,除了时间戳之外,我还会包含一些有关用户和进程的数据,但我认为这不是重点。我什至不确定操作员是否真的清楚他想做什么。
  • 当然我有系统用户,审计表的审计日期。我在表中有 80 列,我不能使用 if else 语句来处理所有 80 列。有没有像 UPDATE(),COLUMN_UPDATED() 这样的例子来检测删除和插入表中的变化,并通过列循环将其插入到审计表中?
【解决方案2】:

我认为您正在寻找 SQL Server 中非官方称为 magic tables 的内容。

表“INSERTED”和“DELETED”被称为魔法表 SQL 服务器。我们在数据库中看不到这些表。但是我们可以访问这些 来自“TRIGGER”的表格

当我们将记录插入表中时,会创建魔术表“INSERTED” 在该表中,当前插入的行将可用。我们可以访问这个 记录在“TRIGGER”中。

当我们更新创建触发器的表上的记录时,魔术表“INSERTED”和“DELETED”都将被创建,更新记录的旧数据将在“DELETED”表中可用并且,新数据将在“INSERTED”表中可用,同时在触发器内访问它们。

当我们从表中删除记录时,会创建魔术表“DELETED” 在该表中,当前删除的行将可用。我们可以访问这个 记录在“TRIGGER”中。

例子:

以下代码解释了魔术表“INSERTED”:

CREATE TRIGGER LogMessage
ON EMP
FOR INSERT
AS
   DECLARE @EMPNAME varchar(50)
   SELECT @EMPNAME= (SELECT EMPNAME FROM INSERTED)
   INSERT INTO LOGTABLE(UserId,Message) values (@EMPNAME,'Record Added')
GO

以下代码解释魔术表“DELETED

CREATE TRIGGER LogMessage
ON EMP
FOR DELETE
AS
   DECLARE @EMPNAME varchar(50)
   SELECT @EMPNAME= (SELECT EMPNAME FROM DELETED)
   INSERT INTO LOGTABLE(UserId,Message) values (@EMPNAME,'Record Removed')
GO

来源(所有这些文章和作者的功劳):

http://www.codeproject.com/Questions/285423/what-is-magic-table-different-types-of-magic-table http://www.dotnetspider.com/resources/29332-Magic-tables-SQL-Server.aspx

。 .

或者

,你可以试试: Obtaining Changes by Using the Change Tracking Functions,它的 MSDN 链接解释如何 使用内置函数跟踪更改。

CHANGETABLE(CHANGES …)函数

此行集函数用于查询更改信息。该函数查询存储在内部更改跟踪表中的数据。该函数返回一个结果集,其中包含已更改的行的主键以及其他更改信息,例如操作、更新的列和该行的版本。

CHANGE_TRACKING_CURRENT_VERSION()函数

用于获取下次查询更改时使用的当前版本。此版本代表上次提交事务的版本。

CHANGE_TRACKING_MIN_VALID_VERSION()function

用于获取客户端可以拥有的最低有效版本,并且仍然可以从 CHANGETABLE() 获得有效结果。客户端应根据此函数返回的值检查上次同步版本。如果上次同步版本小于该函数返回的版本,客户端将无法从 CHANGETABLE() 中获取有效结果,必须重新初始化。

参考http://technet.microsoft.com/en-us/library/cc280358%28v=sql.105%29.aspx的语法和用法

希望对你有帮助。

【讨论】:

  • 这条语句SELECT @EMPNAME= (SELECT EMPNAME FROM INSERTED)非常危险 - 触发器将每个语句调用一次Inserted 可以(并且会! ) 包含多行 - 那么您在这里选择哪一个?同时你也忽略了所有其他的......触发器必须以基于集合的方式编写,考虑到InsertedDeleted 将包含多行!
  • 我已经尝试过这种方法,但我有大约 60-80 列可以从删除和插入的表中进行比较。我的意思是——如果我不想使用 if else 语句来比较值,是否无论如何都要遍历所有列以找出哪个值发生了变化。?例如光标?但我对光标感到困惑
  • @user3387045,更新了添加 msdn 链接的答案。
  • @PranavSingh 好的,我会查看您发布的链接并感谢您的回复。
【解决方案3】:

您可以通过将插入和删除的表转换为 2 个 xml、遍历属性并比较 xml 值来制作动态“比较器”。

示例:

CREATE TRIGGER MY_COMPARER
   ON TABLE_NAME
   AFTER UPDATE
AS
BEGIN
    DECLARE @columnIndex INT = 1;
    DECLARE @maxColumns INT = (select count(*) from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TABLE_NAME');
    DECLARE @insertedXML XML = (select top 1 * from INSERTED FOR XML PATH('ROOT'), ELEMENTS XSINIL);
    DECLARE @deletedXML XML = (select top 1 * from DELETED FOR XML PATH('ROOT'), ELEMENTS XSINIL);

    WHILE @columnIndex <= @maxColumns BEGIN 
        DECLARE @insertedXMLValue XML = (select @insertedXML.query('/ROOT/*[sql:variable("@columnIndex")]'));
        DECLARE @deletedXMLValue XML = (select @deletedXML.query('/ROOT/*[sql:variable("@columnIndex")]'));

        DECLARE @insertedValue NVARCHAR(MAX) = CONVERT(NVARCHAR(MAX), @insertedXMLProp);
        DECLARE @deletedValue NVARCHAR(MAX) = CONVERT(NVARCHAR(MAX), @deletedXMLProp);

        IF (@insertedValue != @deletedValue)
            print('Column: ' + CONVERT(NVARCHAR(MAX), @columnIndex) + ' has changed')

        SET @columnIndex = @columnIndex + 1;
    END
END

【讨论】:

    【解决方案4】:

    第一:不要在触发器内使用光标 - 永远!

    第二:确定更新语句中包含哪些字段,您可以使用: UPDATE()COLUMNS_UPDATED()

    注意:这不会列出其值已更改的字段,仅列出 UPDATE 语句的 SET 部分中包含的列的列表。

    第三:您可以使用多种方法来审核对表的更改(Most efficient method to detect column change in MS SQL Server 上接受的答案有一个很好的列表和使用指南,如果您是使用 SQL Server Enterprise 你也可以看看使用Change Data Capture

    我将使用的一些示例审计代码(我想逐列记录审计表):

    INSERT INTO AuditTable (ColumnChanged, OldValue, NewValue) /* I assume there are default columns logging who/when the change was done by? */
        SELECT 'ColumnA' as ColumnChanged, d.ColumnA, i.ColumnA
        FROM inserted i join deleted d ON d.PKID = i.PKID
        WHERE 
            /* both aren't null and the value has changed */
            (d.ColumnA IS NOT NULL AND i.ColumnA IS NOT NULL AND d.ColumnA != i.ColumnA) 
            /* it was null and now it isn't */
            OR (d.ColumnA IS NULL AND i.ColumnA IS NOT NULL) 
            /* it wasn't null and now it is */
            OR (d.ColumnA IS NOT NULL AND i.ColumnA IS NULL)
    UNION 
        SELECT 'ColumnB' as ColumnChanged, d.ColumnB, i.ColumnB
        FROM inserted i join deleted d ON d.PKID = i.PKID
        WHERE 
            /* both aren't null and the value has changed */
            (d.ColumnB IS NOT NULL AND i.ColumnB IS NOT NULL AND d.ColumnB != i.ColumnB) 
            /* it was null and now it isn't */
            OR (d.ColumnB IS NULL AND i.ColumnB IS NOT NULL) 
            /* it wasn't null and now it is */
            OR (d.ColumnB IS NOT NULL AND i.ColumnB IS NULL)
    ....  /* continuing for each column */
    

    按行审计会更简单(从 sql 的角度来看,并且更快 [由于写入更少]),即:

    INSERT INTO AuditTable (OldValueA, NewValueA, OldValueB, NewValueB) 
    SELECT d.ColumnA, i.ColumnA, d.ColumnB, i.ColumnB
    FROM inserted i join deleted d ON d.PKID = i.PKID
    WHERE 
    /* same check for columnA */
        /* both aren't null and the value has changed */
        (d.ColumnA IS NOT NULL AND i.ColumnA IS NOT NULL AND d.ColumnA != i.ColumnA) 
        /* it was null and now it isn't */
        OR (d.ColumnA IS NULL AND i.ColumnA IS NOT NULL) 
        /* it wasn't null and now it is */
        OR (d.ColumnA IS NOT NULL AND i.ColumnA IS NULL)
    /* now check columnB */
        (d.ColumnB IS NOT NULL AND i.ColumnB IS NOT NULL AND d.ColumnB != i.ColumnB) 
        OR (d.ColumnB IS NULL AND i.ColumnB IS NOT NULL) 
        OR (d.ColumnB IS NOT NULL AND i.ColumnB IS NULL)
    ....  /* continuing for each column */
    

    【讨论】:

    • @AndrewBickerthon ~ 如果我有 80 列,我可以使用 COLUMNS_UPDATED() 循环遍历已删除和插入的表以更改值并插入到审计表中吗?
    • @user3387045 从软件开发人员的角度来看,为审计触发器中的所有 80 列编写几乎相同的代码已经过时了,但最好只做一次(在创建触发器时)而不是让每次更新一行时,sql server 都会执行此工作。我会制定出您想要使用的模式(请参阅链接的答案)并生成您想要放入触发器的 sql。
    • @user3387045 在触发器/存储过程中允许循环(它们是有效的 sql),但我强烈建议您避免使用它们,除非绝对必要,它们相关的成本很高,而代码更大,看起来更令人生畏,如果您将完整的审计 sql 放入其中,它的性能会显着提高
    • @AndrewBickerthon ~ 我使用 if else 语句来比较之前所有插入和删除的表,我的工作被拒绝了。我会试试你的方法。顺便说一句,这是制作审计触发器的好例子吗?下面的链接,因为如果表中添加了新列,我不想再更改触发器。 p2p.wrox.com/sql-server-2000/…
    • @user3387045 如果我是您的 DBA,我会 1) 由于该方法的性能和安全风险而拒绝该触发器。 2)建议您使用该代码的一个版本来生成触发器并要求您重新提交真正的触发器。重申“你不应该动态创建 sql 来进行实时审计(特别是因为你的表的结构不应该经常改变),而是有一个方法/过程/工具可以让你在你的时候快速重新创建触发器代码添加一个新列。
    猜你喜欢
    • 2014-12-23
    • 2014-06-29
    • 2011-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多