29/07/2026

SQL Server Performance Tuning Excersises - 1

Couple of weeks ago, I blogged about how I tried to use AI (Claude) to teach me performance tuning. You can read it here.

Starting from this blog I'm going to share those exercises and what I have learned from them.

I'm modifying what Claude has outputted to looks like it is actual exercise in here.

Setting Up Data

Below assume you have already downloaded and setup some version of AdventureWorks sample database by Microsoft.

The scenario runs on a table modelled after AdventureWorks sales data, but we need to build an inflated copy, so the performance difference is actually felt rather than just theoretical — the stock SalesOrderHeader is only ~31K rows, which is too small to show anything meaningful.

Step 1: Create "SalesOrders" table.

IF OBJECT_ID('dbo.SalesOrders') IS NOT NULL DROP TABLE dbo.SalesOrders;
GO

CREATE TABLE dbo.SalesOrders
(
    SalesOrderID    INT IDENTITY(1,1) NOT NULL,
    OrderDate       DATETIME          NOT NULL,
    CustomerID      INT               NOT NULL,
    TotalDue        MONEY             NOT NULL,
    OrderStatus     TINYINT           NOT NULL,
    OnlineOrderFlag BIT               NOT NULL,
    CONSTRAINT PK_SalesOrders PRIMARY KEY CLUSTERED (SalesOrderID)
);
GO

Step 2: Insert lot of dummy data, by cross joining sys.all_objects.

INSERT INTO dbo.SalesOrders (OrderDate, CustomerID, TotalDue, OrderStatus, OnlineOrderFlag)
SELECT TOP (1500000)
    DATEADD(DAY, ABS(CHECKSUM(NEWID())) % 2920, '2019-01-01'), -- ~8 years of dates
    ABS(CHECKSUM(NEWID())) % 20000 + 1,
    CAST(ABS(CHECKSUM(NEWID())) % 100000 / 100.0 AS MONEY),
    ABS(CHECKSUM(NEWID())) % 8 + 1,
    ABS(CHECKSUM(NEWID())) % 2
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
GO

Step 3: Create a index which simulate real world scenario where users already have some indexes.

CREATE NONCLUSTERED INDEX IX_SalesOrders_OrderDate
ON dbo.SalesOrders (OrderDate)
INCLUDE (CustomerID, TotalDue);
GO

Step 4: Create the stored procedure which we will be tuning.

CREATE OR ALTER PROCEDURE dbo.GetOrdersByYear
    @Year INT
AS
BEGIN
    SET NOCOUNT ON;

    SELECT SalesOrderID, OrderDate, CustomerID, TotalDue
    FROM dbo.SalesOrders
    WHERE YEAR(OrderDate) = @Year
    ORDER BY OrderDate;
END
GO


Scenario

There's a "Sales by Year" report page in the app that calls dbo.GetOrdersByYear with a single year, e.g. EXEC dbo.GetOrdersByYear @Year = 2022;

Users are complaining that this report is sluggish and keeps getting slower the longer the system's been live — it used to feel snappy when the table was small, now it's a few seconds and climbing. 

The DBA is confused because there's a perfectly good index sitting on OrderDate that includes the exact columns the query returns, yet it doesn't seem to be helping. CPU also ticks up noticeably whenever the report runs, even though each call only returns one year's worth of rows.

Exercise

Run the proc with the actual execution plan on and SET STATISTICS IO, TIME ON, then come back with three things: 

  1. The root cause (why that index isn't being used the way you'd hope)
  2. Your fix (the rewritten proc)
  3. The evidence that it worked (before/after logical reads, plan operator change, and duration) 

Keep the proc's signature the same; the caller shouldn't have to change how it invokes it.

The thing to watch for: the fix here shouldn't require adding any new index. The right index already exists — the query just isn't letting SQL Server use it properly. 

-----------------------------------------

I suggest you give a shot at above exercise. Then look for my solution below.

-----------------------------------------

First thing I did was, as per instructions execute and see how it works currently.

SET STATISTICS IO, TIME ON
GO

exec dbo.[GetOrdersByYear] '2022'

I got following execution plan:


As you can see it is using the index, but doing an index scan operation.

Stats as follows:

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
Table 'SalesOrders'. Scan count 1, logical reads 5597, physical reads 1, page server reads 0, read-ahead reads 5603, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 219 ms,  elapsed time = 3593 ms.

 SQL Server Execution Times:
   CPU time = 219 ms,  elapsed time = 3595 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

It was not bad, just 4 seconds for report query? I was wondering. But as mentioned on the exercise text, this is increasingly taking longer. So it seems like, more data on the table it gets more time to execute.

Looking at the execution plan it was obvious why that is happening. More data, more time to scan.

