在不使用JOIN的情况下在触发器中组合INSERTED和DELETED

时间:2013-05-26 11:13:29

标签: sql-server sql-server-2008-r2

我有一个审计跟踪解决方案,它将INSERTED或DELETED表的内容转储到XML的触发器中,以及当前用户,时间戳等。对于插入和更新,它记录前者,并删除它记录后者。

但是,为了确定同一记录的两个日志之间的变化,我需要在审计表上自行加入以获取先前的记录。这本身并不会太糟糕,但如果我可以在触发器中记录data_from和data_to,性能会大大提高。

显而易见的解决方案是在INSERTED和DELETED之间使用内部联接进行更新,但问题是这些表不能并且不能被索引,因此数据库需要执行完整的逐行哈希为了产生结果。因为在更新触发器中,INSERTED和DELETED记录中的顺序是相同的,所以我不禁感到必须有某种方式可以在不使用连接和不使用游标的情况下水平组合这两个表。

我已经尝试过的以及我所知道的内容不起作用:

  1. 在公用表表达式中使用ROW_NUMBER将无效 - CTE未编入索引
  2. 将INSERTED和DELETED的内容插入到临时表中,对其进行索引,然后在连接中使用它们 - 如果我找不到更好的解决方案,这是我目前的一个备用选项。
  3. 使用两个游标,一个用于INSERTED,一个用于DELETED - 出于性能原因,这是不可能的。
  4. 在触发器中加入审计跟踪表以获取以前的XML - 也可以,但不如上面的2,并且由于我在DELETED表中有我需要的数据,我不禁感到那里必须是我能用它做的有用的东西。
  5. 有什么想法吗?

3 个答案:

答案 0 :(得分:3)

您是否考虑过使用Change Data Capture功能?它比触发器更有效地捕获更改,它是一个异步后台进程,这意味着对实际进行更新的进程影响最小。

答案 1 :(得分:2)

我最终采用的解决方案是将inserteddeleted表中的数据传输到索引表变量中,然后从那里使用它们。性能不如CDC那么好,但是可接受且线性,并且上市时间要短得多。我编写了一个代码生成器来生成触发器,我在下面包含了一个示例:

IF TRIGGER_NESTLEVEL(OBJECT_ID('TR_su_type_code_audit_log')) > 1 RETURN

DECLARE @user_key INT, @tp INT = 0
IF EXISTS(SELECT 1 FROM deleted) SET @tp += 1
IF EXISTS(SELECT 1 FROM inserted) SET @tp += 2

DECLARE @i TABLE (type_code_key int, audit_data VARCHAR(MAX), PRIMARY KEY (type_code_key))
DECLARE @d TABLE (type_code_key int, audit_data VARCHAR(MAX), PRIMARY KEY (type_code_key))

INSERT INTO @i SELECT type_code_key,
    (SELECT type_code_key, type_code_group, id, description, is_system_reserved, site_key, code_int, 
            code_str, image_index, image_filename FOR XML RAW('audit'))
FROM inserted

INSERT INTO @d SELECT type_code_key,
    (SELECT type_code_key, type_code_group, id, description, is_system_reserved, site_key, code_int, 
            code_str, image_index, image_filename FOR XML RAW('audit'))
FROM deleted


SET @user_key = dbo.f_get_current_user()

IF @tp = 2
BEGIN
    INSERT INTO audit_trail (mod_type, mod_date, user_key, audit_rec_table, audit_rec_key, audit_rec_key_1, 
            audit_rec_key_2, is_delta, data_to)
    SELECT 'I', GETDATE(), @user_key, 'su_type_code', t.type_code_key, NULL, NULL, 0, audit_data
    FROM @i t
END ELSE IF @tp = 1
BEGIN
    INSERT INTO audit_trail (mod_type, mod_date, user_key, audit_rec_table, audit_rec_key, audit_rec_key_1, 
            audit_rec_key_2, is_delta, data_from)
    SELECT 'D', GETDATE(), @user_key, 'su_type_code', t.type_code_key, NULL, NULL, 0, audit_data
    FROM @d t
END ELSE
BEGIN
    INSERT INTO audit_trail (mod_type, mod_date, user_key, audit_rec_table, audit_rec_key, audit_rec_key_1, 
            audit_rec_key_2, is_delta, data_to, data_from)
    SELECT 'U', GETDATE(), @user_key, 'su_type_code', t.type_code_key, NULL, NULL, 0, t.audit_data, d.audit_data
    FROM @i t INNER JOIN @d d ON (t.type_code_key = d.type_code_key)
    WHERE ISNULL(t.audit_data, '') <> ISNULL(d.audit_data, '')
END
SET NOCOUNT OFF;

答案 2 :(得分:1)

我的工作解决方案是在表格中添加一个唯一的列,就像IDENTITY一样。然后只在该唯一列上加入DELETED和INSERTED。