SQL Server DDL 觸發器(Trigger)-- 創建數據庫級別的DDL觸發器

SQL Server DDL 觸發器(Trigger)-- 創建數據庫級別的DDL觸發器

 

以下針對某個數據庫在創建數據表時調用觸發器,並將創建該數據表的用戶賬戶寫入到Windows的Event Log中。

 

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”節點之下。

 

clip_image001

 

創建完DDL觸發器後,請嘗試使用下列語法創建數據表,由於該DDL觸發器使用RAISERROR系統函數搭配WITH LOG選項,這會將信息寫入到Windows操作系統的事件日誌中。接着,可以在事件查看器中查詢到事件內容。

 

clip_image002

 

由於觸發器默認都是與引發該觸發器的語法包在相同事務內一起執行,因此,我們可以通過ROLLBACK命令回滾先前指令對系統的影響,讓用戶的DROP_TABLE、ALTER_TABLE DDL語法無法在DB內執行。

 

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觸發器之後,我們通過下列語法測試該觸發器:

 

ALTER TABLE tblAbc ADD c2 INT

 

由於直接被觸發器回滾(Rollback),SQL Server將返回錯誤信息。

 

clip_image003

 

另外,通過DDL觸發器搭配EVENTDATA系統函數,經由SQL Server 2005所提供的XQuery語句的Query函數查詢,取出想要的數據。再將所有DDL行爲記錄到另外一個數據表中。

 

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

 

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

 

CREATE TABLE TestTable(a int);
ALTER TABLE TestTable ADD b nvarchar(10);
DROP TABLE TestTable;
GO

 

SELECT * FROM dbo.DatabaseLog ORDER BY DatabaseLogID;

 

clip_image004

 

若要刪除存放觸發器所產生log的數據表dbo.DatabaseLog時,必須先刪除使用到這個數據表的觸發器。否則會有奇怪的錯誤信息如下:

 

clip_image005

 

當然,你在刪除任何對象前,可以先檢查一下對象依賴關係,以確認是否可以刪除。

 

創建具有主外鍵關機的表,來測試利用DDL觸發器避免數據表被刪除。

 

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數據表,纔會出現錯誤信息。

 

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。

 

clip_image006

 

利用XQuery中的Value,取得SchemaName以及ObjectName,再通過假設句判斷是否爲所要保護的對象,即可保護目標數據表。此時,若執行如下的語句:

 

DROP TABLE dbo.Test

 

將會得到如下的錯誤信息:

 

clip_image007

 

若要刪除數據庫級別的DDL觸發器,只要參照如下語法即可:

 

DROP TRIGGER safety ON DATABASE
DROP TABLE dbo.Test

 

在刪除DDL觸發器時,要搭配ON DATABASE或ON ALL SERVER選項,否則SQL Server會以爲要刪除的是一般DML觸發器,因此,會返回找不到對象的錯誤信息。

 

查看[ddlDatabaseTriggerLog]觸發器記錄的信息:

 

SELECT * FROM dbo.DatabaseLog ORDER BY DatabaseLogID

 

clip_image008

 

最後,顯示EVENTDATA函數返回的XML內容如下。

 

<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觸發器爲對象,查詢語法如下:

 

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’)

 

clip_image009



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章