So why does it scan, when you have a index on date column and it is a covering index (i.e. provide all columns query need)?

So, I opened up the stored procedure to check. That's when I realized the issue.

Query is not SARGable. Input parameter was integer (year), so SQL server had to find the year using a "YEAR" function, which made query not SARGable. This is why index scan was used. Because SQL server couldn't predict which rows to seek into.

Now that I have understood issue, fix was to make the query more SARGable. Simlest technique is converting the incoming parameter into a date range and query the date rage. When using date rage, SQL server will be able to seek into the index correctly.

Here is my solution

ALTER   PROCEDURE [dbo].[GetOrdersByYear_MDA]
    @Year INT
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @startDate datetime = DATEFROMPARTS(@Year, 1, 1)
    DECLARE @endDate datetime = DATEFROMPARTS(@Year + 1, 1, 1)

    SELECT SalesOrderID, OrderDate, CustomerID, TotalDue
    FROM dbo.SalesOrders
    WHERE OrderDate >= @startDate AND OrderDate < @endDate
    ORDER BY OrderDate;
END

New execution plan:


New stats:




 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
Table 'SalesOrders'. Scan count 1, logical reads 705, physical reads 2, page server reads 0, read-ahead reads 709, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 32 ms,  elapsed time = 3366 ms.

 SQL Server Execution Times:
   CPU time = 32 ms,  elapsed time = 3373 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

Note that logical reads are down from 5597 to 705 and CPU time is 32 ms against 219 ms in previous round. Although network IO time was bit same and query executed about same time.

To be honest in the first round I used "Date" variables rather than "DateTime" variable in the new stored procedure. But claude has pointed me that data type conversion will consume bit of CPU time and we can optimize more if we use "DateTime". And that is a very good suggestion.

That was not a bad first exercise. Hope to get more complex one next.

18/07/2026

Claude as your Teacher

I've been focusing and studyingon SQL server performance tuning for more than a year now. I was involved in performance tuning task before, but I wanted to be an expert on that area. So I have been following online courses and doing some self-studies and reading books in my spare time.

However, I still feel I lack real world work, which require to be an expert in that field. Best way to get real world experience is find a job on that area. But in my current job I get very little performance tuning experience. I do get lot more SQL development experience. So do use my knowledge on performance tuning when I develop SQL stuff (e.g. stored procedures). 

This led me to search for some real-world experience, without leaving my current job. I did find some information on internet, but didn't find much useful or catered for me (mostly because performance tuning is case by case issue, I think).

Idea came to me suddenly recently, what if I can use AI tool as my real world experience provider. So I started working with Claude to get something developed. I used "Project" feature in Claude chat.

Let us first have a quick look into Claude Project feature.

You can find Project in Claude in left hand side menu:


Claude Projects has 3 components:

  • Memory -> This is something claude autogenerate, while you work on the project. But you can edit if you noticed something is not correct.
  • Instructions -> Most important part. You can give instructions specific to this project and Claude will follow this everytime it do a task within this project.
  • Files -> Any additional knowledge you want to give to claude on the subject.

Your recent chats relate to this project appear under "Recents" section.

So how did I get Claude to give me (or rather teach me) performance tuning exercises?

I gave following prompt/instruction on the project:

I want to challenge my self by resolving various SQL server performance issues. In order to do this I want you to think like a SQL server performance tuning expert and generate me scenarios with performance issues. I want you to generate these challenges in 3 difficult levels -> Easy, Medium and Expert. When I'm ready, I'm going to ask you to generate a scenario and present me in a particular difficult level. For these challenges, you are going to use AdventureWorks sample database provided by Microsoft (which is publicly available) and StackOverflow database which is also available publicly. I want to generate a query or stored procedure or function with a issue and just tell me what users experience when they use it (issues they face). For this you might also need to give some context information also (e.g. parameters particular stored procedure is running, indexes which are already in use) to me. You might also need to give some script to setup the data and other objects to match the scenario in my testing database (e.g. create or drop index scripts). Once you give me the challenge, I will resolve it and present you the solution, for your evaluation. 

Then on project chat area, I gave following prompt:

Please read the instructions given and present me with Easy challenge.

I'm actually really impressed with results. It gave me a scenario where I have to inspect a slow running stored procedure and identify the issue and give solution for it. Not only it gave me a scenario and excersise, it gave me instructions to setup the database in a manner where the issue is occurring.

I'm not going to explain the whole exercises here as it will make this blog very long. But here are the highlights.

In Step 1, it gave me a script to setup the database. Claude used AdventureWorks database. Because default data didn't cater for this exercise, it gave a script to create alternative "SalesOrders" table with about 1.5 million rows in it.

In Step 2, it continued the setup and told me to create an index in a specific way.

In Step 3, it gave me script to create the troublesome stored procedure.

