2017-02-09 96 views
0

我試圖創建一個服務器範圍的觸發器,以防止壓縮或分區在任何有索引的索引中丟失。現在,它只是在開發中。觸發如下:觸發器返回意外的結果

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 

當我嘗試通過增加一個包含列的索引和取頁面壓縮掉,以測試它,我得到如下:(旁註:你可能會問我爲什麼要做到這一點,出於某種未知的原因,這是在生產中使用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. 

這當然不是我指定的錯誤。任何人都可以告訴我我要去哪裏嗎?

+0

DECLARE @IndexName VARCHAR(100)=(SELECT INDEXNAME FROM #T)很肯定這select返回多行,這是在錯誤來自。無論如何,它將是該部分的4個聲明之一。 –

+0

謝謝!你對此絕對正確。 – PMooney

回答

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中篩選結果。你也可以放棄遊標,因爲你只需要在一個數據庫中查找一個索引。

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