40. Performance Optimization Case Studies
Level: Advanced SQL Server for real projects
- Query rewriting for speed
- Index strategy and tuning
- Denormalization trade-offs
- Query plan analysis
- Batch processing optimization
- Caching patterns
- Before/after metrics
Theory -> practice. Real optimization examples. This article teaches measurable improvements.
Case Study 1: Slow Report Query
Problem: Class performance report takes 45 seconds.
-- Original (45 seconds, 2M+ logical reads)
SELECT
s.ClassName,
s.Name,
COUNT(r.ExamResultId) AS Exams,
AVG(r.MarksObtained) AS AvgMarks
FROM Student s
LEFT JOIN ExamResult r ON s.StudentId = r.StudentId
LEFT JOIN Exam e ON r.ExamId = e.ExamId
LEFT JOIN Subject sub ON e.SubjectId = sub.SubjectId
GROUP BY s.StudentId, s.Name, s.ClassName
ORDER BY s.ClassName, AvgMarks DESC;
Analysis:
- Table Scan on ExamResult (all 500K rows)
- Multiple JOINs (expensive)
- Logical reads: 2M (very high)
Optimization: Add indexes
-- Add missing indexes:
CREATE INDEX IX_ExamResult_StudentId ON ExamResult(StudentId);
CREATE INDEX IX_Exam_ExamId ON Exam(ExamId);
CREATE INDEX IX_Subject_SubjectId ON Subject(SubjectId);
-- Re-run same query
-- Result: 5 seconds, 50K logical reads (9x faster)
Alternative: Pre-compute aggregate
-- Create materialized view
CREATE VIEW vw_ClassPerformance WITH SCHEMABINDING AS
SELECT
s.ClassName,
s.Name,
COUNT(r.ExamResultId) AS Exams,
AVG(CAST(r.MarksObtained AS DECIMAL(10,2))) AS AvgMarks
FROM dbo.Student s
LEFT JOIN dbo.ExamResult r ON s.StudentId = r.StudentId
GROUP BY s.StudentId, s.Name, s.ClassName;
-- Create clustered index (materializes)
CREATE UNIQUE CLUSTERED INDEX ix_ClassPerf ON vw_ClassPerformance(ClassName, Name);
-- Query now instant (0.1 seconds)
SELECT * FROM vw_ClassPerformance ORDER BY ClassName, AvgMarks DESC;
Result:
- Original: 45 seconds
- With indexes: 5 seconds (9x faster)
- With materialized view: 0.1 seconds (450x faster)
Case Study 2: Fee Collection Report Timeout
Problem: Monthly fee report times out (> 60 seconds).
-- Original (timeout)
SELECT
s.ClassName,
MONTH(p.PaidOn) AS Month,
SUM(p.Amount) AS Total,
COUNT(DISTINCT s.StudentId) AS PaidStudents
FROM Student s
INNER JOIN FeeAccount f ON s.StudentId = f.StudentId
INNER JOIN FeePayment p ON f.FeeAccountId = p.FeeAccountId
WHERE YEAR(p.PaidOn) = 2024
GROUP BY s.ClassName, MONTH(p.PaidOn);
Issue: Multiple JOINs, scanning 10M fee payment rows.
Optimization: Denormalize data
-- Add calculated column to FeePayment
ALTER TABLE FeePayment
ADD ClassName NVARCHAR(10);
-- Populate from Student (one-time)
UPDATE fp SET ClassName = s.ClassName
FROM FeePayment fp
INNER JOIN FeeAccount f ON fp.FeeAccountId = f.FeeAccountId
INNER JOIN Student s ON f.StudentId = s.StudentId;
-- Add index
CREATE INDEX IX_FeePayment_ClassMonth ON FeePayment(ClassName, PaidOn);
-- New query (no JOINs, direct table)
SELECT
ClassName,
MONTH(PaidOn) AS Month,
SUM(Amount) AS Total,
COUNT(DISTINCT StudentId) AS PaidStudents
FROM FeePayment
WHERE YEAR(PaidOn) = 2024
GROUP BY ClassName, MONTH(PaidOn);
Result:
- Original: Timeout (>60s)
- Optimized: 2 seconds (30x faster)
- Trade-off: Extra column storage, trigger to maintain
Case Study 3: Student Search Performance
Problem: Student search in admin panel sluggish.
-- Original (slow)
SELECT * FROM Student WHERE Name LIKE @SearchTerm + '%';
-- Full table scan, 100K+ students
-- With index on Name
CREATE INDEX IX_Student_Name ON Student(Name);
-- Same query: Still table scan (LIKE wildcard)
-- Optimize: Full-text index
CREATE FULLTEXT CATALOG ftCatalog AS DEFAULT;
CREATE FULLTEXT INDEX ON Student(Name)
KEY INDEX PK_Student;
-- Query
SELECT * FROM Student WHERE CONTAINS(Name, @SearchTerm);
-- Result: Instant (subsecond)
Result:
- Original: 3 seconds
- With standard index: 2.5 seconds
- With full-text: 0.1 seconds (30x faster)
Case Study 4: Attendance Report Batch Processing
Problem: Generate attendance for 50K students daily = 10 minutes, locks table.
-- Original (all at once)
INSERT INTO Attendance (StudentId, SubjectId, Date, IsPresent)
SELECT s.StudentId, sub.SubjectId, GETDATE(), 0
FROM Student s
CROSS JOIN Subject sub;
-- Locks: 10 minutes
Optimization: Batch process
-- Batch 1000 at a time
DECLARE @Offset INT = 0;
DECLARE @BatchSize INT = 1000;
WHILE EXISTS (SELECT 1 FROM Student WHERE StudentId > @Offset)
BEGIN
INSERT INTO Attendance (StudentId, SubjectId, Date, IsPresent)
SELECT s.StudentId, sub.SubjectId, GETDATE(), 0
FROM Student s
CROSS JOIN Subject sub
WHERE s.StudentId > @Offset
ORDER BY s.StudentId
OFFSET 0 ROWS FETCH NEXT @BatchSize ROWS ONLY;
SET @Offset = @Offset + @BatchSize;
-- Allow other queries to run
WAITFOR DELAY '00:00:01';
END;
Result:
- Original: 10 minutes (table locked)
- Batched: 5 minutes (interleaved with other queries)
- Check execution plan (scans vs seeks)
- Add missing indexes on JOIN columns
- Consider materialized view if expensive aggregate
- Pre-compute if reporting frequent
Trade: Storage + maintenance overhead vs speed.
Denormalize if:
- Aggregate queried frequently
- Speed critical
- Trigger maintains data
Don't denormalize: Single-query slowness.
- LIKE '%term%': Table scan (slow)
- LIKE 'term%': Index seek (fast)
- Full-text search: Best (contains, phrase)
Full-text for complex search.
Large inserts (>100K rows):
- Batch into 1000-row chunks
- Insert -> delay -> repeat
- Reduces lock time
- Allows other queries to run
Prevents blocking.
SET STATISTICS IO ON;
-- Before optimization
SET STATISTICS IO OFF;
-- Add index
SET STATISTICS IO ON;
-- After optimization
-- Compare: Logical Reads, Elapsed Time
Compare metrics before/after.
Index: Fast seeks, maintains automatically. Materialized view: Pre-computed, instant.
Index for: SELECT with WHERE. View for: Complex aggregate repeated often.
Use ChatGPT, Claude, or Copilot to go deeper on Performance optimization case studies in SQL Server. Try these prompts:
"Show before/after: slow report optimized with index""When should I denormalize for performance?""How do I optimize LIKE search (full-text)?""Show batch processing to reduce lock time""Quiz me: 5 questions about optimization techniques"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.