2008-12-02 30 views
1

我需要在我的sql 2005實例的每個數據庫中創建一個觸發器。我正在設置一些審計ddl觸發器。在2005實例的每個數據庫中創建一個DDL觸發器

我創建一個包含所有數據庫名稱的遊標,並嘗試執行USE語句。這似乎沒有改變數據庫 - CREATE TRIGGER語句只是在adventureworks中反覆激發。另一個選項是在觸發器對象前加上databasename.dbo.triggername。這也不起作用 - 在創建觸發器方面存在某種限制。當然,我可以手動執行此操作,但我更願意通過腳本輕鬆進行應用和刪除操作。我有其他選擇,如果我不能在1 sql腳本中做到這一點,但我想保持簡單:)

這是我迄今爲止 - 希望你可以找到一個骨頭錯誤!

--setup stuff...  
CREATE DATABASE DBA_AUDIT 
GO 
USE DBA_AUDIT 
GO 
CREATE TABLE AuditLog 
(ID  INT PRIMARY KEY IDENTITY(1,1), 
Command NVARCHAR(1000), 
PostTime DATETIME, 
HostName NVARCHAR(100), 
LoginName NVARCHAR(100) 
) 
GO 

CREATE ROLE AUDITROLE 
GO 

sp_adduser 'guest','guest','AUDITROLE' 
GO 

GRANT INSERT ON SCHEMA::[dbo] 
TO AUDITROLE 

--CREATE TRIGGER IN ALL NON SYSTEM DATABASES 

DECLARE @dataname varchar(255), 
@dataname_header varchar(255), 
@command VARCHAR(MAX), 
@usecommand VARCHAR(100) 
SET @command = ''; 

--get the list of database names 

DECLARE datanames_cursor CURSOR FOR SELECT name FROM sys.databases 
WHERE name not in ('master', 'pubs', 'tempdb', 'model','msdb') 

OPEN datanames_cursor 

FETCH NEXT FROM datanames_cursor INTO @dataname 
WHILE (@@fetch_status = 0) 
BEGIN 

PRINT '----------BEGIN---------' 

PRINT 'DATANAME variable: ' + @dataname; 

EXEC ('USE ' + @dataname); 

PRINT 'CURRENT db: ' + db_name(); 

