触发返回意外结果

时间:2017-02-09 13:16:53

标签: sql sql-server

我正在尝试创建一个服务器范围的触发器,以防止压缩或分区在任何拥有它的索引上丢失。现在,它只在开发中。触发器如下:

CREATE TRIGGER [dbtrg_PREVENT_COMPRESSED_PARTITION_LOSS_ON_INDEXES] ON ALL SERVER
AFTER DDL_EVENTS


AS


SET NOCOUNT ON;

CREATE TABLE #t
    (
      ServerName sysname,
      DatabaseName sysname,
      TableName sysname,
      IndexName sysname,
      PartitionNumber INT,
      CompressionType NVARCHAR(60)
    );

DECLARE @dbName sysname;
DECLARE @dbCursor CURSOR;
DECLARE @sql NVARCHAR(MAX);

SET 
@dbCursor = CURSOR 
FOR SELECT name
    FROM sys.databases
    WHERE source_database_id IS NULL 
        AND database_id > 4
        AND NAME <> 'AdventureWorks2008R2'
        AND is_read_only = 0
        AND state_desc = 'ONLINE'
    ORDER BY name;

OPEN @dbCursor;

FETCH NEXT FROM @dbCursor INTO @dbName;

WHILE ( @@FETCH_STATUS = 0 )
    BEGIN   

        SET @sql = 'USE [' + @dbName + ' ]
            INSERT INTO #t
            SELECT  @@SERVERNAME AS ServerName, 
                    DB_NAME() AS DatabaseName,
                    st.name AS TableName,   
                    si.name AS IndexName,
                    sp.partition_number AS PartitionNumber,
                    sp.data_compression_desc AS CompressionType
            FROM    sys.partitions SP WITH (NOLOCK)
                    LEFT JOIN sys.tables ST WITH (NOLOCK) ON st.object_id = sp.object_id
                    LEFT OUTER JOIN sys.indexes SI WITH (NOLOCK) ON sp.object_id = si.object_id 
                                    AND sp.index_id = si.index_id
                                    AND st.object_id = si.object_id
            WHERE st.type = ''U''
                    AND data_compression <> 0
            ORDER BY st.name, si.index_id, si.name, sp.partition_number';
        EXECUTE sp_executesql @sql;
        FETCH NEXT FROM @dbCursor INTO @dbName;
    END;

CLOSE @dbCursor;
DEALLOCATE @dbCursor;


DECLARE @xmlEventData XML = EVENTDATA();; 
    DECLARE @IndexName VARCHAR(100) = (SELECT IndexName FROM #t) 
    DECLARE @QueryBody VARCHAR(MAX) = (SELECT CONVERT(VARCHAR(MAX), @xmlEventData.query('data(/EVENT_INSTANCE/TSQLCommand/CommandText)')))
    DECLARE @eventType SYSNAME = @xmlEventData.value('(/EVENT_INSTANCE/EventType)[1]','nvarchar(128)');

IF (@eventType IN ('ALTER INDEX', 'CREATE INDEX', 'DROP INDEX'))

BEGIN   

IF (@QueryBody LIKE '%' + @IndexName + '%') AND NOT ((@QueryBody LIKE '%REORG%') OR (@QueryBody LIKE '%REBUILD%')) OR NOT (@QueryBody LIKE '%PAGE_COMPRESSION%')
ROLLBACK;
RAISERROR ('This index is either partitioned, compressed, or both.  Please see the DBAs to update this index', 16, 1) WITH LOG;
RETURN;

END;

ENABLE TRIGGER [dbtrg_PREVENT_COMPRESSED_PARTITION_LOSS_ON_INDEXES] ON ALL SERVER
GO

当我尝试通过向索引添加INCLUDE列并关闭页面压缩来测试它时,我得到以下结果:(旁注:你可能会问我为什么这样做。出于某种未知的原因,这已经发生在生产中使用GUI,我们希望确保它在将来不会发生。)

/*------------------------
USE [AdventureWorks2012]

GO

CREATE UNIQUE NONCLUSTERED INDEX [AK_SalesOrderDetail_rowguid] ON [Sales].[SalesOrderDetail]
(
    [rowguid] ASC
)
INCLUDE (   [SalesOrderID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = ON, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

GO


------------------------*/
Msg 512, Level 16, State 1, Procedure dbtrg_PREVENT_COMPRESSED_PARTITION_LOSS_ON_INDEXES, Line 78
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
The statement has been terminated.

当然,这不是我指定的错误。任何人都可以告诉我我在哪里出错吗?

2 个答案:

答案 0 :(得分:0)

Gareth说得对。 DECLARE @IndexName VARCHAR(100) = (SELECT IndexName FROM #t)返回所有数据库中的所有索引名称。我想你想使用以下方法收集受影响的实际索引名称:

架构:@xmlEventData.value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname')

名称:@xmlEventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname')

然后在#t中查找索引信息。

您还应该能够检索事件的数据库。我实际上会考虑将索引名称查找移动到触发器的开头,然后在#t中过滤结果。您可能还可以删除光标,因为您只需要在一个数据库中查找一个索引。

答案 1 :(得分:0)

Gareth对子查询是正确的。但是,光标在触发器中的临时表将不起作用。它与SQL Server在引用临时表本身之前已经删除索引有关。重新安排逻辑是徒劳的。最终,答案是创建一个由代理作业提供的持久表。该表保留了光标和临时表最初提供的信息。触发器然后查看表格的状况,这很有效。谢谢大家,谢谢你们(以及我工作的同事,帮助解决这个非常模糊的问题非常宝贵)。这是有效的代码。我希望它能帮助处于类似位置的其他人:

USE [master]
GO


SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO


CREATE TRIGGER [dbtrg_PREVENT_COMPRESSED_PARTITION_LOSS_ON_INDEXES] ON ALL SERVER
    AFTER CREATE_INDEX, DROP_INDEX, ALTER_INDEX
AS

    SET NOCOUNT ON;


    DECLARE @xmlEventData XML = EVENTDATA();; 
    DECLARE @CurrentIndexName NVARCHAR(100) = ( SELECT
                                                CONVERT(NVARCHAR(MAX), @xmlEventData.query('data(/EVENT_INSTANCE/ObjectName)')) ); 
    DECLARE @QueryBody NVARCHAR(MAX) = ( SELECT
                                            CONVERT(NVARCHAR(MAX), @xmlEventData.query('data(/EVENT_INSTANCE/TSQLCommand/CommandText)')) );
    DECLARE @eventType SYSNAME = @xmlEventData.value('(/EVENT_INSTANCE/EventType)[1]', 'varchar(128)');

       PRINT @eventType 
       PRINT @QueryBody 
       PRINT @CurrentIndexName


               IF EXISTS ( SELECT
                            IndexName
                        FROM
                            dbo.CompressedIndexes
                        WHERE
                            IndexName = @CurrentIndexName )
                AND ( @QueryBody LIKE '%' + @CurrentIndexName + '%' )
                AND NOT ( ( @QueryBody LIKE '%REORG%' )
                          OR ( @QueryBody LIKE '%REBUILD%' )
                        )
                OR NOT ( @QueryBody LIKE '%PAGE_COMPRESSION%' )
                BEGIN
                    RAISERROR ('This index is either partitioned, compressed, or both.  Please see the DBAs to update this index', 16, 1) WITH LOG;
                    ROLLBACK;
                END;
            RETURN;



ENABLE TRIGGER [dbtrg_PREVENT_COMPRESSED_PARTITION_LOSS_ON_INDEXES] ON ALL SERVER;

GO