【发布时间】:2020-12-03 16:07:35
【问题描述】:
从this article, which is creating a trigger to log insert-, update- and delete-statements within the database 开始,我想创建一个类似的触发器,它不需要定义表及其列。这将使我们在删除或添加列时减少人为错误。
我遇到了很多问题(因此与文章相比有额外的代码),但无法克服通过触发器中的字符串化名称访问表列的问题。
-- Create the ChangeLog table
CREATE TABLE ChangeLog
(
ID BIGINT PRIMARY KEY IDENTITY(1,1) NOT NULL,
COMMAND NCHAR(6) NOT NULL,
CHANGED_DATE DATETIME2 DEFAULT GETDATE() NOT NULL,
TABLE_NAME NVARCHAR(255) NOT NULL,
COLUMN_NAMES TEXT NULL,
COLUMN_OLD_VALUES TEXT NULL,
COLUMN_NEW_VALUES TEXT NULL,
USERNAME NVARCHAR(100) NOT NULL
)
GO
-- Create Trigger for Table to log changes
ALTER TRIGGER CHANGE_MyTableName
ON MyTableName
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
-- Define which command was executed
DECLARE @command CHAR(6)
SET @command =
CASE
WHEN EXISTS(SELECT * FROM inserted) AND EXISTS(SELECT * FROM deleted) THEN 'UPDATE'
WHEN EXISTS(SELECT * FROM inserted) THEN 'INSERT'
WHEN EXISTS(SELECT * FROM deleted) THEN 'DELETE'
ELSE NULL
END
-- Define variables
DECLARE @seperator NVARCHAR(2)
SET @seperator = ', '
DECLARE @column_name NVARCHAR(255)
DECLARE @column_names VARCHAR(MAX)
DECLARE @column_old_values VARCHAR(MAX)
DECLARE @column_new_values VARCHAR(MAX)
-- Select the column names to populate @column_names separated by ', '
SELECT @column_names = COALESCE(@column_names + @seperator, '') + COLUMN_NAME
FROM information_schema.columns
WHERE table_name = 'MyTableName'
-- Create cursor to populate @column_old_values or/and @column_new_values
DECLARE CURSOR_FOR_COLUMN_NAMES CURSOR FOR
-- Select the column name as a string
SELECT sys.columns.name AS ColumnName
FROM sys.columns JOIN sys.tables ON sys.columns.object_id = tables.object_id
WHERE tables.name = 'MyTableName'
-- Perform the first fetch.
OPEN CURSOR_FOR_COLUMN_NAMES
FETCH NEXT FROM CURSOR_FOR_COLUMN_NAMES INTO @column_name
WHILE @@FETCH_STATUS = 0
BEGIN
IF @command = 'UPDATE' or @command = 'DELETE'
-- Select the old values to populate @column_old_values separated by ', '
SELECT @column_old_values = COALESCE(@column_old_values + @seperator, '') + @column_name
FROM deleted
IF @command = 'UPDATE' or @command = 'INSERT'
-- Select the new values to populate @column_new_values separated by ', '
SELECT @column_new_values = COALESCE(@column_new_values + @seperator, '') + @column_name
FROM inserted
FETCH NEXT FROM CURSOR_FOR_COLUMN_NAMES INTO @column_name
END
CLOSE CURSOR_FOR_COLUMN_NAMES
DEALLOCATE CURSOR_FOR_COLUMN_NAMES
-- Insert into the ChangeLog table
IF @command = 'UPDATE'
INSERT INTO ChangeLog (COMMAND, CHANGED_DATE, TABLE_NAME, COLUMN_NAMES, COLUMN_OLD_VALUES, COLUMN_NEW_VALUES, USERNAME)
VALUES (@command, GETDATE(), 'MyTableName', @column_names, @column_old_values, @column_new_values, USER_NAME())
IF @command = 'INSERT'
INSERT INTO ChangeLog (COMMAND, CHANGED_DATE, TABLE_NAME, COLUMN_NAMES, COLUMN_NEW_VALUES, USERNAME)
VALUES (@command, GETDATE(), 'MyTableName', @column_names, @column_new_values, USER_NAME())
IF @command = 'DELETE'
INSERT INTO ChangeLog (COMMAND, CHANGED_DATE, TABLE_NAME, COLUMN_NAMES, COLUMN_OLD_VALUES, USERNAME)
VALUES (@command, GETDATE(), 'MyTableName', @column_names, @column_old_values, USER_NAME())
END
GO
当前代码为COLUMN_NAMES、COLUMN_OLD_VALUES 和COLUMN_NEW_VALUES 创建了一个具有相同值的行,因为填充@column_old_values 和@column_new_values 的代码实际上执行SELECT 'column_name' FROM AnyTable 而不是SELECT columnn_name FROM MyTableName 但是我似乎无法弄清楚如何解决这个问题。
我尝试使用 EXEC() 命令,但这似乎部分结束了我的触发器?即使代码编译,我仍然收到关于未闭合引号的错误。
消息 102,第 15 级,状态 1,第 16 行
'=' 附近的语法不正确。消息 105,级别 15,状态 1,第 16 行
字符串 ') + ColumnName1 FROM deleted' 后面的非右引号。
我认为这个通用触发器会在这个问题得到解决后立即起作用,但当然,我也会对替代解决方案感到满意。
【问题讨论】:
-
我真的建议不要使用该触发器,因为它仍然存在。使用
CURSOR几乎总是您在编写SQL 时应该做的最后一件事,并且将CURSOR放在TRIGGER中是降低任何影响表的DML 语句性能的可靠方法。如果您必须在触发器中执行任何操作,则它需要尽可能降低影响,并且当然需要基于设置;CURSOR不是这些东西。 -
...在
不可以时减少人为错误 - 执行影响分析是无可替代的。如果您认为您可以对 table 进行更改并期望所有依赖代码能够神奇地“调整”以适应更改,那么您只是在自欺欺人。要做到这一点,您需要在动态 tsql 方面有一些严肃的技能。混合使用 varchar 和 nvarchar 表明您需要帮助。使用 TEXT 数据类型是完全错误的。为什么不使用 CDC? -
当 SQL Server 提供开箱即用的功能时,您为什么要尝试从头开始编写此功能?看看“SQL Server 更改跟踪”
-
感谢大家的评论。现在,我决定采用公认的答案,因为这以一种优雅的方式解决了这个问题。没有
CURSORs 剩下并且混合类型已经减少(也改变了文本类型,因为inserted- 和`deleted-tables 不支持选择这些类型)。稍后我将研究开箱即用的功能,因为这看起来确实很有前景
标签: sql sql-server triggers database-cursor