创建触发器的问题。每个表中的列名必须是唯一的。微软SQL
Posted
技术标签:
【中文标题】创建触发器的问题。每个表中的列名必须是唯一的。微软SQL【英文标题】:Problem with creating trigger. Column names in each table must be unique. MsSQL 【发布时间】:2022-01-14 07:14:59 【问题描述】:我有这个触发器,它必须在 CREATE_TABLE
上工作,并且应该检查表中是否有列 updated_at
。如果没有,请创建一个并设置另一个触发器。
看起来是这样的:
CREATE TRIGGER UpdatedAtFiled ON DATABASE
FOR CREATE_TABLE
AS
BEGIN
DECLARE @createdTableName VARCHAR(50), @column VARCHAR(50), @triggerName VARCHAR(50), @execTrigger VARCHAR(300), @sqlcmd VARCHAR(100), @ColumnWasCreated INT, @Itr INT;
SET @Itr = 0;
SET @ColumnWasCreated = 0;
SELECT @createdTableName = name FROM sys.Tables ORDER BY create_date
WHILE (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @createdTableName) > @Itr
BEGIN
SELECT @column = COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @createdTableName ORDER BY COLUMN_NAME OFFSET @Itr ROWS FETCH NEXT 1 ROWS ONLY;
SET @triggerName = 'TRIGGER_' + CONVERT(VARCHAR(20), @createdTableName);
SET @execTrigger = 'CREATE TRIGGER ' + @triggerName + ' ON ' + @createdTableName +
' FOR UPDATE as UPDATE T SET updated_at = GETDATE() ' + ' FROM ' +
@createdTableName + ' AS T JOIN inserted AS i ON T.id = i.id;';
IF (@column != 'updated_at' AND @ColumnWasCreated <= 0)
SET @sqlcmd = 'ALTER TABLE ' + @createdTableName + ' ADD updated_at DATETIME NOT NULL DEFAULT GETDATE()';
EXEC(@sqlcmd)
EXEC(@execTrigger)
SET @ColumnWasCreated = 1;
SET @Itr = @Itr + 1;
END
END
每次创建表时都会出错:
CREATE TABLE test(id INT PRIMARY KEY IDENTITY (1, 1), name VARCHAR(20))
[S0004][2705] 第 1 行:每个表中的列名必须是唯一的。表“test”中的列名“updated_at”被指定了多次。`
我猜,这个条件有问题,但我不确定:
IF (@column != 'updated_at' AND @ColumnWasCreated <= 0)
我确实找不到错误。怎么了?我该如何解决?
【问题讨论】:
这几乎可以肯定是XY Problem。恕我直言,上述逻辑无权存在于TRIGGER
中。 TRIGGER
应该具有尽可能最小 的影响,而您却拥有WHILE
、和 动态 SQL。这种动态 SQL 也很容易注入。您真正想在这里实现什么目标?
我同意拉努的观点。至于错误,很可能是因为您的 INSERTED 表也将包含一个 updated_at 列。尝试更改为SET T.updated_at
。但是,如果您想要实现的只是为每个表添加一个 updated_at
列,请不要为此使用触发器,而应使用 while 循环本身。每次更新都会触发一个触发器。如果您需要在修改行时更新 updated_at
,请在 UPDATE 语句中执行此操作。
除了这似乎不是一个好主意之外,在sys.tables
上使用created_date
是识别已创建表的错误方法。而是使用the EVENTDATA
function 选择架构名称和表名称。
SELECT @SchemaName = EVENTDATA().value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname'), @TableName = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname');
在尝试创建将表连接到该列上的 inserted
表的触发器之前,您还需要检查表中是否包含名为 id
的列。
【参考方案1】:
我会先说我认为为此使用触发器是一个坏主意。对此有a number of other solutions,尤其是要确保您对中的每个表都正确执行此操作第一名。
尽管如此:
您应该在任何地方指定架构名称。 不要从sys.tables
中提取最新的表,而是使用EVENTDATA
来获取与执行的特定CREATE TABLE
命令相关的信息。
对象名最大可以是nvarchar(128)
,可以使用别名sysname
使用QUOTENAME
正确引用对象名称。
DML 触发器需要加入主键,主键可能不是id
,可能不止一列。
CREATE TRIGGER UpdatedAtFiled ON DATABASE
FOR CREATE_TABLE
AS
DECLARE @tableName sysname = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]','sysname');
DECLARE @schemaName sysname = EVENTDATA().value('(/EVENT_INSTANCE/SchemaName)[1]','sysname');
DECLARE @sql nvarchar(max);
IF NOT EXISTS (SELECT 1
FROM sys.columns c
JOIN sys.tables t ON t.object_id = c.object_id
JOIN sys.schemas s ON s.schema_id = t.schema_id
WHERE s.name = @schemaName
AND t.name = @tableName
AND c.name = 'updated_at')
BEGIN
SET @sql = '
ALTER TABLE ' + QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName) + '
ADD updated_at DATETIME NOT NULL DEFAULT GETDATE();
';
EXEC sp_executesql @sql;
END
SET @sql = '
CREATE TRIGGER ' + QUOTENAME('TRIGGER_' + @tableName) + ' ON ' + QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName) + '
FOR UPDATE AS
IF @@ROWCOUNT = 0
RETURN;
UPDATE T
SET updated_at = GETDATE()
FROM ' + QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName) + ' AS T
JOIN inserted AS i ON ('
+ (
SELECT STRING_AGG('T.' + QUOTENAME(c.name) + ' = i.' + QUOTENAME(c.name), ' AND ')
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.indexes i ON i.object_id = t.object_id
JOIN sys.index_columns ic ON ic.object_id = i.object_id
AND ic.index_id = i.index_id
JOIN sys.columns c ON t.object_id = c.object_id
AND c.column_id = ic.column_id
WHERE i.is_primary_key = 1
AND ic.is_included_column = 0
)
+ ');
';
EXEC sp_executesql @sql;
GO
【讨论】:
以上是关于创建触发器的问题。每个表中的列名必须是唯一的。微软SQL的主要内容,如果未能解决你的问题,请参考以下文章
每个表中的列名必须是唯一的。表“dbo.Foos”中的列名“StripeRecipientId”被指定多次