我昨天发布了一个关于我的动态触发器的问题,事实证明我的最后一个问题是由于在我的unpivot
中包含了太多的字段。话虽这么说,我的创作正在发挥作用! (邪恶的笑)
很多人表达了你的担忧,这可能会对我的数据库的整体健康状况造成不利影响,我很好奇为什么会这样......请在回顾我的想法之后详细说明。
这是一个触发器,可以放在任何服务器上的任何表上(一旦你有必要的东西)。您需要按照下面的模型或根据自己的喜好调整它。 (即您自己的日志表,您自己跟踪谁做出更改的方式等)
您只需要:触发器,日志表(请参阅下面的内容),以及表中跟踪进行更改的用户的字段(在我们的示例中,所有连接都通过一个集中的SQL配置文件,因此这是我们唯一的方法跟踪哪个用户进行了更改(通过在更新时将其作为参数传递))。
tblChangeLog:
触发:(尚未添加删除/插入,可能只对更新执行此操作)...
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
也是如此。被认为是不好用的主要原因是,您要为每个事务添加大量额外写入。当所有这些步骤都不必要时,您正在写#inserted,#delete和#changes。
由于表结构是相当静态的,因此像这样的触发器应该是静态的。以动态方式为整个数据库创建触发器的想法是可以理解的,但是对这些触发器和表的支持应遵循适当的源代码控制。
当然,SQL注入是人们用动态代码提到的。但是,如果您有可以创建SQL注入的列名,那么您可能会遇到更大的问题。