SQL Server Data Collector: Nightly purge can leave orphaned rows

The Bug Description: The MDW database has a nightly purge job that removes old data unfortunately two tables are not purged by that job: notable_query_plans and notable_query_text.  This leaves orphaned rows in a Data Collector MDW.

Are you Impacted? You will know you are impacted by this problem if you have run the built-in MDW collection sets for several months, and find that the database keeps growing and that one table in particular -- [snapshots].[notable_query_plans] is using most of the space.

The Long Term Fix: This bug will be fixed in CU5 for SQL 2008

The Work Around:   If you’re affected by this process you can use the script below as a short-term workaround.  This will purge the notable_query_plans and notable_query_text tables by looking for plans that are no longer referenced by any of the rows in the [snapshots].[query_stats] table.  It may take a very long time to run for the first execution (from a few hours up to a day), depending on the number of orphaned rows that need to be cleaned up.  After the first run has caught up on all of the deferred cleanup work, subsequent executions shouldn’t take as long.

    -- Purge snapshots.notable_query_plan table

DECLARE @delete_batch_size bigint;

    DECLARE @rows_affected int;

    SET @delete_batch_size = 500;

    SET @rows_affected = @delete_batch_size;

    WHILE (@rows_affected = @delete_batch_size)

    BEGIN

        DELETE TOP (@delete_batch_size) snapshots.notable_query_plan

        FROM snapshots.notable_query_plan AS qp

        WHERE NOT EXISTS (

            SELECT *

            FROM snapshots.query_stats AS qs

            WHERE qs.[sql_handle] = qp.[sql_handle] AND qs.plan_handle = qp.plan_handle

                AND qs.plan_generation_num = qp.plan_generation_num

                AND qs.statement_start_offset = qp.statement_start_offset

                AND qs.statement_end_offset = qp.statement_end_offset

                AND qs.creation_time = qp.creation_time);

        SET @rows_affected = @@ROWCOUNT;

        RAISERROR ('Deleted %d orphaned rows from snapshots.notable_query_plan', 0, 1, @rows_affected) WITH NOWAIT;

    END;

    -- Purge snapshots.notable_query_text table

    SET @rows_affected = @delete_batch_size;

    WHILE (@rows_affected = @delete_batch_size)

    BEGIN

        DELETE TOP (@delete_batch_size) snapshots.notable_query_text

        FROM snapshots.notable_query_text AS qt

        WHERE NOT EXISTS (

            SELECT *

            FROM snapshots.query_stats AS qs

            WHERE qs.[sql_handle] = qt.[sql_handle]);

        SET @rows_affected = @@ROWCOUNT;

        RAISERROR ('Deleted %d orphaned rows from snapshots.notable_query_text', 0, 1, @rows_affected) WITH NOWAIT;

    END;