Rit*_*kre 2 trigger sql-server audit t-sql ddl
给定与 DDL 命令对应的字符串,我如何确定它是哪个 DDL 命令(列添加、约束添加等)?
我在数据库上有一个 DDL 触发器,只要数据库中的任何表被更改,就会触发它;系统表将记录在数据库中的任何表上触发的 DDL 命令。
所以命令看起来像:
1. ALTER TABLE [dbo.table] ADD [column] VARCHAR(20) NULL;
2. ALTER TABLE [dbo.table] ADD [column] VARCHAR(20) NULL CONSTRAINT [cons] UNIQUE;
3. ALTER TABLE [dbo.table] WITH NOCHECK ADD CONSTRAINT [cons] CHECK ([column] > 1);
4. ALTER TABLE [dbo.table] WITH NOCHECK ADD [column1] VARCHAR(20) NULL,
CONSTRAINT [cons] CHECK ([column] > 1);
Run Code Online (Sandbox Code Playgroud)
以上4条DDL语句有什么办法辨别吗?我只对将新列添加到表中的 DDL 感兴趣,并且希望仅检索此新列的名称和数据类型。
到目前为止,我一直在使用诸如SUBSTRING()
, 之类的函数LEN()
,CHARINDEX()
但运气不佳。
附言。我的触发器能够获取正在执行 DDL 的表的名称。
触发器将简单地将上述 4 个选项之一作为输入,并且需要区分何时发生了列更改,并且只检索新列名及其对应的数据类型。
在对表进行更改之后,触发器将被触发,这个确切的更改命令被记录在一个系统表中,作为我用来跟踪更改的 CDC 功能的一部分(限制我对命令的访问)。这个 CDC 系统表还使我能够检索已进行更改的表的名称,因此我有另一个关键字,即我从 ALTER 命令知道的表名称。
可用于 DDL 触发器的事件列表可在以下 MSDN 页面上找到:DDL 事件组。如果您查看该列表,您会注意到它们没有提供低于基本 CREATE / ALTER / DROP {ObjectType} 的粒度级别......因此捕获ALTER_TABLE
将获取所有 ALTER TABLE...
语句。
触发 DDL 触发器后,您需要使用EVENTDATA()函数以 XML 格式获取触发 DDL 触发器的事件的详细信息。不同类型的命令可以具有与事件相关的不同数据点,因此我们首先需要查看ALTER_TABLE
. 根据 的链接文档EVENTDATA()
,可以通过查看以下文件找到各种选项:
C:\Program Files\Microsoft SQL Server\{version_number}\Tools\Binn\schemas\sqlserver\2006\11\events\events.xsd
或者,我们可以通过创建一个 DDL 触发器来做一个简单的测试,让它只返回EVENTDATA()
值让我们看看它是如何填充的:
CREATE TRIGGER [CaptureAlterTableAddColumn]
ON DATABASE
FOR ALTER_TABLE
AS
SET NOCOUNT ON;
SELECT EVENTDATA() AS [AlterTableEventData];
Run Code Online (Sandbox Code Playgroud)
一旦 DDL 触发器就位,运行以下命令以查看我们必须使用的内容:
CREATE TABLE dbo.AlterTableTest (Col1 INT);
BEGIN TRAN
ALTER TABLE dbo.AlterTableTest ADD Col2 INT;
ROLLBACK;
Run Code Online (Sandbox Code Playgroud)
那应该返回以下内容:
<EVENT_INSTANCE>
<EventType>ALTER_TABLE</EventType>
<PostTime>2015-08-27T14:34:48.730</PostTime>
<SPID>55</SPID>
<ServerName>DALI</ServerName>
<LoginName>Dali\Solomon</LoginName>
<UserName>dbo</UserName>
<DatabaseName>Test</DatabaseName>
<SchemaName>dbo</SchemaName>
<ObjectName>AlterTableTest</ObjectName>
<ObjectType>TABLE</ObjectType>
<AlterTableActionList>
<Create>
<Columns>
<Name>Col2</Name>
</Columns>
</Create>
</AlterTableActionList>
<TSQLCommand>
<SetOptions ANSI_NULLS="ON" ANSI_NULL_DEFAULT="ON" ANSI_PADDING="ON"
QUOTED_IDENTIFIER="ON" ENCRYPTED="FALSE" />
<CommandText>ALTER TABLE dbo.AlterTableTest ADD Col2 INT;
</CommandText>
</TSQLCommand>
</EVENT_INSTANCE>
Run Code Online (Sandbox Code Playgroud)
所以这看起来很有希望。该<AlterTableActionList>元素具有子元素<新建> \ <栏目>所以现在我们有一种方式,不需要不精确的文本分析与SUBSTRING
等以确定这是否ALTER TABLE ADD [column]...
VS ALTER TABLE ADD CONSTRAINT...
。但是呢ALTER TABLE ALTER COLUMN...
?如果你试试:
BEGIN TRAN
ALTER TABLE dbo.AlterTableTest ALTER COLUMN [Col1] BIGINT;
ROLLBACK;
Run Code Online (Sandbox Code Playgroud)
它显示<Create>元素现在是<Alter>。伟大的。但是为什么<Columns>有一个带有<Column>子元素的子元素而不是它:
<Create>
<Column Name="Col2" />
</Create>
Run Code Online (Sandbox Code Playgroud)
如果你试试:
BEGIN TRAN
ALTER TABLE dbo.AlterTableTest ADD Col3 INT, Col4 DATETIME;
ROLLBACK;
Run Code Online (Sandbox Code Playgroud)
它会显示:
...
<Create>
<Columns>
<Name>Col3</Name>
<Name>Col4</Name>
</Columns>
</Create>
...
Run Code Online (Sandbox Code Playgroud)
在这一点上,我们有以下内容:
SET NOCOUNT ON;
DECLARE @Columns TABLE ([ColumnName] sysname NOT NULL);
INSERT INTO @Columns (ColumnName)
SELECT t.c.value(N'(./text())[1]', N'sysname') AS [ColumnName]
FROM EVENTDATA.nodes(N'/EVENT_INSTANCE/AlterTableActionList/Create/Columns/Name') t(c);
IF (@@ROWCOUNT = 0)
BEGIN
RETURN; -- no columns added, so exit
END;
DECLARE @Query NVARCHAR(MAX);
SET @Query = EVENTDATA.value(N'(/EVENT_INSTANCE/TSQLCommand/CommandText/text())[1]',
N'NVARCHAR(MAX)');
Run Code Online (Sandbox Code Playgroud)
现在,您只需要@Query
使用 @Columns 中的值作为从何处开始查找数据类型的指南来解析。考虑到 T-SQL 的结构可能会发生变化,它将需要正则表达式来正确提取数据类型。您可能已经有一些 SQLCLR 函数,或者您可以获得免费版本的SQL#(我是它的作者),它有几个 RegEx 函数。
更新 (2016-07-08)
我最初认为EVENTDATA()
需要解析查询以获取数据类型,但@ErikE有很好的建议来检查已添加的实际列。由于列名自然需要是唯一的,而且我们已经有了列名,这使得很容易加入系统目录视图sys.columns **然后加入其他几个以获得其余信息。最终结果是:
CREATE
--ALTER
TRIGGER [CaptureAlterTableAddColumn]
ON DATABASE
FOR ALTER_TABLE
AS
SET NOCOUNT ON;
-- SELECT EVENTDATA() AS [AlterTableEventData]; -- uncomment line for debug
DECLARE @Columns TABLE ([ColumnName] sysname
COLLATE Latin1_General_100_BIN2
NOT NULL,
[Datatype] NVARCHAR(50) NULL);
DECLARE @Temp XML = EVENTDATA(),
@SchemaName sysname,
@ObjectName sysname;
INSERT INTO @Columns (ColumnName)
SELECT t.c.value(N'(./text())[1]', N'sysname') AS [ColumnName]
FROM @Temp.nodes(N'/EVENT_INSTANCE/AlterTableActionList/Create/Columns/Name') t(c);
IF (@@ROWCOUNT = 0)
BEGIN
RETURN; -- no columns added, so exit
END;
-- SELECT * FROM @Columns; -- uncomment line for debug
SET @SchemaName = EVENTDATA().value(N'(/EVENT_INSTANCE/SchemaName/text())[1]',
N'sysname');
SET @ObjectName = EVENTDATA().value(N'(/EVENT_INSTANCE/ObjectName/text())[1]',
N'sysname');
SELECT CASE
WHEN scol.[user_type_id] IN (165, 167, 173,175)
THEN styp.[name] + N'(' + CASE scol.[max_length]
WHEN -1 THEN N'MAX'
ELSE CONVERT(NVARCHAR(10), scol.[max_length])
END + N')' -- max_length: 1 - 8000, -1 = MAX
WHEN scol.[user_type_id] IN (231, 239)
THEN styp.[name] + N'(' + CASE scol.[max_length]
WHEN -1 THEN N'MAX'
ELSE CONVERT(NVARCHAR(10),
(scol.[max_length] / 2))
END + N')' -- max_length: (2 - 8000)/2, -1 = MAX
WHEN scol.[user_type_id] IN (41, 42, 43)
THEN styp.[name] + N'(' + CONVERT(NVARCHAR(10),
scol.[scale]) + N')' -- scale: 1 - 7
WHEN scol.[user_type_id] IN (106, 108)
THEN styp.[name] + N'(' + CONVERT(NVARCHAR(10), scol.[precision])
+ N', ' + CONVERT(NVARCHAR(10), scol.[scale]) + N')' -- prec. & scale
ELSE styp.[name]
END AS [DataType],
scol.collation_name,
scol.is_nullable,
scol.is_rowguidcol,
scol.is_identity,
scol.is_computed,
scol.is_sparse,
scol.is_xml_document,
scol.xml_collection_id
FROM @Columns col
INNER JOIN sys.columns scol
ON scol.[name] = col.[ColumnName] COLLATE Latin1_General_100_BIN2
INNER JOIN sys.objects sobj
ON sobj.[object_id] = scol.[object_id]
INNER JOIN sys.schemas sscm
ON sscm.[schema_id] = sobj.[schema_id]
INNER JOIN sys.types styp
ON styp.[user_type_id] = scol.[user_type_id]
WHERE sscm.[name] = @SchemaName
AND sobj.[name] = @ObjectName;
Run Code Online (Sandbox Code Playgroud)
一个相当完整的测试是:
BEGIN TRAN
ALTER TABLE dbo.AlterTableTest
ADD Col3 INT, Col4 DATETIME, cOl5 NvARchar(13), col6 nvarchar(max),
col7 datetime2(4), COL8 DEciMAL(18, 9), CoL9 NUMERIC(10),
col10 sql_variant, col11 float, col12 float(2), col13 float(22),
col14 varchar(1111), col15 VARchar(mAx), coL16 varbinary(max),
col17 varbinary(333), col18 binary(334), col19 nchar(789),
col20 char(77) sparse NULL, col21 xml NOT Null,
col22 uniqueIdentifier ROWGUIDCOL, col23 AS ([Col3] * 1.2);
ROLLBACK;
Run Code Online (Sandbox Code Playgroud)
**我使用sys.columns
系统目录视图(和其他几个)而不是INFORMATION_SCHEMA.ROUTINE_COLUMNS
因为INFORMATION_SCHEMA
不包含 Microsoft SQL Server 特定的属性。例如,SELECT
触发器中的语句引用了 中不存在的以下列(以及其他列)INFORMATION_SCHEMA.ROUTINE_COLUMNS
:
此列表取自 SQL Server 2012(SQL Server 2014 的列相同)。如果您在 SQL Server 2016 上运行,那么您可能需要将其他列合并到此触发器正在执行的任何操作中。如果您在 SQL Server 2005、2008 或 2008 R2 上运行,那么您将少一些列(例如is_sparse
,在 SQL Server 2008 中添加)。
归档时间: |
|
查看次数: |
1091 次 |
最近记录: |