Skip to content

Commit 6e717dd

Browse files
Add sp_QueryStoreCleanup: Query Store noise removal tool
New stored procedure to identify and remove duplicate/noisy queries from Query Store. Targets system DMV queries, maintenance operations (index rebuilds, stats updates, DBCC, etc.), with flexible dedup strategies and safety features (forced plan protection, age filtering, report-only mode). Tested across 15 databases on SQL Server 2022 and 2025 with 772 successful removals. Excluded from Install-All due to destructive nature (removes queries). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2fdb798 commit 6e717dd

5 files changed

Lines changed: 1088 additions & 1 deletion

File tree

Install-All/Merge-All.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Get-ChildItem -Path ".." -Filter "sp_*" |
2-
Where-Object { $_.FullName -notlike "*sp_WhoIsActive*" } |
2+
Where-Object { $_.FullName -notlike "*sp_WhoIsActive*" -and $_.FullName -notlike "*sp_QueryStoreCleanup*" } |
33
ForEach-Object { Get-ChildItem $_.FullName |
44
Where-Object { $_.Name -like "sp_*" -and $_.Name -notlike "sp_Human Events Agent*" } } |
55
ForEach-Object { Get-Content $_.FullName -Encoding UTF8 } |

Install-All/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,7 @@ The script will:
5858

5959
The WhoIsActive Logging procedures are not included in this file, as they have a different installation process and depend on Adam Machanic's sp_WhoIsActive.
6060

