2008-12-02 26 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 
相关问题