用于捕获审计中更改的列的触发器 table

Trigger for capturing changed columns in audit table

我有 2 个 table,一个主 table 和一个审计 table。

create sequence dbo.users_seq;
create table dbo.users 
(
 id bigint primary key default(next value for dbo.users_seq),
 name varchar(100) not null, --user's full name
 user_data nvarchar(max) not null check(isjson(user_data) = 1),
 timestamp datetime2 not null default sysdatetime(),
 updated_timestamp datetime2 not null default sysdatetime()
);

create sequence dbo.users_audit_seq;
create table dbo.users_audit
(
 id bigint primary key default(next value for dbo.users_audit_seq),
 users_id bigint not null, --id from `users` table
 old nvarchar(max) not null check(isjson(old) = 1), --original row from `users` table
 new nvarchar(max) not null check(isjson(new) = 1), --new row from `users` table
 query varchar(max) not null, --query used for update
 updated_by varchar(100) not null, --username info
 timestamp datetime2 not null default sysdatetime()
);

我想在 users main table 上创建一个 after update 触发器,可用于捕获 users_audit [=62] 中更改的列(不包括时间戳) =]. (示例如下)

我可以通过 json_modify()OPENJSON(@json 手动执行此操作,但无法通过触发器自动执行此操作

初始插入:

id name user_data timestamp updated_timestamp
1 John {"email":"jdoe@abc.com"} 2021-05-08 18:10:02.0474381 2021-05-08 18:10:02.0474381

示例更新:

id name user_data timestamp updated_timestamp
1 John Doe {"email":"jdoe@abc.com","address":"123 Main St"} 2021-05-08 18:10:02.0474381 2021-05-08 18:12:06.0474381

经过上述更新审核后 table 应该如下所示:

id users_id old new query updated_by timestamp
1 1 {"name":"John","user_data":{"email":"jdoe@abc.com"}} {"name":"John Doe","user_data":{"email":"jdoe@abc.com","address":"123 Main St"}} update query username 2021-05-08 18:12:06.0474381

示例更新 2:

id name user_data timestamp updated_timestamp
1 John {"email":"jdoe@abc.com","address":"123 Main St"} 2021-05-08 18:10:02.0474381 2021-05-08 18:14:16.0474381

在上面的 update2 审计之后 table 应该是这样的: (oldnew 没有捕获 user_data 因为它没有改变)

id users_id old new query updated_by timestamp
1 1 {"name":"John","user_data":{"email":"jdoe@abc.com"}} {"name":"John Doe","user_data":{"email":"jdoe@abc.com","address":"123 Main St"}} update query username 2021-05-08 18:12:06.0474381
2 1 {"name":"John Doe"} {"name":"John"} update query username 2021-05-08 18:14:16.0474381

注意:时间 tablesSQL 审核 方法将不起作用

这是一种方法。

原理与之前回答中提到的基本相同。主要区别是:

  • 使用 dm_exec_input_buffer 获取起始批次。为此,您需要 server-level 权限。
  • FOR JSON 不会显示具有 NULL 值的键,因此我们可以使用 SELECT...EXCEPT 删除 inserted 和 [= 之间相同的值16=].
  • JSON_QUERY 是防止现有 JSON 个对象的 double-escaping 所必需的
CREATE OR ALTER TRIGGER TR_users ON users
AFTER UPDATE
AS

SET NOCOUNT ON;  -- prevent issues with bad client drivers

IF NOT EXISTS (SELECT 1 FROM inserted) AND NOT EXISTS (SELECT 1 FROM deleted)
    RETURN;  -- early bail-out
    
-- needs sa permissions
DECLARE @inputBuf nvarchar(max) /* = (
    SELECT b.event_info
    FROM sys.dm_exec_input_buffer(@@SPID, NULL) b
);*/

INSERT users_audit (users_id, old, new, query, updated_by)
SELECT
  i.id,
  (
      SELECT
          -- SELECT EXCEPT will null this out if they are the same
        name = (SELECT i.name EXCEPT SELECT d.name),
        user_data = JSON_QUERY((SELECT i.user_data EXCEPT SELECT d.user_data))
      FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
  ),
  (
      SELECT
        name = (SELECT d.name EXCEPT SELECT i.name),
        user_data = JSON_QUERY((SELECT d.user_data EXCEPT SELECT i.user_data))
      FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
  ),
  ISNULL(@inputBuf, ''),
  SUSER_SNAME()
FROM inserted i
JOIN deleted d ON d.id = i.id  -- join to match by all primary key columns
WHERE NOT EXISTS (
    SELECT i.name, i.user_data    -- add other columns here
    INTERSECT             -- because INTERSECT deals correctly with nulls
    SELECT d.name, d.user_data
);

go

db<>fiddle