sql-server - T-SQL 我的动态日志记录触发器正在工作 - 请解释为什么它不好用?
问题描述
我昨天发布了一个关于我的动态触发器的问题,结果我的上一个问题是由于在我的unpivot
. 话虽如此,我的创作正在工作!(邪恶的笑)
你们中的很多人表示担心这可能对我的数据库的整体健康状况不利,我很好奇为什么会这样……请在查看我的想法后详细说明。
这是一个可以放置在任何服务器上的任何表上的触发器(一旦你准备好必要的东西)。您要么需要遵循以下模型,要么根据自己的喜好对其进行调整。(即您自己的日志表、您自己跟踪更改者的方式等)
所有你需要的:触发器、一个日志表(见下面我的)和表中的一个字段,用于跟踪进行更改的用户(在我们的例子中,所有连接都通过一个集中的 SQL 配置文件,所以这是我们可以做到的唯一方法跟踪哪个用户进行了更改(通过在更新时将其作为参数传递)。
tblChangeLog:
- ID - int - NOT NULL (标识 (1, 1))
- 消息 - varchar(max) - NOT NULL
- 表名 - varchar(50) - 非空
- PrimaryKey - varchar(50) - 非空
- 活动 - varchar(50) - 非空
- CreatedByUser - varchar(30) - NOT NULL - DEFAULT ('System')
- CreatedDate - datetime - NOT NULL - DEFAULT (getdate())
触发器:(尚未添加删除/插入,可能仅用于更新)...
CREATE TRIGGER (your name here) ON (your table here)
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE @tableName sysname
DECLARE @tableId INT
DECLARE @activity VARCHAR(50)
DECLARE @sql nvarchar(MAX)
-- DETECT AN UPDATE (Records present in both inserted and deleted)
IF EXISTS(SELECT * FROM inserted) AND EXISTS(SELECT * FROM deleted)
BEGIN
SET @activity = 'UPDATE'
-- Gets TableName and TableId
SELECT @tableName = OBJECT_NAME(parent_object_id)
, @tableId = parent_object_id
FROM sys.objects
WHERE sys.objects.name = OBJECT_NAME(@@PROCID)
-- Get the user who made the change
DECLARE @LastUpdUser VARCHAR(50)
SELECT @LastUpdUser = LastUpdUser FROM inserted
-- Stores possible column names
CREATE TABLE #Columns (
name varchar(100)
)
-- Stores only updated columns
CREATE TABLE #Changes (
Id sql_variant,
FieldName sysname,
FieldValue_OLD sql_variant,
FieldValue_NEW sql_variant,
DateChanged datetime DEFAULT (GETDATE()),
LastUpdUser varchar(50),
GroupNumber int
)
-- Gathers names of all possible updated columns (excluding generic)
INSERT INTO #columns
SELECT Name
FROM sys.columns
WHERE object_id = @tableId
AND Name NOT IN ('LastUpdUser', 'LastUpdDate', 'CreatedByUser', 'CreatedDate', 'ConcurrencyId')
-- Builds 2 dynamic strings of columns to use in pivot
DECLARE @castFields nvarchar(max) -- List of columns being cast to sql_variant
DECLARE @listOfFields nvarchar(max) -- List of columns for unpivot
SELECT @castFields = COALESCE(@castFields + ', ', '') + ('CAST(' + QUOTENAME(Name) + ' AS sql_variant) [' + Name + ']') FROM #columns
SELECT @listOfFields = COALESCE(@listOfFields + ', ', '') + QUOTENAME(Name) FROM #columns WHERE Name <> 'Id'
-- Inserting deleted/inserted data into temp tables
SELECT * into #deleted FROM deleted
SELECT * into #inserted FROM inserted
SELECT @sql = ';WITH unpvt_deleted AS (
SELECT Id, FieldName, FieldValue
FROM
(SELECT ' + @castFields + '
FROM #deleted) p
UNPIVOT
(FieldValue FOR FieldName IN
(' + @listOfFields + ')
) AS deleted_unpivot
),
unpvt_inserted AS (
SELECT Id, FieldName, FieldValue
FROM
(SELECT ' + @castFields + '
FROM #inserted) p
UNPIVOT
(FieldValue FOR FieldName IN
(' + @listOfFields + ')
) AS inserted_unpivot
)
INSERT INTO #Changes (Id, FieldName, FieldValue_OLD, FieldValue_NEW, LastUpdUser, GroupNumber)
SELECT COALESCE(D.Id, I.Id) Id
, COALESCE(D.FieldName, I.FieldName) FieldName
, D.FieldValue AS FieldValue_OLD
, I.FieldValue AS FieldValue_NEW
, ''' + @LastUpdUser + '''
, DENSE_RANK() OVER(ORDER BY I.Id) AS GroupNumber
FROM unpvt_deleted D
FULL OUTER JOIN unpvt_inserted I ON D.Id = I.Id AND D.FieldName = I.FieldName
WHERE D.FieldValue <> I.FieldValue
DECLARE @i INT = 1
DECLARE @lastGroup INT
SELECT @lastGroup = MAX(GroupNumber) FROM #Changes
WHILE @i <= @lastGroup
BEGIN
DECLARE @Changes VARCHAR(MAX)
SELECT @Changes = COALESCE(@Changes + ''; '', '''')
+ UPPER(CAST(FieldName AS VARCHAR)) + '': ''
+ '''''''' + CAST(FieldValue_OLD AS VARCHAR) + '''''' to ''
+ '''''''' + CAST(FieldValue_NEW AS VARCHAR) + ''''''''
FROM #Changes
WHERE GroupNumber = @i
ORDER BY GroupNumber
INSERT INTO tblChangeLog (Message, TableName, PrimaryKey, Activity, CreatedByUser, CreatedDate)
SELECT Distinct @Changes, ''' + @tableName + ''', CONVERT(VARCHAR, Id), ''' + @activity + ''', LastUpdUser, DateChanged
FROM #Changes
WHERE GroupNumber = @i
SET @Changes = NULL
SET @i += 1
END
DROP TABLE #Changes
DROP TABLE #columns
DROP TABLE #deleted
DROP TABLE #inserted'
exec sp_executesql @sql
END
END
为了使其更加通用,我的 DBA 提出了一个漂亮的脚本和虚拟表,它将这个触发器作为一行存储在虚拟表中。该脚本使用游标查找数据库中的每个表,并为每个表动态创建触发器。我们也对此进行了测试,它就像一种魅力,此时无需在此处发布。
解决方案
我注意到的一些事情:
inserted
或中可以有多个记录deleted
。Sql Server 只会为一个语句调用一次触发器,即使它会影响许多行。应用程序可能是在极不可能的情况下构建的,但我已经看到这种情况多次在触发器中咬人。inserted
在处理时间和处理时间之间不保留订单deleted
。- 我会
sq_executesql
为LastUpdUser
. 我知道您不太可能遇到由组织分配的用户名引起的任何问题,但我总是对尽可能多地使用参数感觉更好,这可能在这里。也是如此@activity
。 - 表现。您对每一次更改都执行此操作,并且涉及大量字符串处理。Sql Server 中的字符串处理非常昂贵,因此这可能会产生有意义的性能损失。
- 为什么?此功能已内置在 Sql Server中,并且从 Sql Server 2016 sp2 开始,它包含在 Standard Edition 中(无需为此获取 Enterprise)。
推荐阅读
- javascript - MySQL值更改时如何播放声音
- c++ - Bind() Windows Socket 适用于 IPv4 但不适用于 IPv6
- linux - 无法将文件 rsync 到 AWS:没有这样的文件或目录
- c# - 在安装自己的包时自动安装第三方 NuGet 包
- php - 导入大量数据并返回而不存储
- ios - 编码新手,我的 segues 在模拟时不起作用
- excel - 工作表选择更改的错误处理程序
- java - Java Spring App - RequestParam 映射为我自己的对象
- python - Pygame不更新键盘事件,只更新鼠标
- javascript - 无法在反应中解构变量