创建触发器的问题。每个表中的列名必须是唯一的。微软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 &lt;= 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”被指定多次

每个视图或函数中的列名必须是唯一的

内联函数,每个视图或函数中的列名必须唯一

MySQL触发器

如何解决sqlserver2005中用多表连接的结果建一张新表时,提示各表中的列名必须唯一的问题?

在 SQL 数据库中查找列名的所有唯一值