61+
sp_QueryStoreCleanup is also not included, as it is a destructive procedure that removes queries from Query Store. Install it separately from the [sp_QueryStoreCleanup](../sp_QueryStoreCleanup) directory.
62+
6163
Copyright 2026 Darling Data, LLC
6264
Released under MIT license

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- [sp_HealthParser](#health-parser): Pull all the performance-related data from the system health Extended Event
1616
- [sp_LogHunter](#log-hunter): Get all of the worst stuff out of your error log
1717
- [sp_IndexCleanup](#index-cleanup): Identify unused and duplicate indexes
18+
- [sp_QueryStoreCleanup](#query-store-cleanup): Remove duplicate and noisy queries from Query Store
1819

1920
## Who are these scripts for?
2021
You need to troubleshoot performance problems with SQL Server, and you need to do it now.
@@ -493,4 +494,33 @@ Current valid parameter details:
493494

494495
[*Back to top*](#navigatory)
495496

497+
## Query Store Cleanup
498+
499+
Query Store is great, but it collects a lot of noise. System DMV queries, index maintenance, statistics updates, and other background operations all pile up as duplicate entries, wasting space and making it harder to find the queries you actually care about.
500+
501+
This procedure identifies and removes duplicate and noisy queries from Query Store in any database on your server. It uses text pattern matching and hash-based deduplication to find the junk, and removes it using `sp_query_store_remove_query`.
502+
503+
By default, it targets system queries, maintenance operations, and removes all copies of duplicated query and plan hashes. You can customize what to target, how to deduplicate, and whether to just report or actually remove.
504+
505+
Queries with forced plans are always protected from removal.
506+
507+
**Note:** This procedure is not included in the Install-All script due to its destructive nature. Install it separately.
508+
509+
Current valid parameter details:
510+
511+
| parameter_name | data_type | description | valid_inputs | defaults |
512+
|---|---|---|---|---|
513+
| @database_name | sysname | the database to clean query store in | a database name with query store enabled | NULL; current database if NULL |
514+
| @cleanup_targets | varchar(100) | what to target for cleanup | all, system, maintenance (or maint), custom, none | all |
515+
| @custom_query_filter | nvarchar(1024) | custom LIKE pattern for query text filtering | a valid LIKE pattern | NULL |
516+
| @dedupe_by | varchar(50) | deduplication strategy | all, query_hash, plan_hash, none | all |
517+
| @min_age_days | integer | only remove queries not executed in this many days | a positive integer | NULL; no age filter |
518+
| @report_only | bit | report what would be removed without removing | 0 or 1 | 0 |
519+
| @help | bit | how you got here | 0 or 1 | 0 |
520+
| @debug | bit | prints dynamic sql and diagnostics | 0 or 1 | 0 |
521+
| @version | varchar | OUTPUT; for support | none; OUTPUT | none; OUTPUT |
522+
| @version_date | datetime | OUTPUT; for support | none; OUTPUT | none; OUTPUT |
523+
524+
[*Back to top*](#navigatory)
525+
496526
[licence badge]:https://img.shields.io/badge/license-MIT-blue.svg

sp_QueryStoreCleanup/README.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<img src="https://erikdarling.com/wp-content/uploads/2025/08/darling-data-logo_RGB.jpg" width="300px" />
2+
3+
# sp_QueryStoreCleanup
4+
5+
Query Store is great, but it collects a lot of noise. System DMV queries, index maintenance, statistics updates, and other background operations all pile up as duplicate entries, wasting space and making it harder to find the queries you actually care about.
6+
7+
This procedure identifies and removes duplicate and noisy queries from Query Store in any database on your server. It uses text pattern matching and hash-based deduplication to find the junk, and removes it using `sp_query_store_remove_query`.
8+
9+
By default, it targets system queries (`FROM sys.%`), maintenance operations (index rebuilds, statistics updates, DBCC commands, etc.), and removes all copies of duplicated query and plan hashes. You can customize what to target, how to deduplicate, and whether to just report or actually remove.
10+
11+
Queries with forced plans are always protected from removal.
12+
13+
## Parameters
14+
15+
| parameter_name | data_type | description | valid_inputs | defaults |
16+
|---|---|---|---|---|
17+
| @database_name | sysname | the database to clean query store in | a database name with query store enabled | NULL; current database if NULL |
18+
| @cleanup_targets | varchar(100) | what to target for cleanup | all, system, maintenance (or maint), custom, none | all |
19+
| @custom_query_filter | nvarchar(1024) | custom LIKE pattern for query text filtering; also applied when @cleanup_targets = all | a valid LIKE pattern | NULL |
20+
| @dedupe_by | varchar(50) | deduplication strategy | all, query_hash, plan_hash, none | all |
21+
| @min_age_days | integer | only remove queries whose last execution is older than this many days | a positive integer | NULL; no age filter |
22+
| @report_only | bit | report what would be removed without removing | 0 or 1 | 0 |
23+
| @debug | bit | prints dynamic sql and diagnostics | 0 or 1 | 0 |
24+
| @help | bit | how you got here | 0 or 1 | 0 |
25+
| @version | varchar(30) | OUTPUT; for support | none; OUTPUT | none; OUTPUT |
26+
| @version_date | datetime | OUTPUT; for support | none; OUTPUT | none; OUTPUT |
27+
28+
### Cleanup Targets
29+
30+
The `@cleanup_targets` parameter controls which queries are identified by text pattern matching:
31+
32+
| Value | What It Matches |
33+
|---|---|
34+
| `system` | Queries containing `FROM sys.%` |
35+
| `maintenance` (or `maint`) | Index operations (`ALTER INDEX`, `CREATE INDEX`, `ALTER TABLE`), statistics operations (`UPDATE STATISTICS`, `CREATE STATISTICS`, `SELECT StatMan`), DBCC commands, and parameterized maintenance queries (`@_msparam`) |
36+
| `custom` | Uses your `@custom_query_filter` LIKE pattern |
37+
| `all` | system + maintenance combined; also applies `@custom_query_filter` if provided |
38+
| `none` | No text filtering; deduplication is purely hash-based across all queries |
39+
40+
### Deduplication Strategy
41+
42+
The `@dedupe_by` parameter controls how duplicates are identified after text filtering:
43+
44+
| Value | Behavior |
45+
|---|---|
46+
| `query_hash` | Find queries with duplicate `query_hash` values (same query compiled multiple times) |
47+
| `plan_hash` | Find queries with duplicate `query_plan_hash` values (different queries producing identical plans) |
48+
| `all` | Both query_hash and plan_hash |
49+
| `none` | Skip hash deduplication entirely; send all text-matched queries directly to removal |
50+
51+
**Note:** Hash deduplication removes all copies of duplicated hashes, not all-but-one. This is intentional, as the queries targeted are noise that will be recaptured by Query Store if they execute again.
52+
53+
## Examples
54+
55+
```sql
56+
-- Default: remove all system + maintenance duplicates from the current database
57+
EXECUTE dbo.sp_QueryStoreCleanup;
58+
59+
-- Target a specific database
60+
EXECUTE dbo.sp_QueryStoreCleanup
61+
@database_name = N'StackOverflow2013';
62+
63+
-- Report what would be removed without removing anything
64+
EXECUTE dbo.sp_QueryStoreCleanup
65+
@database_name = N'YourDatabase',
66+
@report_only = 1;
67+
68+
-- Only clean up system DMV queries
69+
EXECUTE dbo.sp_QueryStoreCleanup
70+
@database_name = N'YourDatabase',
71+
@cleanup_targets = 'system';
72+
73+
-- Only clean up maintenance operations (index rebuilds, stats updates, DBCC, etc.)
74+
EXECUTE dbo.sp_QueryStoreCleanup
75+
@database_name = N'YourDatabase',
76+
@cleanup_targets = 'maint';
77+
78+
-- Remove all text-matched queries without hash deduplication
79+
EXECUTE dbo.sp_QueryStoreCleanup
80+
@database_name = N'YourDatabase',
81+
@cleanup_targets = 'system',
82+
@dedupe_by = 'none';
83+
84+
-- Use a custom text filter to find specific query patterns
85+
EXECUTE dbo.sp_QueryStoreCleanup
86+
@database_name = N'YourDatabase',
87+
@cleanup_targets = 'custom',
88+
@custom_query_filter = N'%some_noisy_query%';
89+
90+
-- Only remove queries that haven't executed in the last 30 days
91+
EXECUTE dbo.sp_QueryStoreCleanup
92+
@database_name = N'YourDatabase',
93+
@min_age_days = 30;
94+
95+
-- Debug mode to see the generated dynamic SQL
96+
EXECUTE dbo.sp_QueryStoreCleanup
97+
@database_name = N'YourDatabase',
98+
@debug = 1;
99+
```
100+
101+
Copyright 2026 Darling Data, LLC
102+
Released under MIT license

0 commit comments

Comments
 (0)