Finally Claude explained the scenario. Here are the actual text:

There's a "Sales by Year" report page in the app that calls dbo.GetOrdersByYear with a single year, e.g. EXEC dbo.GetOrdersByYear @Year = 2022;. Users are complaining that this report is sluggish and keeps getting slower the longer the system's been live — it used to feel snappy when the table was small, now it's a few seconds and climbing. The DBA is confused because there's a perfectly good index sitting on OrderDate that includes the exact columns the query returns, yet it doesn't seem to be helping. CPU also ticks up noticeably whenever the report runs, even though each call only returns one year's worth of rows.

What I want from you:
Run the proc with the actual execution plan on and SET STATISTICS IO, TIME ON, then come back with three things — the root cause (why that index isn't being used the way you'd hope), your fix (the rewritten proc), and the evidence that it worked (before/after logical reads, plan operator change, and duration). Keep the proc's signature the same; the caller shouldn't have to change how it invokes it.  


Not a very bad exercise to start with, isn't it?

I was able to resolve it. When I presented the solution, for my surprise, it showed me some minor things (e.g. data type conversion, which I didn't think of in this situation as it didn't contribute much), I missed.

I'm going to continue to use it. Hoping to blog a about each exercise, and my solution, so you can also learn something from it. If you have any ideas to improve this project, please comments under this blog.


01/07/2026

I LIKE it with ESCAPE

Most of you who are in TSQL world like me have used LIKE operator to find strings that not exactly match or find a row which contains a specific word.


For example, if you want to find names that starts with "ST", you would do someting like this in TSQL:

SELECT DisplayName FROM dbo.Users

WHERE DisplayName LIKE 'ST%'

This will bring display names like "Stephen", "Stanley", "Stone" etc.

But recently, I came across neat trick I can use with LIKE operator in TSQL, specially when using wild card charaters.

LIKE operator accept several wild card characters:

  • % matches any string of zero or more characters (this is the most used)
  • _ matches exactly one character
    • LIKE 'ST_' => matches => ST5, STT, ST3, etc (exactly one character after ST)
  • [...] matches any single character in a set or range (like [a-f])
    • LIKE 'ST[a-f]phen => matches => STaphen, STbphe, STcphen, STdphen, STephen, STfphen (characters from a to f)
  • [^...] matches any single character not in a set or range
    • Similar to above, but it is NOT match
When you using wild card characters like this, what if search string contains, wild card characters in it and you want to search fo them?

That is where ESCAPE hint comes handy.

For example let us consider following:

SELECT * FROM Products WHERE ProductCode LIKE '%_DISCONTINUED%';

In here we want to search for product codes, ended with "_DISCONTINUED" E.g. CAT1_DISCOUNTINUED, LOSS_DISCOUNTINUED. In other words we need "_" character (which LIKE operator consider as wild card character) in the search string.

In that scenario, we can change the T-SQL like below:

SELECT * FROM Products WHERE ProductCode LIKE '%\_DISCONTINUED%' ESCAPE '\';

This tells SQL parser, we are using "\" character as escape character and we escaping "_"character and telling LIKE operator to include it in the search.

If we don't use escape character, LIKE "%_DISCOUNTINUED%" will result in results like "ADISCOUNINUED", "IGNOREMEDISCOUNTINUED" which we really don't want.

Now \_ means "a literal underscore," and the query only matches rows where that underscore is actually there. The ESCAPE '\' clause is what tells SQL Server "hey, whenever you see a backslash in this pattern, treat the next character as literal, not special."

You don't always have to use ackslash, by the way — any single character works, as long as it's one you're not otherwise using in the pattern:

SELECT * FROM Products WHERE ProductCode LIKE '%!_DISCONTINUED%' ESCAPE '!';

Above T-SQL also brings same results.

Consider a example you want to find values like 50%.

SELECT * FROM Discounts WHERE Description LIKE '%50\%%' ESCAPE '\';

In above example you can see two % signs together, one sign tells SQL server next character appear need escaping from wild card treatment, there fore looking for 50%.

This setting is per query, andn will not effect all queries you execute after that.

Hope you have learned something new today like me.

Creating Videos for Tutorial using Remotion and Claude AI

I always believe, picture worth thousand words and video worth even more when it comes to do tutorials.

Recently I found a tool called Remotion.




Remotion is a short video generating app which uses React at the core. It is programmatic way to generate videos. Because it is programmatic, it works well with AI agents. This is why it has got my attention.

Therefore, in blog I'm going to show how remotion can be used with Claude.ai.

Since we are going to use Remotion with Claude.Ai, we need Claude code installed on the machine we are intended to use Remotion. Note that Claude code require paid subscription to work.

Another pre-requisite is Node.js. Install the latest Node.js (remotion will require v16 or higher).


