读取SQL触发器中插入的列名和值

时间:2010-07-09 14:35:54

标签: tsql triggers

我被要求为数据库中的每个表创建历史表。然后创建一个触发器,只要主表更新,它就会写入历史表。

历史记录表与主表具有相同的结构,但有一些额外的行('id'和'update type')

我以前从未对触发器做过任何事情,但我想做的是动态浏览'Inserted'中的列并构造一个insert语句来填充历史表。

但是我无法弄清楚如何读取列的名称及其各个值。

我的半完成触发器目前看起来像......

CREATE TRIGGER tr_address_history
ON address
FOR UPDATE
AS

DECLARE @colCount int
DECLARE @maxCols int
SET @colCount = 0
SET @maxCols = (SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'Inserted')

PRINT 'Number of columns = ' + CONVERT(varChar(10),@maxCols)
WHILE (@colCount <= @maxCols)
BEGIN
    DECLARE @name varchar(255)
    SELECT @name = column_name FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'Inserted'
    DECLARE @value varchar(255)
    SELECT @value = @name FROM Inserted

    PRINT 'name = ' + @name + ' and value = ' + @value
    SET @colCount = @colCount + 1
END
PRINT 'Done';

当触发器运行时,它只是说“列数= 0”

任何人都可以告诉我出了什么问题:

SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'Inserted'

...谢谢

3 个答案:

答案 0 :(得分:2)

Beenay25提出的第一个解决方案很好,但你应该使用受影响的表而不是'inserted'pseigotable。

这是:

SELECT @name = column_name FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'AFFECTED_TABLE'

而不是'INSERTED'

此外,您应该使用动态SQL。

这将是一个完整的解决方案:

ALTER TRIGGER [dbo].[tr_address_history]
ON [dbo].[address]
AFTER Insert
AS

DECLARE @ColumnName nvarchar(500)
DECLARE @TableName nvarchar(500)
DECLARE @value nvarchar(500)
DECLARE @Sql nvarchar(500)

Set @TableName='address'

DECLARE ColumnsCursor CURSOR FOR
select column_name FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = 'address'

OPEN ColumnsCursor
FETCH NEXT FROM ColumnsCursor into @ColumnName

WHILE @@FETCH_STATUS=0
BEGIN 

      select * into #tmp from inserted
      Set @Sql= 'SELECT @value =' + @ColumnName + ' FROM #tmp'

      EXEC sp_executesql @Sql, N'@Value nvarchar(500) OUTPUT', @Value OUTPUT

      DROP TABLE #TMP

      print '[' + @ColumnName +'='+ ltrim(rtrim(@Value))+']'

      FETCH NEXT FROM ColumnsCursor into @ColumnName
END   

CLOSE ColumnsCursor
DEALLOCATE ColumnsCursor

答案 1 :(得分:1)

'inserted'表是伪表;它没有出现在INFORMATION_SCHEMA中。

在触发器中使用UPDATE()运算符:

CREATE TRIGGER trigger_name ON tablename
FOR UPDATE
AS
SET NOCOUNT ON
IF (UPDATE(Column1) OR UPDATE(Column2))
BEGIN
  your sql here
END

COLUMNS_UPDATED

UPDATE()

答案 2 :(得分:1)

有一种方法可以完成提问者的要求:

我在触发器中做了一些东西,测试特定表的所有列是否实际参与了该表的插入。如果他们这样做,我后来将它们复制到历史表中。如果他们没有,则回滚和打印只有完整的行可以插入到报表中。也许他们可以根据自己的需求进行调整:

这里是:

    [

if exists (select 1 from inserted) and not exists (select 1 from deleted) -- if an insert has been performed
begin -- and we want to test whether all the columns in the report table were included in the insert
declare @inserted_columncount int, @actual_num_of_columns int, @loop_columns int, @current_columnname nvarchar(300),
    @sql_test nvarchar(max), @params nvarchar(max), @is_there bit
set @actual_num_of_columns = (
    select count(*) from (
    select COLUMN_NAME
    from INFORMATION_SCHEMA.COLUMNS
    where TABLE_NAME = 'renameFilesFromTable_report') as z)
set @inserted_columncount = 0
set @loop_columns = 1
declare inserted_columnnames cursor scroll for -- these are not really the inserted ones, but we are going to test them 1 by 1
    select COLUMN_NAME
    from INFORMATION_SCHEMA.COLUMNS
    where TABLE_NAME = 'renameFilesFromTable_report'
set @params = '@is_there_in bit output'
open inserted_columnnames
fetch next from inserted_columnnames into @current_columnname
select * into #temp_for_dynamic_sql from inserted -- this is necessary because the scope of sp_executesql does not include inserted pseudo table
while (@loop_columns <= @actual_num_of_columns) -- looping with independent integer arithmetic
begin
set @sql_test = '
set @is_there_in = 0
if exists (select ['+@current_columnname+'] from #temp_for_dynamic_sql where ['+@current_columnname+'] is not null)
set @is_there_in = 1'
exec sp_executesql @sql_test, @params, @is_there output
if @is_there = 1
begin
fetch next from inserted_columnnames into @current_columnname
set @inserted_columncount = @inserted_columncount + 1
set @loop_columns = @loop_columns + 1
end
else if @is_there <> 1
begin
fetch next from inserted_columnnames into @current_columnname
set @loop_columns = @loop_columns + 1
end
end 
close inserted_columnnames
deallocate inserted_columnnames
-- at this point we hold in two int variables the number of columns participating in the insert and the total number of columns

    ]

然后你可以简单地做@inserted_columncount&lt; @actual_num_of_columns ..........

我这样做是因为我有一个sp,每次运行时都会在报告表中插入1条完整的行。那没关系,但我不希望别人误触摸那张桌子。甚至不是我自己。我也想保留历史。因此,我使用此触发器来保留历史记录,同时检查是否尝试插入而没有报告表中所有列的值,并进一步检查是否尝试更新或删除并回滚的代码。

我正在考虑扩展它以允许更新但是其中设置了所有列。 这可以按如下方式完成:

如果尝试更新,

and exists (
select possibly_excluded.COLUMN_NAME from (
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'renameFilesFromTable_report') as possibly_excluded
group by possibly_excluded.COLUMN_NAME
having COLUMN_NAME not in (
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'renameFilesFromTable_report' and
sys.fn_IsBitSetInBitmask(@ColumnsUpdated, COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + TABLE_NAME), COLUMN_NAME, 'ColumnID')) <> 0)
)
begin
rollback transaction
print 'Only updates that set the values for a complete row are allowed on the report table..'
end