Updates_20260701#814
Merged
Merged
Conversation
Adds "event_time" as a valid value for @query_sort_order so query results can be ordered newest-first by capture time. event_time is converted to a bigint via DATEDIFF_BIG so it shares the numeric result type of the existing CASE branches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New @skip_execution_plans bit parameter (default 0) lets callers bypass the execution plans result set and jump straight to the findings rollup. - Main path: GOTO BlockingRollup skips #available_plans gathering, the sys.dm_exec_text_query_plan calls, and the plan result set. - system_health path: RETURNs after the blocking results (no rollup there). - Added to @help across description, valid inputs, and defaults. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sp_HumanEvents: allow sorting query results by event_time
- Widen @query_sort_order from nvarchar(10) to nvarchar(20) so longer values like "avg duration" (12 chars) no longer silently truncate. - Reformat the event_time DATEDIFF_BIG in the ORDER BY CASE to the multi-line multi-arg function style used elsewhere in the proc. - Reword @help text and README so "avg" clearly applies only to the metric list, with "event_time" listed as a separate option. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The #filtered_objects population gated every table behind an EXISTS against sys.dm_db_index_usage_stats. That DMV only has a row for an index touched since the last stats reset, and Azure SQL DB / Hyperscale reset it on every failover and scaling operation. With the DMV empty, no table passed the EXISTS even at the default @min_reads = 0 / @min_writes = 0, so #filtered_objects came back empty, @rc = 0, and the proc hit a bare RETURN whose only message was @debug-gated at severity 10 -- exiting with no result set and no error. Fixes: 1. Only append the index-usage EXISTS when @min_reads > 0 OR @min_writes > 0. At the defaults, usage is no longer consulted for filtering, so an empty or reset DMV cannot empty the result and never-used indexes are analyzed (the point of the tool). Also fixes freshly-restarted on-prem instances. 2. Replace the silent single-database RETURN with a fall-through so an empty database surfaces in the existing 'DATABASES WITH NO QUALIFYING OBJECTS' result instead of returning nothing. Validated against fresh Azure SQL Database and Hyperscale instances with dm_db_index_usage_stats reset to empty: old proc returned nothing/no error, fixed proc correctly returned results and flagged duplicate indexes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…usage-stats-early-exit sp_IndexCleanup: fix silent early exit on Azure SQL DB/Hyperscale when index usage stats are empty
The totals CTE in both HumanEvents_Queries view bodies computed executions as COUNT_BIG(*) OVER (PARTITION BY query_plan_hash_signed, query_hash_signed, plan_handle) -- the exact same three columns as the query's GROUP BY. After grouping, every partition is a single row, so executions was always 1 (issue #802). Replaced with a plain COUNT_BIG(q.plan_handle) aggregate, matching the live #totals build path. Applies to both view variants (@skip_plans = 0 and @skip_plans = 1). Source proc only; Install-All/DarlingData.sql is auto-generated by Merge-All.ps1 and is not hand-edited. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…executions-802 sp_HumanEvents: fix executions always 1 in HumanEvents_Queries view (#802)
…view Follow-up to #802. The earlier fix (743e228) replaced the always-1 windowed count with COUNT_BIG(q.plan_handle) in the totals CTE, but in the @skip_plans = 0 view the query_agg CTE is a UNION ALL of two event types: sp_statement_completed (the execution rows) and query_post_execution_showplan (plan/memory-grant rows). plan_handle is non-NULL in both branches, so COUNT_BIG counted every execution twice -- a query that ran twice reported executions = 4 instead of 2. Tag each query_agg row with is_execution (1 for the statement-completed branch, 0 for the showplan branch) and compute executions = SUM(CONVERT(bigint, q.is_execution)), so only real executions are counted. Applied to both view variants to keep the totals CTE identical; in HumanEvents_Queries_np (no showplan branch) it is a functional no-op but slightly more robust than COUNT_BIG against a NULL plan_handle. Verified against SQL Server 2022: a query with 2 statement-completed + 2 showplan rows now reports executions = 2 (was 4) in the with-plans view and 2 in the _np view, with totals unchanged. Source proc only; Install-All/DarlingData.sql is auto-generated by Merge-All.ps1 and is not hand-edited. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…double-count-802 sp_HumanEvents: fix executions double-counted in HumanEvents_Queries view
…mit filtered-index fix script Reporting numbers diverged between the top line and the detail because the three summary levels were built from different base tables: SUMMARY rolled up #index_analysis (analyzed nonclustered indexes) while DATABASE and TABLE rolled up #partition_stats (every index, including heaps and clustered), with a per-column join to #index_details double-counting reads and writes. Rebuild all three levels bottom-up from one fan-out-free spine of analyzed nonclustered indexes so SUM(TABLE) = DATABASE = SUMMARY by construction: - TABLE: one row per analyzed table, metrics pre-aggregated per index. - DATABASE: a SUM of the current database's TABLE rows. - SUMMARY: moved after the database loop, a SUM of the DATABASE rows. This also removes the duplicate top-line rows the old in-loop cumulative insert emitted on multi-database runs. Also: - Order unused-index recommendations by writes first, then size, then rows, so high-write (pure write-amplification) indexes surface ahead of merely large ones. - Add a ready-to-run create_index_script to the FILTERED INDEXES NEEDING INCLUDED COLUMNS report (rebuilds the index with the filter columns appended to INCLUDE via DROP_EXISTING), and QUOTENAME the missing-column list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ITH options on filtered-index fix Loosen the summary spine from index_id > 1 to index_id > 0. #index_analysis only carries clustered indexes that are compression candidates, so this counts every index the tool examined and can act on (nonclustered dedup candidates plus clustered compression candidates) instead of nonclustered only. Tables that have just a clustered index now appear in the report, and compressable_indexes lines up with the emitted compression scripts (e.g. a clustered-only database that previously reported 0 tables / 0 compressable while emitting compression scripts now reports them). All three levels still reconcile: SUM(TABLE) = DATABASE = SUMMARY. Carry the standard WITH options through the filtered-index fix script so it matches the merge/create scripts: DROP_EXISTING, FILLFACTOR, SORT_IN_TEMPDB, edition-aware ONLINE, DATA_COMPRESSION when eligible, plus the filegroup/partition-scheme ON clause (built_on) so the rebuilt index stays on its original storage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cheme aware The original_index_definition reference statement stopped at the WHERE clause, so using it to re-create an index (e.g. to reverse a DISABLE) would land the index on PRIMARY instead of its real filegroup. Append the storage clause built from #partition_stats.built_on (ISNULL(partition_scheme_name, filegroup_name)) in both build sites - the nonclustered/dedup build and the clustered/PK build - so the statement round-trips faithfully: ... ON [SomeFilegroup]; ... ON [SomePartitionScheme](PartitioningColumn); Verified against partitioned objects: PRIMARY KEY CLUSTERED constraints and CREATE [UNIQUE] CLUSTERED/NONCLUSTERED INDEX statements now carry the correct ON clause, and SUM(TABLE) = DATABASE = SUMMARY still reconciles (the added joins are 1:1 per index, no fan-out). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reporting-improvements sp_IndexCleanup: reconcile reporting levels, sort unused by writes, filegroup-aware fix scripts
…807) When @event_type = 'blocking' is run while 'blocked process threshold' is 0, the existing guard raises a severity-11 message telling the user to configure the blocked process report. Because that RAISERROR runs inside the procedure-wide TRY, control transfers to the CATCH, which unconditionally ran ALTER EVENT SESSION ... STATE = STOP against a session that was never created -- throwing Msg 15151 and masking the real, helpful message. Guard the CATCH cleanup so it only stops/drops the session when it actually exists, using the proc's existing @azure-branched sys.server_event_sessions / sys.database_event_sessions existence checks. This restores the helpful message for the blocking case and for every other pre-session-creation validation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address Copilot review on #808: - Use SELECT 1 instead of SELECT 1/0 in the EXISTS checks. - Wrap the session-existence check in its own TRY/CATCH that defaults @session_exists = 0, so if the catalog lookup itself fails (e.g. a missing permission on the event-session catalog views) it can't re-mask the original error before the final THROW. - Collapse each @Azure branch to a single CASE/EXISTS assignment; the branch itself stays because the server- vs database-scoped catalog view differs (and sys.database_event_sessions can't be referenced on pre-2016 on-box SQL Server). Re-validated on SQL Server 2025 (17.0.4045.5): threshold 0 returns the configuration instructions, threshold 5 runs and self-cleans. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sp_HumanEvents: stop CATCH cleanup from masking pre-creation errors (blocking + blocked process threshold = 0)
…hecks PR #808 changed SELECT 1/0 to SELECT 1 in the two EXISTS checks on a reviewer suggestion, but the SELECT list inside EXISTS is never evaluated, so there is no divide-by-zero risk. 1/0 is the house style for EXISTS in this codebase; revert both checks to it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…th counts Render query text as a clickable processing-instruction XML column (matching sp_QuickieStore) in the main result set and in the @find_single_use_plans / @find_duplicate_plans modes. Re-source the plan cache health rollup so its numbers reconcile with the actual plan cache: - Single-use plan bloat now reads sys.dm_exec_cached_plans (compiled plans, usecounts = 1, Adhoc/Prepared) instead of statement-grained sys.dm_exec_query_stats, which produced misleadingly tiny per-database counts (e.g. "27 of 27 plans" on a 100k+ plan cache). - Plan cache instability/stability and duplicate-plan findings now count at plan grain (COUNT_BIG(DISTINCT plan_handle)) and report the true cached compiled plan total, with consistent system-database filters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… rollup The check_id 9 "Top Lead Blocker" finding dumped the raw inputbuf into object_name, so a blocker running a stored procedure showed the literal "Proc [Database Id = N Object Id = M]" marker instead of the procedure name. Apply the same OBJECT_SCHEMA_NAME/OBJECT_NAME resolution the detail output already uses, falling back to the raw inputbuf for ad hoc statements or objects that can't be resolved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…TIAL_KEY consistently - Compression candidates now sort largest-first by size in default mode instead of writes-first, so the biggest space wins surface at the top. - OPTIMIZE_FOR_SEQUENTIAL_KEY now follows through to every generated rebuild/recreate script. The MERGE, filter-rebuild, and KEPT-record compression scripts previously omitted it; on the CREATE INDEX ... DROP_EXISTING forms that silently reset the option to OFF. FILLFACTOR = 100 stays intentional. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r-indexcleanup Fixes: sp_QuickieCache, sp_HumanEventsBlockViewer, sp_IndexCleanup
Fix include-filter widening: @include_plan_ids and @include_query_ids both fed a single #include_plan_ids table checked by one EXISTS, so they combined with OR. Passing a specific plan id alongside its query id re-added every sibling plan, erasing the plan-id filter. Include filters now intersect (AND across filters, OR within a list) via a seed-or-intersect model: the first supplied filter seeds the set, each later filter deletes non-matching plans. Ignore filters keep union semantics (correct for exclusion). Port identifier filters from sp_QuickieStore: @include/@ignore_query_hashes, _plan_hashes, and _sql_handles (hex parsed to binary(8)/varbinary(64)). Wires through params, NULLIF normalization, temp tables, resolution, final WHERE gates, @help, and the README. All filter resolution stays gated under @query_plan_xml IS NULL so direct-XML mode is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…clude-filters sp_QueryReproBuilder: intersect include filters; add hash/handle filters
… output Adds a version_store_blockers element to the tempdb XML, listing the oldest active row-versioning transactions (RCSI/snapshot) that hold back instance-wide version store cleanup, oldest first. One such transaction in any database freezes cleanup everywhere, which is what inflates the adjacent version_store_gb. Surfaces session, host/login/program, isolation level, transaction age, XSN, idle time, and last command so the cause is visible right next to the symptom. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… rollup (#806) The blocked process monitor fires for any task waiting longer than the configured blocked process threshold, not only lock waits. Non-lock waits (memory grants, parallelism, etc.) and self-referential reports (a session reported as blocking itself) therefore surface as blocked process reports, but were counted alongside genuine lock contention in the findings rollup with no way to tell them apart. Add finding check_id 10 ("Non-Lock and Self Blocking") that counts these reports per database, filtered on resource_owner_type <> 'LOCK'. LOCK is the only lock value in the resource_owner_type XE map; every other value is a non-lock wait, and a self-referential report is always non-lock. Counts distinct events via event_time since transaction_id is unreliable (often 0) for non-lock waits and each report yields both a blocking and blocked row. The reports already flow through the main result set; this only adds the rollup signal. Existing lock findings and @version/@version_date are unchanged. Validated on SQL 2017 and SQL 2025 (case-sensitive collation): the new finding fires for a non-lock self-wait and excludes a normal lock block. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Checks 1 (Database Locks) and 2 (Object Locks) counted every #blocks row, so non-lock and self-referential reports inflated them alongside genuine lock contention. Filter both to resource_owner_type = 'LOCK' (or NULL, to keep unknown-type rows rather than silently drop them) so they report real lock contention only; the non-lock reports remain counted in check_id 10. Wait-time totals (check_id 1000/1001) and Login, App, and Host (check_id 8) are intentionally left inclusive: a non-lock wait is still real wait time and real activity. Validated on SQL 2017 and SQL 2025 (case-sensitive collation): with one lock and one non-lock event staged, checks 1 and 2 now read 1 (was 2), check 10 reads 1, and the wait-time totals stay at the combined value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…self-806 Surface non-lock and self-blocking reports in sp_HumanEventsBlockViewer rollup (#806)
…ource (#812) contentious_object was ~99% "Unresolved: ... object_id: N" because it resolved OBJECT_NAME() against the blocked_process_report event's object_id field, which is unreliable for KEY/PAGE/RID lock waits (it reports 0 or a non-object id). Decode the lock resource string instead: KEY -> hobt_id via [db].sys.partitions -> object_id (per contended database) OBJECT -> object_id directly PAGE/RID -> sys.dm_db_page_info (2019+, VIEW DATABASE STATE) -> object_id Non-object locks (DATABASE / XACT / METADATA / ...) and anything that can't resolve keep the "Unresolved" sentinel the findings + output logic depends on. Resolved values stay plain schema.object so the @object_name filter still matches. The event object_id is kept as a COALESCE fallback so nothing that resolved before regresses. Adds @product_version (PARSENAME of ProductVersion, matching the QuickieStore idiom) to gate dm_db_page_info; cross-db lookups are per-database dynamic SQL (QUOTENAME'd, parameterized via sp_executesql), guarded by DB-online checks and TRY/CATCH for the page-info permission. Compound waitresources ("XACT: ... KEY: ...") resolve off the embedded KEY/PAGE token. TRY_CONVERT makes the string parse unthrowable. Validated on SQL Server 2025 (HammerDB workload): contentious_object resolution ~0.3% -> ~97% on the full history; end-to-end through the proc, KEY locks resolve to dbo.district / new_order / order_line / customer, PAGE locks that can't resolve are labeled honestly. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Bump @version_date to 20260701 across all 10 combined-install procs (minors unchanged) - Restore SET ANSI_NULLS ON + SET ANSI_PADDING ON to sp_IndexCleanup, sp_PerfCheck, sp_QuickieCache headers (parity with the other 7 procs; inert - no NULL comparisons) - Regenerate Install-All/DarlingData.sql Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Updates_20260701 release prep
# Conflicts: # Install-All/DarlingData.sql
…his file is owned by the Format-and-Build CI job and must not be hand-edited
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Updates_20260701
Release merge to
main.@version_datebumped to20260701on all 10 combined-install procs (minor versions unchanged).SET ANSI_NULLS ON;+SET ANSI_PADDING ON;header parity fix for sp_IndexCleanup, sp_PerfCheck, sp_QuickieCache (behaviorally inert — no= NULLcomparisons).Install-All/DarlingData.sqlintentionally left untouched — the Format-and-Build CI job regenerates it on main.CS_AScollation).Folds in all
devwork sinceUpdates_20260601: sp_HumanEvents (executions fix, CATCH hardening), sp_HumanEventsBlockViewer (non-lock/self detection, wait_resource object resolution), sp_IndexCleanup (filtered-index fix script, compression counts), sp_PressureDetector (version-store cleanup blockers), sp_QueryReproBuilder (include-filter intersect, hash/handle filters), sp_QuickieCache (clickable query-text XML).🤖 Generated with Claude Code