以下针对某个数据库在创建数据表时调用触发器,并将创建该数据表的用户账户写入到Windows的Event Log中。
1
2
3
4
5
6
7
|
CREATE TRIGGER reminder
ON DATABASE
FOR CREATE_TABLE
AS DECLARE @str NVARCHAR(100)
SET @str=suser_sname() + N 'create a new table'
RAISERROR(@str,10,1) WITH LOG
|
创建完DDL触发器后,因该触发器所在的等级,而会显示在“Object Explorer”中不同的位置,上述是创建数据库等级的触发器,因此,显示在某个数据库的“Programmability”中“Database Triggers”节点之下。
创建完DDL触发器后,请尝试使用下列语法创建数据表,由于该DDL触发器使用RAISERROR系统函数搭配WITH LOG选项,这会将信息写入到Windows操作系统的事件日志中。接着,可以在事件查看器中查询到事件内容。
由于触发器默认都是与引发该触发器的语法包在相同事务内一起执行,因此,我们可以通过ROLLBACK命令回滚先前指令对系统的影响,让用户的DROP_TABLE、ALTER_TABLE DDL语法无法在DB内执行。
1
2
3
4
5
6
|
CREATE TRIGGER safety
ON DATABASE
FOR DROP_TABLE, ALTER_TABLE
AS PRINT N 'Before drop or alter table,you should drop trigger safety!!!!'
ROLLBACK |
完成限制的DDL触发器之后,我们通过下列语法测试该触发器:
1
|
ALTER TABLE tblAbc ADD c2 INT
|
由于直接被触发器回滚(Rollback),SQL Server将返回错误信息。
另外,通过DDL触发器搭配EVENTDATA系统函数,经由SQL Server 2005所提供的XQuery语句的Query函数查询,取出想要的数据。再将所有DDL行为记录到另外一个数据表中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
USE [AdventureWorks2012] GO /****** Object: Table [dbo].[DatabaseLog] Script Date : 2014/12/31 11:33:19 ******/
SET ANSI_NULLS ON
GO SET QUOTED_IDENTIFIER ON
GO CREATE TABLE [dbo].[DatabaseLog](
[DatabaseLogID] [ int ] IDENTITY(1,1) NOT NULL ,
[PostTime] [datetime] NOT NULL ,
[DatabaseUser] [sysname] NOT NULL ,
[Event] [sysname] NOT NULL ,
[ Schema ] [sysname] NULL ,
[Object] [sysname] NULL ,
[TSQL] [nvarchar]( max ) NOT NULL ,
[XmlEvent] [xml] NOT NULL ,
CONSTRAINT [PK_DatabaseLog_DatabaseLogID] PRIMARY KEY NONCLUSTERED
( [DatabaseLogID] ASC
) WITH (PAD_INDEX = OFF , STATISTICS_NORECOMPUTE = OFF , IGNORE_DUP_KEY = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON ) ON [ PRIMARY ]
) ON [ PRIMARY ] TEXTIMAGE_ON [ PRIMARY ]
GO EXEC sys.sp_addextendedproperty @ name =N 'MS_Description' , @value=N 'Primary key for DatabaseLog records.' , @level0type=N 'SCHEMA' ,@level0name=N 'dbo' , @level1type=N 'TABLE' ,@level1name=N 'DatabaseLog' , @level2type=N 'COLUMN' ,@level2name=N 'DatabaseLogID'
GO EXEC sys.sp_addextendedproperty @ name =N 'MS_Description' , @value=N 'The date and time the DDL change occurred.' , @level0type=N 'SCHEMA' ,@level0name=N 'dbo' , @level1type=N 'TABLE' ,@level1name=N 'DatabaseLog' , @level2type=N 'COLUMN' ,@level2name=N 'PostTime'
GO EXEC sys.sp_addextendedproperty @ name =N 'MS_Description' , @value=N 'The user who implemented the DDL change.' , @level0type=N 'SCHEMA' ,@level0name=N 'dbo' , @level1type=N 'TABLE' ,@level1name=N 'DatabaseLog' , @level2type=N 'COLUMN' ,@level2name=N 'DatabaseUser'
GO EXEC sys.sp_addextendedproperty @ name =N 'MS_Description' , @value=N 'The type of DDL statement that was executed.' , @level0type=N 'SCHEMA' ,@level0name=N 'dbo' , @level1type=N 'TABLE' ,@level1name=N 'DatabaseLog' , @level2type=N 'COLUMN' ,@level2name=N 'Event'
GO EXEC sys.sp_addextendedproperty @ name =N 'MS_Description' , @value=N 'The schema to which the changed object belongs.' , @level0type=N 'SCHEMA' ,@level0name=N 'dbo' , @level1type=N 'TABLE' ,@level1name=N 'DatabaseLog' , @level2type=N 'COLUMN' ,@level2name=N 'Schema'
GO EXEC sys.sp_addextendedproperty @ name =N 'MS_Description' , @value=N 'The object that was changed by the DDL statment.' , @level0type=N 'SCHEMA' ,@level0name=N 'dbo' , @level1type=N 'TABLE' ,@level1name=N 'DatabaseLog' , @level2type=N 'COLUMN' ,@level2name=N 'Object'
GO EXEC sys.sp_addextendedproperty @ name =N 'MS_Description' , @value=N 'The exact Transact-SQL statement that was executed.' , @level0type=N 'SCHEMA' ,@level0name=N 'dbo' , @level1type=N 'TABLE' ,@level1name=N 'DatabaseLog' , @level2type=N 'COLUMN' ,@level2name=N 'TSQL'
GO EXEC sys.sp_addextendedproperty @ name =N 'MS_Description' , @value=N 'The raw XML data generated by database trigger.' , @level0type=N 'SCHEMA' ,@level0name=N 'dbo' , @level1type=N 'TABLE' ,@level1name=N 'DatabaseLog' , @level2type=N 'COLUMN' ,@level2name=N 'XmlEvent'
GO EXEC sys.sp_addextendedproperty @ name =N 'MS_Description' , @value=N 'Audit table tracking all DDL changes made to the AdventureWorks database. Data is captured by the database trigger ddlDatabaseTriggerLog.' , @level0type=N 'SCHEMA' ,@level0name=N 'dbo' , @level1type=N 'TABLE' ,@level1name=N 'DatabaseLog'
GO EXEC sys.sp_addextendedproperty @ name =N 'MS_Description' , @value=N 'Primary key (nonclustered) constraint' , @level0type=N 'SCHEMA' ,@level0name=N 'dbo' , @level1type=N 'TABLE' ,@level1name=N 'DatabaseLog' , @level2type=N 'CONSTRAINT' ,@level2name=N 'PK_DatabaseLog_DatabaseLogID'
GO |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
CREATE TRIGGER [ddlDatabaseTriggerLog] ON DATABASE
FOR DDL_DATABASE_LEVEL_EVENTS AS
BEGIN SET NOCOUNT ON ;
DECLARE @data XML;
DECLARE @ schema sysname;
DECLARE @object sysname;
DECLARE @eventType sysname;
SET @data = EVENTDATA();
SET @eventType = @data.value( '(/EVENT_INSTANCE/EventType)[1]' , 'sysname' );
SET @ schema = @data.value( '(/EVENT_INSTANCE/SchemaName)[1]' , 'sysname' );
SET @object = @data.value( '(/EVENT_INSTANCE/ObjectName)[1]' , 'sysname' )
IF @object IS NOT NULL
PRINT ' ' + @eventType + ' - ' + @ schema + '.' + @object;
ELSE PRINT ' ' + @eventType + ' - ' + @ schema ;
IF @eventType IS NULL
PRINT CONVERT (nvarchar( max ), @data);
INSERT [dbo].[DatabaseLog]
( [PostTime], [DatabaseUser], [Event], [ Schema ],
[Object], [TSQL], [XmlEvent] ) VALUES ( GETDATE(), CONVERT (sysname, CURRENT_USER ),
@eventType, CONVERT (sysname, @ schema ),
CONVERT (sysname, @object),
@data.value( '(/EVENT_INSTANCE/TSQLCommand)[1]' , 'nvarchar(max)' ),
@data ); END ;
GO |
1
2
3
4
|
CREATE TABLE TestTable(a int );
ALTER TABLE TestTable ADD b nvarchar(10);
DROP TABLE TestTable;
GO |
1
|
SELECT * FROM dbo.DatabaseLog ORDER BY DatabaseLogID;
|
若要删除存放触发器所产生log的数据表dbo.DatabaseLog时,必须先删除使用到这个数据表的触发器。否则会有奇怪的错误信息如下:
当然,你在删除任何对象前,可以先检查一下对象依赖关系,以确认是否可以删除。
创建具有主外键关机的表,来测试利用DDL触发器避免数据表被删除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
USE [AdventureWorks2012] GO /****** Object: Table [dbo].[Test] Script Date : 2014/12/31 15:10:19 ******/
SET ANSI_NULLS ON
GO SET QUOTED_IDENTIFIER ON
GO CREATE TABLE [dbo].[Test](
[id] [ int ] IDENTITY(1,1) NOT NULL ,
[DetailID] [ int ] NULL ,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED
( [id] ASC
) WITH (PAD_INDEX = OFF , STATISTICS_NORECOMPUTE = OFF , IGNORE_DUP_KEY = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON ) ON [ PRIMARY ]
) ON [ PRIMARY ]
GO /****** Object: Table [dbo].[TestDetail] Script Date : 2014/12/31 15:10:19 ******/
SET ANSI_NULLS ON
GO SET QUOTED_IDENTIFIER ON
GO SET ANSI_PADDING ON
GO CREATE TABLE [dbo].[TestDetail](
[ID] [ int ] IDENTITY(1,1) NOT NULL ,
[ Desc ] [ varchar ](50) NULL ,
CONSTRAINT [PK_TestDetail] PRIMARY KEY CLUSTERED
( [ID] ASC
) WITH (PAD_INDEX = OFF , STATISTICS_NORECOMPUTE = OFF , IGNORE_DUP_KEY = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON ) ON [ PRIMARY ]
) ON [ PRIMARY ]
GO SET ANSI_PADDING ON
GO ALTER TABLE [dbo].[Test] WITH CHECK ADD CONSTRAINT [FK_Test_DetailID] FOREIGN KEY ([DetailID])
REFERENCES [dbo].[TestDetail] ([ID])
GO ALTER TABLE [dbo].[Test] CHECK CONSTRAINT [FK_Test_DetailID]
GO |
搭配EventData系统函数与XQuery语句解析其内容,例如,以VALUE函数判断对象名称,而后决定是否允许用户删除。将safety触发器修改了下,如果用户删除的是dbo.Test数据表,才会出现错误信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
ALTER TRIGGER safety
ON DATABASE
FOR DROP_TABLE
AS DECLARE @data XML=EVENTDATA()
DECLARE @SchemaName nvarchar( max )
DECLARE @TableName nvarchar( max )
SET @SchemaName=EVENTDATA().value( '(/EVENT_INSTANCE/SchemaName)[1]' , 'sysname' )
SET @TableName=EVENTDATA().value( '(/EVENT_INSTANCE/ObjectName)[1]' , 'sysname' )
IF @SchemaName= 'dbo' AND @TableName= 'Test'
BEGIN DECLARE @msg NVARCHAR( MAX )=N 'You can' 't Delete the table: [' + @SchemaName + '].[' + @TableName + ']'
RAISERROR(@msg,16,1) ROLLBACK TRAN
END |
正常情况而言,数据表会被FOREIGN KEY条件约束保护,而无法删除,但是FOREIGN KEY约束只保护主数据表,这里对应dbo.TestDetail,而不保护参照数据表,这里对应dbo.Test。
利用XQuery中的Value,取得SchemaName以及ObjectName,再通过假设句判断是否为所要保护的对象,即可保护目标数据表。此时,若执行如下的语句:
1
|
DROP TABLE dbo.Test
|
将会得到如下的错误信息:
若要删除数据库级别的DDL触发器,只要参照如下语法即可:
1
2
|
DROP TRIGGER safety ON DATABASE
DROP TABLE dbo.Test
|
在删除DDL触发器时,要搭配ON DATABASE或ON ALL SERVER选项,否则SQL Server会以为要删除的是一般DML触发器,因此,会返回找不到对象的错误信息。
查看[ddlDatabaseTriggerLog]触发器记录的信息:
1
|
SELECT * FROM dbo.DatabaseLog ORDER BY DatabaseLogID
|
最后,显示EVENTDATA函数返回的XML内容如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
< EVENT_INSTANCE >
< EventType >DROP_TABLE</ EventType >
< PostTime >2014-12-31T15:30:01.010</ PostTime >
< SPID >53</ SPID >
< ServerName >WIN-LLPKR5BUV6S</ ServerName >
< LoginName >WIN-LLPKR5BUV6S\Administrator</ LoginName >
< UserName >dbo</ UserName >
< DatabaseName >AdventureWorks2012</ DatabaseName >
< SchemaName >dbo</ SchemaName >
< ObjectName >Test</ ObjectName >
< ObjectType >TABLE</ ObjectType >
< TSQLCommand >
< SetOptions ANSI_NULLS = "ON" ANSI_NULL_DEFAULT = "ON" ANSI_PADDING = "ON" QUOTED_IDENTIFIER = "ON" ENCRYPTED = "FALSE" />
< CommandText >DROP TABLE dbo.Test</ CommandText >
</ TSQLCommand >
</ EVENT_INSTANCE >
|
另外,SQL Server也针对触发器提供查询元数据的方式,以前述创建的DDL触发器为对象,查询语法如下:
1
2
3
|
SELECT * FROM sys.triggers WHERE name =’safety’
SELECT definition FROM sys.sql_modules
WHERE object_id=( SELECT object_id FROM sys.triggers WHERE name =’safety’)
|
本文转自UltraSQL51CTO博客,原文链接: http://blog.51cto.com/ultrasql/1598185,如需转载请自行联系原作者