Code Review – RockyPC / SQLStorm – 2026-05-31
Tuning goal: Code Review
Server
RockyPC
Database
SQLStorm
Version
SQL Server 2022 (16.x), 16.0.1180.1, RTM-GDR
Edition
Developer Edition (64-bit), Engine Edition: Enterprise
Platform notes
Online index operations, compression, columnstore, partition switching, and SQL Server 2022 IQP features are available.
25 stored procedures reviewed
0 functions reviewed
0 triggers reviewed
0 views reviewed
Most recent objects only were provided
Executive summary
-
Top priority: Correct logic and cardinality errors before tuning indexes. Several procedures contain incorrect joins or row-multiplying patterns that can return wrong results and waste CPU and I/O. Highest-risk objects:
dbo.sp00060,dbo.sp09982,dbo.sp09990,dbo.sp09974.Confidence: 98% -
Reduce lock duration immediately in
dbo.usp_UpdateTablesWithDelay. It holds an explicit transaction open acrossWAITFOR DELAY '00:00:05', which is a direct blocking and deadlock risk.Confidence: 99% -
Add a small set of targeted nonclustered indexes to support the repeated access patterns on
Posts,Votes,Badges,Comments, andPostHistory. The current usage summary shows heavy clustered scans on the largest tables.Confidence: 94% -
Standardize aggregation strategy by pre-aggregating each child table separately before joining. Many procedures aggregate across
Posts,Votes,Comments, andBadgesin a single query, which overcounts and inflates work.Confidence: 96% -
Remove non-SARGable tag matching and stale literal date filters.
LIKE '%' + TagName + '%'onPosts.Tagsforces scans and can match incorrect tags. Hard-coded 2024 dates make logic stale and estimates less reliable.Confidence: 93% -
Retire or quarantine low-value unused procedures. Most reviewed procedures had zero Query Store executions in the last 30 days and appear to be generated test patterns or demos rather than production-grade routines.
Confidence: 88%
Scope and objects reviewed
- Reviewed object types provided: 25 stored procedures, 0 functions, 0 triggers, 0 views.
- Only the most recent objects provided were reviewed; conclusions should be interpreted as code-review findings for this supplied subset, not the entire database codebase.
- Supporting evidence used: procedure text, Query Store execution counts for 30 days, top tables over 1,000 rows, and largest index usage/size summary.
| Reviewed set summary | Count | Notes |
|---|---|---|
| Stored procedures | 25 | All provided procedures analyzed |
| Functions | 0 | None provided |
| Triggers | 0 | None provided |
| Views | 0 | None provided |
| Potentially unused in Query Store (30d) | 22 | Based on provided execution counts |
| Executed in Query Store (30d) | 3 | dbo.sp00060, dbo.sp00060Fixed, dbo.sp00996 |
Detailed prioritized recommendations
-
Replace or retire
dbo.sp00060; keep the corrected pattern fromdbo.sp00060Fixeddbo.sp00060has a probable join bug:LEFT JOIN RankedPosts rp ON tp.UserId = rp.PostId. That joins a user ID to a post ID.- It also uses
Posts.Tags LIKE '%' + Tags.TagName + '%', which is non-SARGable, scan-heavy, and can produce false positives. dbo.sp00060Fixedis materially better because it pre-aggregates posts, votes, and badges separately and gets the latest post withOUTER APPLY TOP (1).- Recommendation: replace callers of
dbo.sp00060withdbo.sp00060Fixedlogic or alterdbo.sp00060to the corrected implementation.
Objects:
dbo.sp00060,dbo.sp00060Fixed. Confidence: 99%. -
Move
WAITFORoutside the transaction indbo.usp_UpdateTablesWithDelay- The current code updates
dbo.Users, then waits 5 seconds, then updatesdbo.Posts, all inside one explicit transaction. - This guarantees unnecessary lock retention on
Usersand raises the chance of blocking chains, deadlocks, and log flush delays. - For a test harness, delay should occur before the transaction or between separate autonomous operations, not while locks are held.
dbo.usp_UpdateTablesInLoopamplifies the problem by repeatedly calling it and usingORDER BY NEWID(), which scans large tables.
Objects:
dbo.usp_UpdateTablesWithDelay,dbo.usp_UpdateTablesInLoop. Confidence: 99%. - The current code updates
-
Fix fanout aggregation anti-patterns across posts, votes, badges, and comments
- Common pattern: join parent to multiple one-to-many tables, then aggregate in one pass. This multiplies rows and inflates counts and sums.
- Examples with likely overcount risk:
dbo.sp09991dbo.sp09972dbo.sp09977dbo.sp09994dbo.sp09964dbo.sp00060
- Preferred pattern: aggregate each child table by key first, then join the aggregated results.
- This improves correctness first, then performance.
Confidence: 96%.
-
Correct high-risk join logic that can produce invalid result sets
dbo.sp09982:JOIN PostSummary ps ON us.UserId = ps.PostIdis almost certainly wrong.dbo.sp09990:LEFT JOIN PopularTags UTC ON UA.PostsChanged > 0is effectively a conditional cross join for users with activity.dbo.sp09974:CROSS JOIN PopularPostsmultiplies every qualifying user by every top post, which is usually not intended.dbo.sp09971anddbo.sp09955:ROW_NUMBER() OVER (PARTITION BY p.Id ...)or equivalent per unique key is redundant and hides inefficiency.
Confidence: 97%.
-
Add targeted covering indexes for the dominant access paths
- The largest tables are
Votes(926k),PostHistory(848k),Badges(439k),Comments(351k),Users(267k), andPosts(247k). - Usage summary shows extensive scans on clustered PKs, especially:
Postsclustered index scans: 2342Usersclustered index scans: 1650Votesclustered index scans: 1173Commentsclustered index scans: 793Badgesclustered index scans: 648PostHistoryclustered index scans: 476
- Recommended first-wave indexes:
Posts (OwnerUserId, PostTypeId, CreationDate DESC, Id DESC)include common output columnsVotes (PostId, VoteTypeId)Badges (UserId, Class)Comments (PostId)and optionallyComments (UserId)for correlated comment countsPostHistory (PostId, PostHistoryTypeId, CreationDate DESC)
- Because this is SQL Server 2022 Enterprise-capable,
ONLINE = ONandDATA_COMPRESSION = PAGEare valid options for these additions.
Confidence: 94%.
- The largest tables are
-
Replace correlated scalar subqueries and repeated per-row subqueries
- Examples:
dbo.sp09974uses multiple scalar subqueries for per-user counts and per-post vote counts.dbo.sp09967usesSTRING_AGGin a per-row correlated subquery.dbo.sp00996uses a per-user correlated comment count.dbo.sp09978uses a per-row correlated duplicate count onPostLinks.
- Rewrite with pre-aggregated CTEs or temp tables keyed once per join key.
- This reduces repeated scans and stabilizes memory grants.
Confidence: 92%.
- Examples:
-
Remove or redesign non-SARGable tag searches
dbo.sp00060,dbo.sp09990, anddbo.sp09982use tag matching againstPosts.Tagswith leading wildcard patterns.- This prevents index seeks and is also semantically weak because plain substring matching can confuse similar tags.
- Long-term fix: normalize post-tag relationships into a bridge table such as
PostTags(PostId, TagId). - Short-term fix: avoid tag expansion unless truly required; if required, stage tags outside the hot query path.
Confidence: 95%.
-
Remove stale literal dates and use parameters or current-date logic
- Many procedures hard-code
'2024-10-01 12:34:56'. Examples:dbo.sp09995,dbo.sp09994,dbo.sp09993,dbo.sp09982,dbo.sp09978,dbo.sp09974,dbo.sp09972,dbo.sp09964,dbo.sp09963,dbo.sp09954. - That makes the logic age badly and undermines cardinality accuracy as data distribution changes.
- Use input parameters or expressions such as
DATEADD(YEAR, -1, SYSUTCDATETIME()).
Confidence: 90%.
- Many procedures hard-code
-
Prefer temp tables for reusable intermediate top-N sets when estimates are unstable
- Several procedures chain multiple CTEs and then sort, rank, and rejoin large sets.
- For larger executions, materializing selective sets into temp tables with supporting temp indexes can outperform repeated CTE expansion.
- This is especially relevant for
dbo.sp09994,dbo.sp09995,dbo.sp09955, anddbo.sp09960.
Confidence: 78%.
-
Deprecate or isolate low-value unused procedures
- 22 of 25 reviewed procedures show no Query Store executions in the last 30 days.
- Many of these share similar synthetic naming and repetitive patterns, suggesting generated or experimental code.
- Retiring them reduces review surface, plan cache churn, accidental misuse, and maintenance overhead.
Confidence: 88%.
Object-specific findings
| Object | Primary issues | Priority |
|---|---|---|
dbo.sp00060 |
Wrong join key, non-SARGable tag match, heavy fanout aggregation, likely incorrect results | Critical |
dbo.sp00060Fixed |
Best pattern in reviewed set; still benefits from Posts/Votes/Badges indexes |
Low |
dbo.usp_UpdateTablesWithDelay |
WAITFOR inside transaction, lock retention, blocking risk |
Critical |
dbo.usp_UpdateTablesInLoop |
ORDER BY NEWID() scans, test harness behavior, noisy PRINT usage |
High |
dbo.sp09982 |
Wrong join key UserId = PostId, tag scan, likely invalid output |
Critical |
dbo.sp09990 |
Conditional cross join to tags, tag scan, inflated result set risk | Critical |
dbo.sp09974 |
Multiple correlated subqueries plus CROSS JOIN result explosion |
Critical |
dbo.sp09991, dbo.sp09972, dbo.sp09977, dbo.sp09964, dbo.sp09994 |
Fanout aggregation and potential overcounting | High |
dbo.sp09971, dbo.sp09955 |
Redundant row_number partitioning by unique key; extra complexity | Medium |
dbo.sp00996, dbo.sp09967, dbo.sp09978 |
Repeated correlated subqueries causing avoidable repeated reads | Medium |
Most sp099xx procedures |
Hard-coded 2024 dates, likely stale logic, no recent use | Medium |
Locking visual
Figure: the current test procedure intentionally stretches transaction lifetime. Even in a non-production harness, this pattern is unsafe if reused in real workloads.
Index and table observations
| Table | Rows | Observed issue | Recommendation |
|---|---|---|---|
dbo.Votes |
926,084 | 1173 clustered scans; common grouping by PostId and filtering on VoteTypeId |
Add (PostId, VoteTypeId) |
dbo.PostHistory |
847,593 | 476 clustered scans; frequent PostId and type/date aggregations |
Add (PostId, PostHistoryTypeId, CreationDate DESC) |
dbo.Badges |
439,352 | 648 clustered scans; repeated user badge counts and class splits | Add (UserId, Class) |
dbo.Comments |
351,440 | 793 clustered scans; frequent counts by PostId and UserId |
Add (PostId); consider (UserId) if comment-by-user queries are important |
dbo.Users |
267,193 | 1650 clustered scans; many ranking/filter queries on reputation and recent access | Only add a ranking/filter index if these queries remain after cleanup |
dbo.Posts |
246,672 | 2342 clustered scans; most reviewed procedures read by OwnerUserId, PostTypeId, CreationDate, Id |
Add (OwnerUserId, PostTypeId, CreationDate DESC, Id DESC) with includes |
Version/edition-aware note: SQL Server 2022 Enterprise-capable features help operationally, but they do not compensate for logic bugs, fanout errors, or non-SARGable predicates. Intelligent Query Processing, Parameter Sensitive Plan optimization, and DOP feedback are beneficial, yet the reviewed issues are primarily query-shape and schema-access problems.
Scripts
Script 1: Replace dbo.sp00060 with the corrected implementation from Recommendation 1
USE [SQLStorm];
GO
CREATE OR ALTER PROCEDURE dbo.sp00060
AS
BEGIN
SET NOCOUNT ON;
;WITH PostCounts AS
(
SELECT
p.OwnerUserId AS UserId,
COUNT_BIG(*) AS TotalPosts
FROM dbo.Posts AS p
WHERE p.OwnerUserId IS NOT NULL
GROUP BY p.OwnerUserId
),
VoteCounts AS
(
SELECT
p.OwnerUserId AS UserId,
SUM(CASE WHEN v.VoteTypeId = 2 THEN 1 ELSE 0 END) AS UpVotes,
SUM(CASE WHEN v.VoteTypeId = 3 THEN 1 ELSE 0 END) AS DownVotes
FROM dbo.Posts AS p
LEFT JOIN dbo.Votes AS v
ON v.PostId = p.Id
AND v.VoteTypeId IN (2,3)
WHERE p.OwnerUserId IS NOT NULL
GROUP BY p.OwnerUserId
),
BadgeCounts AS
(
SELECT
b.UserId,
COUNT_BIG(*) AS BadgeCount
FROM dbo.Badges AS b
GROUP BY b.UserId
),
TopUsers AS
(
SELECT
u.Id AS UserId,
u.DisplayName,
CONVERT(int, ISNULL(pc.TotalPosts, 0)) AS TotalPosts,
CONVERT(int, ISNULL(vc.UpVotes, 0)) AS UpVotes,
CONVERT(int, ISNULL(vc.DownVotes, 0)) AS DownVotes,
CONVERT(int, ISNULL(bc.BadgeCount, 0)) AS BadgeCount,
DENSE_RANK() OVER
(
ORDER BY
ISNULL(pc.TotalPosts, 0) DESC,
ISNULL(vc.UpVotes, 0) - ISNULL(vc.DownVotes, 0) DESC
) AS UserRank
FROM dbo.Users AS u
LEFT JOIN PostCounts AS pc
ON pc.UserId = u.Id
LEFT JOIN VoteCounts AS vc
ON vc.UserId = u.Id
LEFT JOIN BadgeCounts AS bc
ON bc.UserId = u.Id
WHERE ISNULL(pc.TotalPosts, 0) > 10
)
SELECT
tu.UserRank,
tu.DisplayName,
tu.TotalPosts,
tu.UpVotes,
tu.DownVotes,
tu.BadgeCount,
lp.Title AS LatestPostTitle,
lp.CreationDate AS LatestPostDate,
CASE WHEN lp.PostId IS NULL THEN NULL ELSE 'Latest' END AS PostStatus
FROM TopUsers AS tu
OUTER APPLY
(
SELECT TOP (1)
p.Id AS PostId,
p.Title,
p.CreationDate
FROM dbo.Posts AS p
WHERE p.OwnerUserId = tu.UserId
AND p.PostTypeId = 1
ORDER BY p.CreationDate DESC, p.Id DESC
) AS lp
ORDER BY tu.UserRank, lp.CreationDate DESC;
END;
GO
Script 2: Shorten lock duration in dbo.usp_UpdateTablesWithDelay for Recommendation 2
USE [SQLStorm];
GO
CREATE OR ALTER PROCEDURE dbo.usp_UpdateTablesWithDelay
@UserId INT,
@NewReputation INT,
@PostId INT,
@NewScore INT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @RowsAffected1 INT = 0;
DECLARE @RowsAffected2 INT = 0;
BEGIN TRY
-- Keep any test delay outside the transaction to avoid holding locks unnecessarily.
WAITFOR DELAY '00:00:05';
BEGIN TRANSACTION;
UPDATE dbo.Users
SET Reputation = @NewReputation,
LastAccessDate = GETDATE()
WHERE Id = @UserId;
SET @RowsAffected1 = @@ROWCOUNT;
UPDATE dbo.Posts
SET Score = @NewScore,
LastActivityDate = GETDATE()
WHERE Id = @PostId;
SET @RowsAffected2 = @@ROWCOUNT;
COMMIT TRANSACTION;
SELECT
'Success' AS Status,
'Both tables updated successfully' AS Message,
@RowsAffected1 AS UsersRowsAffected,
@RowsAffected2 AS PostsRowsAffected;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
SELECT
'Error' AS Status,
ERROR_NUMBER() AS ErrorNumber,
ERROR_MESSAGE() AS ErrorMessage,
ERROR_LINE() AS ErrorLine,
ERROR_PROCEDURE() AS ErrorProcedure;
THROW;
END CATCH
END;
GO
Script 3: Create first-wave supporting indexes for Recommendations 3 and 5
USE [SQLStorm];
GO
IF NOT EXISTS
(
SELECT 1
FROM sys.indexes
WHERE object_id = OBJECT_ID(N'dbo.Posts')
AND name = N'IX_Posts_OwnerUserId_PostTypeId_CreationDate_Id'
)
BEGIN
CREATE INDEX IX_Posts_OwnerUserId_PostTypeId_CreationDate_Id
ON dbo.Posts (OwnerUserId, PostTypeId, CreationDate DESC, Id DESC)
INCLUDE (Title, Score, ViewCount, AnswerCount, CommentCount, LastActivityDate, AcceptedAnswerId)
WITH (ONLINE = ON, SORT_IN_TEMPDB = ON, DATA_COMPRESSION = PAGE);
END
GO
IF NOT EXISTS
(
SELECT 1
FROM sys.indexes
WHERE object_id = OBJECT_ID(N'dbo.Votes')
AND name = N'IX_Votes_PostId_VoteTypeId'
)
BEGIN
CREATE INDEX IX_Votes_PostId_VoteTypeId
ON dbo.Votes (PostId, VoteTypeId)
WITH (ONLINE = ON, SORT_IN_TEMPDB = ON, DATA_COMPRESSION = PAGE);
END
GO
IF NOT EXISTS
(
SELECT 1
FROM sys.indexes
WHERE object_id = OBJECT_ID(N'dbo.Badges')
AND name = N'IX_Badges_UserId_Class'
)
BEGIN
CREATE INDEX IX_Badges_UserId_Class
ON dbo.Badges (UserId, Class)
WITH (ONLINE = ON, SORT_IN_TEMPDB = ON, DATA_COMPRESSION = PAGE);
END
GO
IF NOT EXISTS
(
SELECT 1
FROM sys.indexes
WHERE object_id = OBJECT_ID(N'dbo.Comments')
AND name = N'IX_Comments_PostId'
)
BEGIN
CREATE INDEX IX_Comments_PostId
ON dbo.Comments (PostId)
INCLUDE (UserId, Score, CreationDate)
WITH (ONLINE = ON, SORT_IN_TEMPDB = ON, DATA_COMPRESSION = PAGE);
END
GO
IF NOT EXISTS
(
SELECT 1
FROM sys.indexes
WHERE object_id = OBJECT_ID(N'dbo.PostHistory')
AND name = N'IX_PostHistory_PostId_PostHistoryTypeId_CreationDate'
)
BEGIN
CREATE INDEX IX_PostHistory_PostId_PostHistoryTypeId_CreationDate
ON dbo.PostHistory (PostId, PostHistoryTypeId, CreationDate DESC)
INCLUDE (UserId)
WITH (ONLINE = ON, SORT_IN_TEMPDB = ON, DATA_COMPRESSION = PAGE);
END
GO
Script 4: Add an optional user-centric comments index for Recommendation 6 where comment-by-user counts matter
USE [SQLStorm];
GO
IF NOT EXISTS
(
SELECT 1
FROM sys.indexes
WHERE object_id = OBJECT_ID(N'dbo.Comments')
AND name = N'IX_Comments_UserId'
)
BEGIN
CREATE INDEX IX_Comments_UserId
ON dbo.Comments (UserId)
INCLUDE (PostId, Score, CreationDate)
WITH (ONLINE = ON, SORT_IN_TEMPDB = ON, DATA_COMPRESSION = PAGE);
END
GO