Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Install-All/Merge-All.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Get-ChildItem -Path ".." -Filter "sp_*" |
Where-Object { $_.FullName -notlike "*sp_WhoIsActive*" } |
Where-Object { $_.FullName -notlike "*sp_WhoIsActive*" -and $_.FullName -notlike "*sp_QueryStoreCleanup*" } |
ForEach-Object { Get-ChildItem $_.FullName |
Where-Object { $_.Name -like "sp_*" -and $_.Name -notlike "sp_Human Events Agent*" } } |
ForEach-Object { Get-Content $_.FullName -Encoding UTF8 } |
Expand Down
2 changes: 2 additions & 0 deletions Install-All/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,7 @@ The script will:

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.

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.

Copyright 2026 Darling Data, LLC
Released under MIT license
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- [sp_HealthParser](#health-parser): Pull all the performance-related data from the system health Extended Event
- [sp_LogHunter](#log-hunter): Get all of the worst stuff out of your error log
- [sp_IndexCleanup](#index-cleanup): Identify unused and duplicate indexes
- [sp_QueryStoreCleanup](#query-store-cleanup): Remove duplicate and noisy queries from Query Store

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

[*Back to top*](#navigatory)

## Query Store Cleanup

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.

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`.

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.

Queries with forced plans are always protected from removal.

**Note:** This procedure is not included in the Install-All script due to its destructive nature. Install it separately.

Current valid parameter details:

| parameter_name | data_type | description | valid_inputs | defaults |
|---|---|---|---|---|
| @database_name | sysname | the database to clean query store in | a database name with query store enabled | NULL; current database if NULL |
| @cleanup_targets | varchar(100) | what to target for cleanup | all, system, maintenance (or maint), custom, none | all |
| @custom_query_filter | nvarchar(1024) | custom LIKE pattern for query text filtering | a valid LIKE pattern | NULL |
| @dedupe_by | varchar(50) | deduplication strategy | all, query_hash, plan_hash, none | all |
| @min_age_days | integer | only remove queries not executed in this many days | a positive integer | NULL; no age filter |
| @report_only | bit | report what would be removed without removing | 0 or 1 | 0 |
| @help | bit | how you got here | 0 or 1 | 0 |
| @debug | bit | prints dynamic sql and diagnostics | 0 or 1 | 0 |
| @version | varchar | OUTPUT; for support | none; OUTPUT | none; OUTPUT |
| @version_date | datetime | OUTPUT; for support | none; OUTPUT | none; OUTPUT |

[*Back to top*](#navigatory)

[licence badge]:https://img.shields.io/badge/license-MIT-blue.svg
2 changes: 1 addition & 1 deletion sp_HealthParser/sp_HealthParser.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5677,7 +5677,7 @@ AND ca.utc_timestamp < @end_date';
isolation_level = e.x.value('@isolationlevel', 'sysname'),
clientoption1 = e.x.value('@clientoption1', 'bigint'),
clientoption2 = e.x.value('@clientoption2', 'bigint'),
query_text_pre = e.x.value('(//process/inputbuf/text())[1]', 'nvarchar(max)'),
query_text_pre = e.x.value('(inputbuf/text())[1]', 'nvarchar(max)'),
process_xml = e.x.query(N'.'),
deadlock_resources = d.xml_deadlock_report.query('//deadlock/resource-list')
FROM #deadlocks AS d
Expand Down
13 changes: 7 additions & 6 deletions sp_HumanEvents/sp_HumanEvents.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1544,7 +1544,8 @@ SET @session_filter_query_plans +=
ISNULL(@database_name_filter, N'') +
ISNULL(@session_id_filter, N'') +
ISNULL(@username_filter, N'') +
ISNULL(@object_name_filter, N'')
ISNULL(@object_name_filter, N'') +
ISNULL(@requested_memory_mb_filter, N'')
);

/* Recompile can have almost everything except... duration */
Expand Down Expand Up @@ -3754,7 +3755,7 @@ BEGIN
N'.' +
QUOTENAME(hew.output_schema) +
N'.' +
hew.output_table
QUOTENAME(hew.output_table)
FROM #human_events_worker AS hew
WHERE hew.id = @min_id
AND hew.is_table_created = 0;
Expand Down Expand Up @@ -4105,7 +4106,7 @@ END;
N'.' +
QUOTENAME(hew.output_schema) +
N'.' +
hew.output_table,
QUOTENAME(hew.output_table),
@date_filter =
DATEADD
(
Expand Down Expand Up @@ -4848,7 +4849,7 @@ BEGIN
SELECT
@i_cleanup_tables +=
N''DROP TABLE '' +
SCHEMA_NAME(s.schema_id) +
QUOTENAME(SCHEMA_NAME(s.schema_id)) +
N''.'' +
QUOTENAME(s.name) +
''; '' +
Expand Down Expand Up @@ -4879,7 +4880,7 @@ BEGIN
SELECT
@i_cleanup_views +=
N''DROP VIEW '' +
SCHEMA_NAME(v.schema_id) +
QUOTENAME(SCHEMA_NAME(v.schema_id)) +
N''.'' +
QUOTENAME(v.name) +
''; '' +
Expand Down Expand Up @@ -4912,7 +4913,7 @@ BEGIN CATCH

/*Only try to drop a session if we're not outputting*/
IF (@output_database_name = N''
AND @output_schema_name = N'')
AND @output_schema_name IN (N'', N'dbo'))
BEGIN
IF @debug = 1
BEGIN
Expand Down
8 changes: 3 additions & 5 deletions sp_HumanEvents/sp_HumanEventsBlockViewer.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1835,9 +1835,7 @@ BEGIN
/* Build dynamic SQL to extract the XML */
SET @extract_sql = N'
SELECT TOP (' + CONVERT(nvarchar(20), CASE WHEN @max_blocking_events > 0 THEN @max_blocking_events ELSE 2147483647 END) + N')
human_events_xml = ' +
QUOTENAME(@target_column) +
N'
human_events_xml = e.x.query(''.'')
FROM ' +
QUOTENAME(@target_database) +
N'.' +
Expand Down Expand Up @@ -3622,7 +3620,7 @@ BEGIN
bigint,
b.wait_time_ms
)
) / 1000
) / 1000 % 86400
),
'19000101'
),
Expand Down Expand Up @@ -3711,7 +3709,7 @@ BEGIN
bigint,
b.wait_time_ms
)
) / 1000
) / 1000 % 86400
),
'19000101'
),
Expand Down
28 changes: 15 additions & 13 deletions sp_IndexCleanup/sp_IndexCleanup.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,16 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
ON #filtered_index_columns_analysis
(database_id, schema_id, object_id, index_id);

CREATE TABLE
#merged_includes
(
scope_hash varbinary(32) NOT NULL,
index_name sysname NOT NULL,
key_columns nvarchar(max) NOT NULL,
merged_includes nvarchar(max) NULL,
PRIMARY KEY (scope_hash, index_name)
);

/* Parse @include_databases comma-separated list */
IF @get_all_databases = 1
AND @include_databases IS NOT NULL
Expand Down Expand Up @@ -1135,10 +1145,10 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
OPTION(RECOMPILE);

/* If we found any conflicts, raise an error */
IF LEN(@conflict_list) > 0
IF DATALENGTH(@conflict_list) > 0
BEGIN
/* Remove trailing comma and space */
SET @conflict_list = LEFT(@conflict_list, LEN(@conflict_list) - 2);
SET @conflict_list = LEFT(@conflict_list, DATALENGTH(@conflict_list) / 2 - 2);

SET @error_msg =
N'The following databases appear in both @include_databases and @exclude_databases, which creates ambiguity: ' +
Expand Down Expand Up @@ -1380,6 +1390,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#check_constraints_analysis;
TRUNCATE TABLE
#filtered_index_columns_analysis;
TRUNCATE TABLE
#merged_includes;

/*Validate searched objects per-database*/
IF @schema_name IS NOT NULL
Expand Down Expand Up @@ -2178,7 +2190,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
WHERE c.system_type_id = t.system_type_id
AND c.user_type_id = t.user_type_id
AND t.name IN (N''varchar'', N''nvarchar'')
AND t.max_length = -1
AND c.max_length = -1
)
THEN 1
ELSE 0
Expand Down Expand Up @@ -3374,16 +3386,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
OPTION(RECOMPILE);
END;

CREATE TABLE
#merged_includes
(
scope_hash bigint NOT NULL,
index_name sysname NOT NULL,
key_columns nvarchar(max) NOT NULL,
merged_includes nvarchar(max) NULL,
PRIMARY KEY (scope_hash, index_name)
);

/* Gather all supersets that need include merging */
INSERT INTO
#merged_includes
Expand Down
5 changes: 3 additions & 2 deletions sp_LogHunter/sp_LogHunter.sql
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,9 @@ BEGIN
DELETE
e WITH(TABLOCKX)
FROM #enum AS e
WHERE e.log_date < CONVERT(date, @start_date)
OR e.log_date > CONVERT(date, @end_date)
WHERE (e.log_date < CONVERT(date, @start_date)
OR e.log_date > CONVERT(date, @end_date))
AND e.archive > 0
OPTION(RECOMPILE);
END;

Expand Down
8 changes: 6 additions & 2 deletions sp_PerfCheck/sp_PerfCheck.sql
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
END;
END;

IF @has_view_server_state = 1
BEGIN
/* Check for high number of deadlocks */
INSERT INTO
#results
Expand Down Expand Up @@ -1101,7 +1103,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
CASE
WHEN CONVERT(decimal(10, 2), (domc.pages_kb / 1024.0 / 1024.0)) > 5
THEN 20 /* Very high priority >5GB */
WHEN CONVERT(decimal(10, 2), (domc.pages_kb / 1024.0 / 1024.0)) BETWEEN 3 AND 5
WHEN CONVERT(decimal(10, 2), (domc.pages_kb / 1024.0 / 1024.0)) BETWEEN 2 AND 5
THEN 30 /* High priority >2GB */
WHEN CONVERT(decimal(10, 2), (domc.pages_kb / 1024.0 / 1024.0)) BETWEEN 1 AND 2
THEN 40 /* Medium-high priority >1GB */
Expand Down Expand Up @@ -1130,6 +1132,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
osi.physical_memory_kb / 1024.0 / 1024.0
)
FROM sys.dm_os_sys_info AS osi;
END;

/* Check if Lock Pages in Memory is enabled (on-prem and managed instances only) */
IF @azure_sql_db = 0
Expand Down Expand Up @@ -3306,7 +3309,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
END;

/* Check for single data file */
IF @tempdb_data_file_count = 1
IF @tempdb_data_file_count = 1
AND @processors IS NOT NULL
BEGIN
INSERT INTO
#results
Expand Down
57 changes: 39 additions & 18 deletions sp_PressureDetector/sp_PressureDetector.sql
Original file line number Diff line number Diff line change
Expand Up @@ -441,17 +441,21 @@ OPTION(MAXDOP 1, RECOMPILE);',
ELSE 0
END,
@prefix sysname =
ISNULL
(
SELECT TOP (1)
SUBSTRING
(
dopc.object_name,
1,
CHARINDEX(N':', dopc.object_name)
)
FROM sys.dm_os_performance_counters AS dopc
) +
N'%',
(
SELECT TOP (1)
SUBSTRING
(
dopc.object_name,
1,
CHARINDEX(N':', dopc.object_name)
)
FROM sys.dm_os_performance_counters AS dopc
) +
N'%',
N'%'
),
@memory_grant_cap xml,
@cache_xml xml,
@cache_sql nvarchar(max) = N'',
Expand Down Expand Up @@ -1452,7 +1456,16 @@ OPTION(MAXDOP 1, RECOMPILE);',
CONVERT
(
decimal(38,1),
(w2.avg_ms_per_wait + w.avg_ms_per_wait) / 2
ISNULL
(
(w2.hours_wait_time - w.hours_wait_time) /
NULLIF
(
1. * (w2.waiting_tasks_count_n - w.waiting_tasks_count_n),
0.
),
0.
)
),
percent_signal_waits =
CONVERT
Expand Down Expand Up @@ -2056,7 +2069,8 @@ OPTION(MAXDOP 1, RECOMPILE);',
p.cntr_value,
p.cntr_type
FROM p
WHERE p.object_name LIKE @prefix
WHERE p.cntr_value > 0
AND p.object_name LIKE @prefix
AND p.instance_name NOT IN
(
N'internal', N'master', N'model', N'msdb', N'model_msdb',
Expand Down Expand Up @@ -2105,11 +2119,15 @@ OPTION(MAXDOP 1, RECOMPILE);',
dopc.cntr_value /
ISNULL
(
DATEDIFF
NULLIF
(
SECOND,
dopc.sample_time,
SYSDATETIME()
DATEDIFF
(
SECOND,
dopc.sample_time,
SYSDATETIME()
),
0
),
1
),
Expand All @@ -2125,7 +2143,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
p.total,
p.total_per_second
FROM p
WHERE p.cntr_value > 0
WHERE p.total_per_second <> N'0'
ORDER BY
p.object_name,
p.counter_name,
Expand Down Expand Up @@ -3931,7 +3949,10 @@ OPTION(MAXDOP 1, RECOMPILE);',
THEN N'
der.cpu_time DESC,
der.parallel_worker_count DESC
OPTION(MAXDOP 1, RECOMPILE);'
OPTION(MAXDOP 1, RECOMPILE);

SET LOCK_TIMEOUT -1;
'
ELSE N'
der.cpu_time DESC
OPTION(MAXDOP 1, RECOMPILE);
Expand Down
6 changes: 3 additions & 3 deletions sp_QueryReproBuilder/sp_QueryReproBuilder.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1770,7 +1770,7 @@ SELECT
MAX(qsrs_with_lasts.max_rowcount),';

/*Add SQL 2017+ columns*/
IF @new = 1
IF @sql_2017 = 1
BEGIN
SELECT @sql += N'
AVG((qsrs_with_lasts.avg_num_physical_io_reads * 8.) / 1024.),
Expand Down Expand Up @@ -1911,7 +1911,7 @@ FROM
),';

/*Add SQL 2017+ windowing columns*/
IF @new = 1
IF @sql_2017 = 1
BEGIN
SELECT @sql += N'
partitioned_last_num_physical_io_reads =
Expand Down Expand Up @@ -2210,7 +2210,7 @@ BEGIN
qsp.is_optimized_plan_forcing_disabled,
qsp.plan_type_desc';
END;
ELSE IF @new = 1
ELSE IF @sql_2017 = 1
BEGIN
SELECT @sql += N'
qsp.plan_forcing_type_desc,
Expand Down
Loading