SELECT @command = 'CREATE TRIGGER DBA_Audit ON DATABASE 
FOR DDL_DATABASE_LEVEL_EVENTS 
AS 
DECLARE @data XML 
DECLARE @cmd NVARCHAR(1000) 
DECLARE @posttime NVARCHAR(24) 
DECLARE @spid NVARCHAR(6) 
DECLARE @loginname NVARCHAR(100) 
DECLARE @hostname NVARCHAR(100) 
SET @data = EVENTDATA() 
SET @cmd = @data.value(''(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]'', ''NVARCHAR(1000)'') 
SET @cmd = LTRIM(RTRIM(REPLACE(@cmd,'''',''''))) 
SET @posttime = @data.value(''(/EVENT_INSTANCE/PostTime)[1]'', ''DATETIME'') 
SET @spid = @data.value(''(/EVENT_INSTANCE/SPID)[1]'', ''nvarchar(6)'') 
SET @loginname = @data.value(''(/EVENT_INSTANCE/LoginName)[1]'', 
    ''NVARCHAR(100)'') 
SET @hostname = HOST_NAME() 
INSERT INTO [DBA_AUDIT].dbo.AuditLog(Command, PostTime,HostName,LoginName) 
VALUES(@cmd, @posttime, @hostname, @loginname);' 

EXEC (@command); 
FETCH NEXT FROM datanames_cursor INTO @dataname; 

PRINT '----------END---------' 

END 
CLOSE datanames_cursor 
DEALLOCATE datanames_cursor 

OUTPUT: 
----------BEGIN--------- 
DATANAME variable: adventureworks 
CURRENT db: master 
Msg 2714, Level 16, State 2, Procedure DBA_Audit, Line 18 
There is already an object named 'DBA_Audit' in the database. 
----------END--------- 
----------BEGIN--------- 
DATANAME variable: SQL_DBA 
CURRENT db: master 
Msg 2714, Level 16, State 2, Procedure DBA_Audit, Line 18 
There is already an object named 'DBA_Audit' in the database. 
----------END--------- 

編輯: 我已經嘗試了sp_msforeachdb方法

Msg 111, Level 15, State 1, Line 1 
'CREATE TRIGGER' must be the first statement in a query batch. 

編輯:

這是我最後的代碼 - 這個確切的腳本尚未經過測試,但它在生產中大約有100個左右的數據庫。乾杯!

一個注意事項 - 您的數據庫需要處於兼容模式(每個數據庫的選項爲90),否則您可能會出現錯誤。聲明的EXECUTE AS部分中的帳戶也需要訪問權限才能插入到管理表中。

USE [SQL_DBA] 
GO 
/****** Object: Table [dbo].[DDL_Login_Log] Script Date: 03/03/2009 17:28:10 ******/ 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE TABLE [dbo].[DDL_Login_Log](
    [DDL_Id] [int] IDENTITY(1,1) NOT NULL, 
    [PostTime] [datetime] NOT NULL, 
    [DB_User] [nvarchar](100) NULL, 
    [DBName] [nvarchar](100) NULL, 
    [Event] [nvarchar](100) NULL, 
    [TSQL] [nvarchar](2000) NULL, 
    [Object] [nvarchar](1000) NULL, 
CONSTRAINT [PK_DDL_Login_Log] PRIMARY KEY CLUSTERED 
(
    [DDL_Id] ASC, 
    [PostTime] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 
-------------------------------------------------------------------------------- 
-------------------------------------------------------------------------------- 
--This creates the trigger on the model database so all new DBs get it 
USE [model] 
GO 
/****** Object: DdlTrigger [ddl_DB_User] Script Date: 03/03/2009 17:26:13 ******/ 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE TRIGGER [ddl_DB_User] 
ON DATABASE 
FOR DDL_DATABASE_SECURITY_EVENTS 
AS 

DECLARE @data XML 
declare @user nvarchar(100) 

SET @data = EVENTDATA() 
select @user = convert(nvarchar(100), SYSTEM_USER) 

execute as login='domain\sqlagent' 
INSERT sql_dba.dbo.DDL_Login_Log 
    (PostTime, DB_User, DBName, Event, TSQL,Object) 
    VALUES 
    (@data.value('(/EVENT_INSTANCE/PostTime)[1]', 'nvarchar(100)'), 
    @user, 
    db_name(), 
    @data.value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(100)'), 
    @data.value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)'), 
    @data.value('(/EVENT_INSTANCE/ObjectName)[1]', 'nvarchar(1000)') 
) 

GO 
SET ANSI_NULLS OFF 
GO 
SET QUOTED_IDENTIFIER OFF 
GO 


-------------------------------------------------------------------------------- 
-------------------------------------------------------------------------------- 
--CREATE TRIGGER IN ALL NON SYSTEM DATABASES 

DECLARE @dataname varchar(255), 
@dataname_header varchar(255), 
@command VARCHAR(MAX), 
@usecommand VARCHAR(100) 
SET @command = ''; 
DECLARE datanames_cursor CURSOR FOR SELECT name FROM sys.databases 
WHERE name not in ('master', 'pubs', 'tempdb', 'model','msdb') 
OPEN datanames_cursor 
FETCH NEXT FROM datanames_cursor INTO @dataname 
WHILE (@@fetch_status = 0) 
BEGIN 

PRINT '----------BEGIN---------' 

PRINT 'DATANAME variable: ' + @dataname; 

EXEC ('USE ' + @dataname); 

PRINT 'CURRENT db: ' + db_name(); 

SELECT @command = 'CREATE TRIGGER DBA_Audit ON DATABASE 
FOR DDL_DATABASE_LEVEL_EVENTS 
AS 
DECLARE @data XML 
DECLARE @cmd NVARCHAR(1000) 
DECLARE @posttime NVARCHAR(24) 
DECLARE @spid NVARCHAR(6) 
DECLARE @loginname NVARCHAR(100) 
DECLARE @hostname NVARCHAR(100) 
SET @data = EVENTDATA() 
SET @cmd = @data.value(''(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]'', ''NVARCHAR(1000)'') 
SET @cmd = LTRIM(RTRIM(REPLACE(@cmd,'''',''''))) 
SET @posttime = @data.value(''(/EVENT_INSTANCE/PostTime)[1]'', ''DATETIME'') 
SET @spid = @data.value(''(/EVENT_INSTANCE/SPID)[1]'', ''nvarchar(6)'') 
SET @loginname = @data.value(''(/EVENT_INSTANCE/LoginName)[1]'', 
    ''NVARCHAR(100)'') 
SET @hostname = HOST_NAME() 
INSERT INTO [DBA_AUDIT].dbo.AuditLog(Command, PostTime,HostName,LoginName) 
VALUES(@cmd, @posttime, @hostname, @loginname);' 

EXEC (@command); 
FETCH NEXT FROM datanames_cursor INTO @dataname; 
PRINT '----------END---------' 
END 
CLOSE datanames_cursor 
DEALLOCATE datanames_cursor 

-------------------------------------------------------------------------------- 
-------------------------------------------------------------------------------- 

----Disable all triggers when things go haywire 
sp_msforeachdb @command1='use [?]; IF EXISTS (SELECT * FROM sys.triggers WHERE name = N''ddl_DB_User'' AND parent_class=0)disable TRIGGER [ddl_DB_User] ON DATABASE' 

回答

2

當你使用EXEC()時,每個使用都在它自己的上下文中。所以,當你執行EXEC('USE MyDB')時,它會切換到MyDB,然後命令結束,你又回到了你開始的地方。有幾個可能的解決方案......

你可以調用sp_executesql的與數據庫名稱(例如,MyDB..sp_executesql),它會在該數據庫中運行。訣竅是讓你這樣做動態的,所以你基本上把它包兩次像這樣:

DECLARE @cmd NVARCHAR(2000), @my_db VARCHAR(255) 

SET @my_db = 'MyDatabaseName' 

SET @cmd = 'DECLARE @my_cmd NVARCHAR(2000); SET @my_cmd = ''CREATE TRIGGER DBA_Audit ON DATABASE 
FOR DDL_DATABASE_LEVEL_EVENTS 
AS 
DECLARE @data XML 
DECLARE @cmd NVARCHAR(1000) 
DECLARE @posttime NVARCHAR(24) 
DECLARE @spid NVARCHAR(6) 
DECLARE @loginname NVARCHAR(100) 
DECLARE @hostname NVARCHAR(100) 
SET @data = EVENTDATA() 
SET @cmd = @data.value(''''(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]'''', ''''NVARCHAR(1000)'''') 
SET @cmd = LTRIM(RTRIM(REPLACE(@cmd,'''''''',''''''''))) 
SET @posttime = @data.value(''''(/EVENT_INSTANCE/PostTime)[1]'''', ''''DATETIME'''') 
SET @spid = @data.value(''''(/EVENT_INSTANCE/SPID)[1]'''', ''''nvarchar(6)'''') 
SET @loginname = @data.value(''''(/EVENT_INSTANCE/LoginName)[1]'''', 
    ''''NVARCHAR(100)'''') 
SET @hostname = HOST_NAME() 
INSERT INTO [DBA_AUDIT].dbo.AuditLog(Command, PostTime,HostName,LoginName) 
VALUES(@cmd, @posttime, @hostname, @loginname);''; EXEC ' + @my_db + '..sp_executesql @my_cmd' 

EXEC (@cmd) 

另一種選擇是做這個的過程分爲兩個步驟,其中第一步驟生成並打印出實際的代碼用USE語句和全部,然後你運行生成的代碼。

+0

GO由SSMS解釋,所以我認爲這不會起作用。但也許我做錯了:'USE'+ @dataname +'GO CREATE Trigger ....'。我添加了分號無濟於事。沒有去,我回到'創建觸發器必須是第一個聲明...' – Sam 2008-12-02 17:46:36

1

您不必創建一個遊標...

sp_msforeachdb 'USE ?; PRINT ''Hello ?''' 

編輯: 「使用?」部分是切換到指定的數據庫......你可能想要放置一個IF語句來確保數據庫名稱是你想要的。

+0

我已經試過這個ap但是觸發器必須是批次中的第一條語句。此外,這是一個無證的程序 - 我寧願創造一些合法的東西。 – Sam 2008-12-02 17:13:28

+0

sp_msforeachdb是合法的...它甚至在SQL Server Management Studio中突出顯示紅色。它由微軟作爲其產品的一部分編寫而成,並且已經存在了十多年,並且將繼續保持爲「 – 2008-12-03 02:55:36

+0

」,並且已經存在了十多年,並將繼續「 - 我不會這麼認爲。你可能會希望如此,但這並不是真的:)如果他們把它拿走,你總是可以自己寫。 – Sam 2011-03-17 23:18:41

0

我想嘗試的第一件事就是把「使用」命令你@command字符串中但如果它的抱怨是,DDL觸發器必須先在批,這是不太可能的工作。

你有機會到Visual Studio?這將是相當快的代碼中像一個C#控制檯應用程序,讓你可以在任何時候運行exe文件。不像SQL腳本那樣透明,但可以將源代碼放在一邊。

0

另一個可能的解決方案可能是在模型數據庫中創建觸發器,這樣所有的數據庫都會以繼承它。

0

感謝這個偉大的職位。 我只是漫步,你可以採取一種更簡單的方法以這樣的方式

CREATE TRIGGER trgMonitorNewDB適用於所有服務器FOR OBJECT_ACTION

又見link text

0

這是一個有點蠻力,但你可以做一個基本的循環並打印出你想要執行的代碼,然後在一個單獨的步驟中執行代碼。沒有必要不必要地跳過這些箍來使用sp_msforeachdb ...授予可能會限制數據庫的數量,這將使這種方法不切實際,但它可能有助於某人使用一組較小的數據庫:

` - 第一個抓住你的數據庫名稱列表,過濾掉你不希望在任何WHERE子句:

select name 
into #d 
from sys.databases 

--Then,通過使用列表上面,循環和打印爲CREATE TRIGGER CODE每個數據庫:

DECLARE @dbname varchar(100) 
DECLARE @Trig VARCHAR(max) 
select @dbname = (select top 1 name FROM #d order by name asc) 

WHILE @dbname IS NOT NULL 
BEGIN 
SET @Trig = 'USE ' + @dbname +'; 
GO 

CREATE TRIGGER [DBA_KillerTrigger] 
ON DATABASE 
AFTER DDL_DATABASE_LEVEL_EVENTS 
AS 
/*...Trigger magic goes here...*/ 
GO 

ENABLE TRIGGER [DBA_KillerTrigger] ON DATABASE 
GO 


PRINT @Trig 

SELECT @dbname = (select top 1 name FROM #d where name > @dbname order by name asc) 

END 
相關問題