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

发布于 2024-09-08 16:15:28 字数 1065 浏览 3 评论 0原文

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

历史表与主表具有相同的结构,但有一些额外的行(“id”和“更新类型”),

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

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

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

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'

谢谢......

I've been asked to create history tables for every table in a database. Then create a trigger that will write to the history table whenever the primary table is updated.

The history tables have the same structure as the primary table, but with a couple of extra rows ('id' and 'update type')

I've never done anything with triggers before, but I would like to do is dynamically go through the columns in 'Inserted' and construct an insert statement to populate the history table.

However I cannot work out how to read the names of the columns and their individual values.

My half finished trigger currently looks like...

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';

When the trigger runs it just says "Number of columns = 0"

Can anyone tell me what's wrong with :

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

Thanks...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

一袭白衣梦中忆 2024-09-15 16:15:28

Beenay25 提出的第一个解决方案很好,但您应该使用受影响的表而不是“插入”伪表。

这是:

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

First solution proposed by Beenay25 is good, but you should use affected table instead of 'inserted' pseudotable.

This is:

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

Instead of 'INSERTED'

Also, you should use dynamic SQL.

This will be a complete working solution:

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
零度℉ 2024-09-15 16:15:28

'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()

The 'inserted' table is a pseudo-table; it doesn't appear in INFORMATION_SCHEMA.

There is the UPDATE() operator for use in triggers:

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()

回首观望 2024-09-15 16:15:28

有一种方法可以满足提问者的要求:

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

这里是:

    [

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

    ]

然后您可以简单地执行 if @inserted_columncount < @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

There is a way to do what the questioner requires:

I have made something inside a trigger that tests whether all the columns of a particular table actually participated in an insert to that table. If they did, I later copied them to a history table. If they did not, then rollback and print only complete rows may be inserted into the report table. Perhaps they could adapt this to their needs:

here it is:

    [

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

    ]

Then you can simply do if @inserted_columncount < @actual_num_of_columns ..........

I did this because i have a sp that inserts 1 complete line to the report table every time it runs. That's fine, but i don't want anyone else touching that table by mistake. not even myself. I also want to keep history. So i made this trigger to keep the history but also to check if an insert was attempted without values for all the columns in the report table, and further down the code it checks if an update or delete was attempted and it rollbacks.

i was thinking of expanding this to allow an update but in which all the columns are set.
this could possibly be done as follows:

if update was attempted,

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文