我需要在sql 2005实例的每个数据库中创建一个触发器。我正在设置一些审计ddl触发器。
我创建一个包含所有数据库名称的游标,并尝试执行USE语句。这似乎并没有改变数据库 - CREATE TRIGGER语句只是在冒险作品中反复出现。另一个选项是使用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'
答案 0 :(得分:2)
当您使用EXEC()时,每次使用都在其自己的上下文中。因此,当您执行EXEC('USE MyDB')时,它会切换到MyDB以获取该上下文,然后命令结束,并且您回到了开始的位置。有几种可能的解决方案......
您可以使用数据库名称(例如,MyDB..sp_executesql)调用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语句打印出实际代码,然后运行生成的代码。
答案 1 :(得分:1)
您不必创建游标......
sp_msforeachdb 'USE ?; PRINT ''Hello ?'''
编辑:“使用?”部分是切换到指定的数据库...您可能希望放置一个IF语句,以确保数据库名称是您想要的。
答案 2 :(得分:0)
我要尝试的第一件事是将'USE'命令放在@command字符串中,尽管如果它抱怨触发器ddl必须是批处理中的第一个,那就不太可能了。
您是否可以访问Visual Studio?像C#控制台应用程序一样,编程速度相当快,只需要一个可以随时运行的exe文件。不像sql脚本那样透明,但你可以将源代码存储在它旁边。
答案 3 :(得分:0)
另一种可能的解决方案是在模型数据库中创建触发器,这样所有数据库都将继承。
答案 4 :(得分:0)
答案 5 :(得分:0)
这是一种更强大的力量,但您可以执行基本循环并打印出您想要执行的代码,然后在单独的步骤中执行代码。不需要不必要地跳过箍来使用sp_msforeachdb ...被授予的数据库数量可能会受到限制,这会使这种方法变得不切实际,但它可能会帮助某些使用较小数据库的人:
` - 首先获取数据库名称列表,过滤掉WHERE子句中不需要的任何内容:
select name
into #d
from sys.databases
- 然后,使用上面的列表,遍历并打印每个数据库的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