Installation

Create a folder for your project and open that folder using terminal.

Type following:

npx create-video@latest

Follow the wizard in terminal.

Wizard will ask you to choose a template, choose the blank template as we ae just getting started and we intend to use this with Claude code.

Wizard will ask you to install TailWindCSS or not. For our beginner project, I would say no. But it is completely optional, choosing yes is also ok, but add more complexity.

Next question is "Add agent skills?". Definitely answer yes for this question.

You will be asked to choose which agent to get skills installed for. For us it is Claude code.

Then you will require to choose installation scope. I would keep it to project. You can choose Global if you prefer to install it once.

Installation method for skill is "Symlink"

Then wizard will install the skills and complete the installation.

Next step is to install all packages. To this run following:

npm i

Once all packages are installed, you can run the app using following command:

npm run dev

There will be nothing as we haven't add any frames.


Setting up Claude Code

Next step is to run Claude code on the projects folder.

Assuming you are still on the project folder in terminal type following to invoke Claude Code:

Claude

Assuming everything is setup correctly on Claude code, above will open Claude code in project folder.

run /init to initialize Claude code for the project. This will read the content of the folder and add the remotion skill and will create claude.md file for the project. Once this is done Claude is ready for your instructions.


Prompt

Now you provide the prompt to Claude code to build the video. More elaborate the prompt is more sophisticate your video will be.

To make this demo easier, I have asked following from Claude.ai (chat box) and create the prompt for me.

Create a prompt which I can give for Claude code to create simple video tutorial for SQL Server Constraint types using "Remotion". This is for remotion project. See remotion information on https://www.remotion.dev/ . SQL server Constraint video should be based on following blog post -> https://mpa-tech-tales.blogspot.com/2026/05/all-constraints-in-sql-server.html . It should have attractive opening screen and then will need several animating screens based on sections of the blog post. For example it should show a one animation screen explaining what are sql server constraints and then transit from that screen to next screen to show primary key constraints and related animations. Can you please generate the prompt for this?

It has created very comprehensive prompt, which I can paste or give Claude Code as a md file.

If the prompt is so big, it is advisable to instruct Code Claude to proceed phase by phase (after validating output of each phase). This will make sure Claude keep on the track we wanted instead of what it wanted.

Here is the first draft of the video created by Claude Code and remotion, using above prompt. It is not any mean polish video I wanted, but you can keep refine it with Claude Code to make it more preferable. 

In my opinion (as per now), Remotion + Claude can create you basic animation videos which you can use for tutorials or presentations. I think more appropriate use case is use it to create short animations which can be embedded/merge into your overall video, rather than making entire video from Remotion + Claude.

Schema Compare in SSMS 22.7.0

One of the coolest features released lately with SSMS (SQL Server Management Studio) is Schema Compare feature, which released on version 22.7.0.

To me it is one of anticipated feature. Yes, I know we had commercial products like Redgate SQL compare, but most organisations I worked with (small to mid size), doesn't have budget to afford that (or don't want to spend money on that).

There fore I had quick look at what it can do.

Currently it can be invoked through "Tools" menu in SSMS.


Worth noting, this feature is still in "Preview" mode, so it is not fully production ready and might not compare all of the database objects.

Schema Compare UI is as follows:


It has two main panes, one in top (showing what it compare) and one in bottom showing differences in objects when selected.

Optoins button give ability customize your comparison. When pressed following dialog box is opened:

Dialog box has two tabs.

  1. General Options
  2. Object types to compare

General options tab give you control over the comparison process and change script generation process. It gives options such as "Ignore ANSI NULL". Object Types to compare tab allows you to select which objects to compare:


You can compare following database types:
  • Databses
  • Databse Projects
  • Data-tier Application Files (dacpac)
As the source you can select any of these.


In my demo I have selected two versions of Stack Overlow database:


Then press "Compare" button.

Comparison takes little while, if you database has thousands of ojects, it will definitely take considerable time. I think this is something they need to focus on before release to production.

Here are the results from comparison:


It shows deleted table in target table -> dbo.LegacyPostVotes. New table in source database -> dbo.Badges. And also shows several changes. If you click on one of these lines, you can see detail view of the change. For example following screenshot shows, what appear when I select db.usp_GetUserById stored procedure, which was changed:


You can include/exclude changes you want or don't want. Then press either "Generate Script" button or "Apply" button. Generate Script button, generates the change script and open in a new Script Window. Apply button directly apply included changes into target database.

In my openion, It is not perfect, but start to walk write direction I would say.

I'm hoping to test this more with future releases and write about it.










SQL Server Performance Tuning Excersises - 2

This is the part to of the series of blogs where we discuss Performance Tuning Excersises generate by Claude. Setup Require StackOverflow da...