Skip to content

Commit 90b36a2

Browse files
Merge pull request #647 from erikdarlingdata/dev
Dev
2 parents c8dbad7 + c201542 commit 90b36a2

File tree

12 files changed

+4061
-269
lines changed

12 files changed

+4061
-269
lines changed

Create Long IN List/Longingly.sql

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ OTHER DEALINGS IN THE SOFTWARE.
3232
CREATE OR ALTER PROCEDURE
3333
dbo.Longingly
3434
(
35-
@loops int = 1,
35+
@loops integer = 1,
3636
@debug bit = 0
3737
)
3838
AS
@@ -42,14 +42,11 @@ BEGIN
4242
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
4343

4444
DECLARE
45-
@psql nvarchar(MAX) =
46-
N'DECLARE @p',
45+
@psql nvarchar(MAX) = N'DECLARE @p',
4746
@ssql nvarchar(MAX) =
4847
N'SELECT c = COUNT_BIG(*) FROM dbo.Users AS u WHERE u.Reputation < 0 OR u.DisplayName IN (@p',
49-
@asql nvarchar(MAX) =
50-
N'',
51-
@i int =
52-
1;
48+
@asql nvarchar(MAX) = N'',
49+
@i integer = 1;
5350

5451
WHILE @i <= @loops
5552
BEGIN

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- [sp_HumanEvents](#human-events): Use Extended Events to track down various query performance issues
1212
- [sp_HumanEventsBlockViewer](#human-events-block-viewer): Analyze the blocked process report
1313
- [sp_QuickieStore](#quickie-store): The fastest and most configurable way to navigate Query Store data
14+
- [sp_QueryReproBuilder](#query-repro-builder): Generate executable reproduction scripts from Query Store data
1415
- [sp_HealthParser](#health-parser): Pull all the performance-related data from the system health Extended Event
1516
- [sp_LogHunter](#log-hunter): Get all of the worst stuff out of your error log
1617
- [sp_IndexCleanup](#index-cleanup): Identify unused and duplicate indexes
@@ -309,6 +310,42 @@ Current valid parameter details:
309310
| @version_date | datetime | OUTPUT; for support | none; OUTPUT | none; OUTPUT |
310311

311312

313+
[*Back to top*](#navigatory)
314+
315+
## Query Repro Builder
316+
317+
This procedure extracts queries and their parameters from SQL Server Query Store and generates executable reproduction scripts that you can run in a new query window.
318+
319+
It's designed to make it easy to reproduce query performance issues by capturing:
320+
* Query text with parameter declarations removed
321+
* Actual parameter values from the query plan
322+
* Context settings (ANSI options, language, date format, etc.)
323+
* Warnings about potential reproduction obstacles (temp tables, OPTION(RECOMPILE), etc.)
324+
* Embedded constants extracted from plans using parameter embedding optimization
325+
326+
You can filter queries by plan_id or query_id, and optionally specify a date range.
327+
328+
The executable_query column is displayed as clickable XML - click on it in SSMS to view the formatted, ready-to-run script.
329+
330+
More examples can be found here: [Examples](https://github.com/erikdarlingdata/DarlingData/blob/main/sp_QueryReproBuilder/Examples.sql)
331+
332+
More resources:
333+
* For a text-based adventure, head to [my site here](https://www.erikdarling.com/sp_queryreprobuilder/).
334+
335+
Current valid parameter details:
336+
337+
| parameter_name | data_type | description | valid_inputs | defaults |
338+
|---------------------|------------|-----------------------------------------------------------------------|----------------------------------------------------|----------------------------------|
339+
| @database_name | sysname | the name of the database you want to extract queries from | a database name with query store enabled | NULL; current database if NULL |
340+
| @start_date | datetime2 | the begin date of your search | January 1, 1753, through December 31, 9999 | the last seven days |
341+
| @end_date | datetime2 | the end date of your search | January 1, 1753, through December 31, 9999 | current date/time |
342+
| @include_plan_ids | nvarchar | a list of plan ids to search for | a string; comma separated for multiple ids | NULL |
343+
| @include_query_ids | nvarchar | a list of query ids to search for | a string; comma separated for multiple ids | NULL |
344+
| @help | bit | how you got here | 0 or 1 | 0 |
345+
| @debug | bit | prints dynamic sql and statement length | 0 or 1 | 0 |
346+
| @version | varchar | OUTPUT; for support | none; OUTPUT | none; OUTPUT |
347+
| @version_date | datetime | OUTPUT; for support | none; OUTPUT | none; OUTPUT |
348+
312349
[*Back to top*](#navigatory)
313350

314351
## Health Parser

sp_HealthParser/sp_HealthParser.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ BEGIN
7070
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
7171

7272
SELECT
73-
@version = '2.6',
74-
@version_date = '20250601';
73+
@version = '2.11',
74+
@version_date = '20251114';
7575

7676
IF @help = 1
7777
BEGIN

sp_HumanEvents/sp_HumanEvents.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ SET XACT_ABORT ON;
8787
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
8888

8989
SELECT
90-
@version = '6.6',
91-
@version_date = '20250601';
90+
@version = '6.11',
91+
@version_date = '20251114';
9292

9393
IF @help = 1
9494
BEGIN

sp_HumanEvents/sp_HumanEventsBlockViewer.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ SET XACT_ABORT OFF;
9393
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
9494

9595
SELECT
96-
@version = '4.6',
97-
@version_date = '20250601';
96+
@version = '4.11',
97+
@version_date = '20251114';
9898

9999
IF @help = 1
100100
BEGIN

sp_IndexCleanup/sp_IndexCleanup.sql

Lines changed: 134 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ BEGIN
7272
SET NOCOUNT ON;
7373
BEGIN TRY
7474
SELECT
75-
@version = '1.6',
76-
@version_date = '20250601';
75+
@version = '1.11',
76+
@version_date = '20251114';
7777

7878
IF
7979
/* Check SQL Server 2012+ for FORMAT and CONCAT functions */
@@ -438,10 +438,13 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
438438
index_id integer NOT NULL,
439439
index_name sysname NOT NULL,
440440
can_compress bit NOT NULL
441-
INDEX filtered_objects CLUSTERED
442-
(database_id, schema_id, object_id, index_id)
443441
);
444442

443+
CREATE CLUSTERED INDEX
444+
filtered_objects
445+
ON #filtered_objects
446+
(database_id, schema_id, object_id, index_id);
447+
445448
CREATE TABLE
446449
#operational_stats
447450
(
@@ -584,10 +587,13 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
584587
superseded_by nvarchar(4000) NULL,
585588
/* Priority score from 0-1 to determine which index to keep (higher is better) */
586589
index_priority decimal(10,6) NULL
587-
INDEX index_analysis CLUSTERED
588-
(database_id, schema_id, object_id, index_id)
589590
);
590591

592+
CREATE CLUSTERED INDEX
593+
index_analysis
594+
ON #index_analysis
595+
(database_id, schema_id, object_id, index_id);
596+
591597
CREATE TABLE
592598
#compression_eligibility
593599
(
@@ -782,11 +788,14 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
782788
index_name sysname NULL,
783789
filter_definition nvarchar(max) NULL,
784790
missing_included_columns nvarchar(max) NULL,
785-
should_include_filter_columns bit NOT NULL,
786-
INDEX c CLUSTERED
787-
(database_id, schema_id, object_id, index_id)
791+
should_include_filter_columns bit NOT NULL
788792
);
789793

794+
CREATE CLUSTERED INDEX
795+
c
796+
ON #filtered_index_columns_analysis
797+
(database_id, schema_id, object_id, index_id);
798+
790799
/* Parse @include_databases comma-separated list */
791800
IF @get_all_databases = 1
792801
AND @include_databases IS NOT NULL
@@ -2719,6 +2728,13 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27192728
AND id.user_scans > 0
27202729
) THEN 100 ELSE 0
27212730
END /* Indexes with scans get some priority */
2731+
+
2732+
CASE
2733+
WHEN #index_analysis.included_columns IS NOT NULL
2734+
AND LEN(#index_analysis.included_columns) > 0
2735+
THEN 50 /* Indexes with includes get priority over those without */
2736+
ELSE 0
2737+
END /* Prefer indexes with included columns */
27222738
OPTION(RECOMPILE);
27232739

27242740
IF @debug = 1
@@ -3736,6 +3752,115 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37363752
WHERE ia.index_name = kdd.winning_index_name
37373753
OPTION(RECOMPILE);
37383754

3755+
/* Merge all included columns from Key Duplicate indexes into the winning index */
3756+
IF @debug = 1
3757+
BEGIN
3758+
RAISERROR('Merging included columns from Key Duplicate indexes', 0, 0) WITH NOWAIT;
3759+
END;
3760+
3761+
WITH
3762+
KeyDuplicateIncludes AS
3763+
(
3764+
SELECT
3765+
winner.database_id,
3766+
winner.object_id,
3767+
winner.index_id,
3768+
winner.index_name,
3769+
winner.included_columns AS winner_includes,
3770+
loser.included_columns AS loser_includes
3771+
FROM #index_analysis AS winner
3772+
JOIN #key_duplicate_dedupe AS kdd
3773+
ON winner.database_id = kdd.database_id
3774+
AND winner.object_id = kdd.object_id
3775+
AND winner.key_columns = kdd.base_key_columns
3776+
AND ISNULL(winner.filter_definition, '') = kdd.filter_definition
3777+
AND winner.index_name = kdd.winning_index_name
3778+
JOIN #index_analysis AS loser
3779+
ON loser.database_id = kdd.database_id
3780+
AND loser.object_id = kdd.object_id
3781+
AND loser.key_columns = kdd.base_key_columns
3782+
AND ISNULL(loser.filter_definition, '') = kdd.filter_definition
3783+
AND loser.index_name <> kdd.winning_index_name
3784+
AND loser.action = N'DISABLE'
3785+
AND loser.consolidation_rule = N'Key Duplicate'
3786+
WHERE winner.action = N'MERGE INCLUDES'
3787+
AND winner.consolidation_rule = N'Key Duplicate'
3788+
)
3789+
UPDATE
3790+
ia
3791+
SET
3792+
ia.included_columns =
3793+
(
3794+
SELECT
3795+
/* Combine all includes from winner and all losers, removing duplicates */
3796+
combined_cols =
3797+
STUFF
3798+
(
3799+
(
3800+
SELECT DISTINCT
3801+
N', ' +
3802+
t.c.value('.', 'sysname')
3803+
FROM
3804+
(
3805+
/* Create XML from winner's includes */
3806+
SELECT
3807+
x = CONVERT
3808+
(
3809+
xml,
3810+
N'<c>' +
3811+
REPLACE(ISNULL(kdi.winner_includes, N''), N', ', N'</c><c>') +
3812+
N'</c>'
3813+
)
3814+
FROM KeyDuplicateIncludes AS kdi
3815+
WHERE kdi.database_id = ia.database_id
3816+
AND kdi.object_id = ia.object_id
3817+
AND kdi.index_id = ia.index_id
3818+
AND kdi.winner_includes IS NOT NULL
3819+
3820+
UNION ALL
3821+
3822+
/* Create XML from each loser's includes */
3823+
SELECT
3824+
x = CONVERT
3825+
(
3826+
xml,
3827+
N'<c>' +
3828+
REPLACE(kdi.loser_includes, N', ', N'</c><c>') +
3829+
N'</c>'
3830+
)
3831+
FROM KeyDuplicateIncludes AS kdi
3832+
WHERE kdi.database_id = ia.database_id
3833+
AND kdi.object_id = ia.object_id
3834+
AND kdi.index_id = ia.index_id
3835+
AND kdi.loser_includes IS NOT NULL
3836+
) AS a
3837+
/* Split XML into individual columns */
3838+
CROSS APPLY a.x.nodes('/c') AS t(c)
3839+
/* Filter out empty strings that can result from NULL handling */
3840+
WHERE LEN(t.c.value('.', 'sysname')) > 0
3841+
FOR
3842+
XML
3843+
PATH('')
3844+
),
3845+
1,
3846+
2,
3847+
''
3848+
)
3849+
)
3850+
FROM #index_analysis AS ia
3851+
WHERE ia.action = N'MERGE INCLUDES'
3852+
AND ia.consolidation_rule = N'Key Duplicate'
3853+
AND EXISTS
3854+
(
3855+
SELECT
3856+
1/0
3857+
FROM #key_duplicate_dedupe AS kdd
3858+
WHERE kdd.database_id = ia.database_id
3859+
AND kdd.object_id = ia.object_id
3860+
AND kdd.winning_index_name = ia.index_name
3861+
)
3862+
OPTION(RECOMPILE);
3863+
37393864
/* Find indexes with same key columns where one has includes that are a subset of another */
37403865
IF @debug = 1
37413866
BEGIN

sp_LogHunter/sp_LogHunter.sql

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
7272

7373
BEGIN
7474
SELECT
75-
@version = '2.6',
76-
@version_date = '20250601';
75+
@version = '2.11',
76+
@version_date = '20251114';
7777

7878
IF @help = 1
7979
BEGIN
@@ -676,6 +676,7 @@ BEGIN
676676
OR el.text LIKE N'SSPI%'
677677
OR el.text LIKE N'%Severity: 1[0-8]%'
678678
OR el.text LIKE N'Login succeeded for user%'
679+
OR el.text LIKE N'%query notification%'
679680
OR el.text IN
680681
(
681682
N'The Database Mirroring endpoint is in disabled or stopped state.',

sp_PerfCheck/sp_PerfCheck.sql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ BEGIN
6363
Set version information
6464
*/
6565
SELECT
66-
@version = N'1.6',
67-
@version_date = N'20250601';
66+
@version = N'1.11',
67+
@version_date = N'20251114';
6868

6969
/*
7070
Help section, for help.
@@ -4368,9 +4368,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
43684368
category = N'Database Configuration',
43694369
finding = N'Query Store Not Enabled',
43704370
database_name = d.name,
4371-
details = N'Query Store is not enabled.
4372-
Consider enabling Query Store to track query performance
4373-
over time and identify regression issues.',
4371+
details = N'Query Store is not enabled.'
4372+
+ N' Consider enabling Query Store to track query performance'
4373+
+ N' over time and identify regression issues.',
43744374
url = N'https://erikdarling.com/sp_PerfCheck#QueryStore'
43754375
FROM #databases AS d
43764376
WHERE d.database_id = @current_database_id

0 commit comments

Comments
 (0)