Implementation scripts
Create 1: dbo.Comments(PostId)
USE [SQLStorm];
GO
CREATE INDEX [IX_Comments_PostId]
ON [dbo].[Comments] ([PostId])
INCLUDE ([Id], [CreationDate], [Score])
WITH (
ONLINE = ON,
SORT_IN_TEMPDB = ON,
DATA_COMPRESSION = PAGE,
FILLFACTOR = 100
);
GO
Create 2: dbo.Votes(PostId, VoteTypeId)
USE [SQLStorm];
GO
CREATE INDEX [IX_Votes_PostId_VoteTypeId]
ON [dbo].[Votes] ([PostId], [VoteTypeId])
INCLUDE ([UserId], [BountyAmount], [CreationDate])
WITH (
ONLINE = ON,
SORT_IN_TEMPDB = ON,
DATA_COMPRESSION = PAGE,
FILLFACTOR = 100
);
GO
Create 3: dbo.Badges(UserId) INCLUDE(Class)
USE [SQLStorm];
GO
CREATE INDEX [IX_Badges_UserId]
ON [dbo].[Badges] ([UserId])
INCLUDE ([Class])
WITH (
ONLINE = ON,
SORT_IN_TEMPDB = ON,
DATA_COMPRESSION = PAGE,
FILLFACTOR = 100
);
GO
Create 4: dbo.Votes(UserId, VoteTypeId) INCLUDE(BountyAmount)
USE [SQLStorm];
GO
CREATE INDEX [IX_Votes_UserId_VoteTypeId]
ON [dbo].[Votes] ([UserId], [VoteTypeId])
INCLUDE ([BountyAmount])
WITH (
ONLINE = ON,
SORT_IN_TEMPDB = ON,
DATA_COMPRESSION = PAGE,
FILLFACTOR = 100
);
GO
Alternative smaller filtered index for user bounty workload
USE [SQLStorm];
GO
CREATE INDEX [IX_Votes_UserId_Bounty_VT8]
ON [dbo].[Votes] ([UserId])
INCLUDE ([BountyAmount], [CreationDate])
WHERE [VoteTypeId] = 8 AND [UserId] IS NOT NULL
WITH (
ONLINE = ON,
SORT_IN_TEMPDB = ON,
DATA_COMPRESSION = PAGE,
FILLFACTOR = 100
);
GO
Optional drop: unused dbo.PostLinks index
USE [SQLStorm];
GO
DROP INDEX [IX_PostLinks_RelatedPostId]
ON [dbo].[PostLinks];
GO
Statistics refresh after index deployment
USE [SQLStorm];
GO
UPDATE STATISTICS [dbo].[Posts] WITH FULLSCAN;
UPDATE STATISTICS [dbo].[Votes] WITH FULLSCAN;
UPDATE STATISTICS [dbo].[Comments] WITH FULLSCAN;
UPDATE STATISTICS [dbo].[Badges] WITH FULLSCAN;
UPDATE STATISTICS [dbo].[Users] WITH FULLSCAN;
GO
Query rewrite pattern: replace correlated subqueries with pre-aggregation
/* Pattern example */
WITH VoteAgg AS
(
SELECT
v.PostId,
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,
SUM(CASE WHEN v.VoteTypeId = 8 THEN ISNULL(v.BountyAmount,0) ELSE 0 END) AS TotalBounty
FROM dbo.Votes AS v
GROUP BY v.PostId
),
CommentAgg AS
(
SELECT
c.PostId,
COUNT(*) AS CommentCount
FROM dbo.Comments AS c
GROUP BY c.PostId
)
SELECT
p.Id,
p.Title,
ISNULL(va.UpVotes,0) AS UpVotes,
ISNULL(va.DownVotes,0) AS DownVotes,
ISNULL(va.TotalBounty,0) AS TotalBounty,
ISNULL(ca.CommentCount,0) AS CommentCount
FROM dbo.Posts AS p
LEFT JOIN VoteAgg AS va
ON va.PostId = p.Id
LEFT JOIN CommentAgg AS ca
ON ca.PostId = p.Id
WHERE p.CreationDate >= DATEADD(YEAR, -1, @AsOfDate);
Schema pattern: normalize tags to eliminate LIKE '%tag%'
/* Suggested model */
CREATE TABLE dbo.PostTags
(
PostId int NOT NULL,
TagId int NOT NULL,
CONSTRAINT PK_PostTags PRIMARY KEY CLUSTERED (PostId, TagId),
CONSTRAINT FK_PostTags_Posts FOREIGN KEY (PostId) REFERENCES dbo.Posts(Id),
CONSTRAINT FK_PostTags_Tags FOREIGN KEY (TagId) REFERENCES dbo.Tags(Id)
);
GO
CREATE INDEX IX_PostTags_TagId_PostId
ON dbo.PostTags(TagId, PostId)
WITH (
ONLINE = ON,
SORT_IN_TEMPDB = ON,
DATA_COMPRESSION = PAGE,
FILLFACTOR = 100
);
GO