2010-07-09 81 views
1

我被要求爲數據庫中的每個表創建歷史表。然後創建一個觸發器,在主表更新時寫入歷史表。在TSQL觸發器中讀取插入的列名和值

歷史表中具有相同的結構與主表,但有幾個額外行的(「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' 

謝謝...

回答

1

'inserted'表是一個pseu做表;它不會出現在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()

+0

@ Beenay25的'inserted'表將具有相同的模式,它出現在'INFORMATION_SCHEMA'如果這是任何使用地址。 – 2010-07-09 14:41:49

+0

嗨米奇, 感謝,但我不知道是運行觸發前的列名會是什麼......這就是爲什麼我試圖讓信息從INFORMATION_SCHEMA 的 我得把觸發六十張桌子。我不想硬編碼每個列的名稱:( 我只是想重複使用相同的代碼(很明顯,不同的表名) – Beenay25 2010-07-09 14:46:26

+1

@ Beenay25:寫一個腳本來生成硬連接名稱的觸發器... – 2010-07-09 16:05:57

1

有一種方法做什麼的提問要求:我做了一些

在一個觸發器內部測試一個特定表的所有列是否實際參與了一個inser t到那張桌子。如果他們這樣做了,我後來將它們複製到歷史記錄表中。如果他們沒有,那麼回滾和打印只有完整的行可能會插入到報表中。也許他們能夠適應這個自己的需求:

那就是:

[ 

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 ['[email protected]_columnname+'] from #temp_for_dynamic_sql where ['[email protected]_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 < @actual_num_of_columns ..........

我這樣做是因爲我有一個sp,每次運行時都會向報表中插入一行完整行。沒關係,但我不想讓其他人誤觸這張桌子。甚至不是我自己。我也想保留歷史。所以我讓這個觸發器保留了歷史記錄,還檢查了是否嘗試了沒有值的報告表中的所有列的插入,並進一步向下檢查代碼是否嘗試更新或刪除並回滾。

我想擴大這個允許更新,但其中所有的列設置。 這可能被完成如下:

如果更新嘗試,

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 
2

通過Beenay25提出首先解決方案是好的,但你應該使用的,而不是「插入」 pseudotable受影響的表。

這就是:

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

而不是 '' 插入

此外,你應該使用動態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