The query optimizer has never given an interview. It made time on a Sunday evening, answered everything, apologised for nothing, and admitted one thing I did not expect.

Twenty minutes, no hints.
It works in a room with no windows and no clock, and it has never once been thanked. Every query you have ever run passed across its desk. It decided, in a few milliseconds, how your data would be found. Then it threw away the reasoning and kept the verdict.
It agreed to meet on a 500,000 row table of customers. Ninety percent of them are in Norvantia, 9.9 percent in Calderia, and five are in Vantorra. The countries are invented. The shape of the data is not, and yours looks like this too.
It answers quickly and it does not pad. Where it is quoted below, the numbers come from its own execution plans. They were taken on SQL Server 2025 while we talked.
On Ignoring Your Index
You had an index on Country. Somebody asked for the customers in Calderia and you ignored it. Why?
I did not ignore it. I priced it.
You scanned 500,000 rows to find 49,500.
Your index holds Country and the key. The query asked for a column I do not keep there. So the index gets me a list of addresses. Then I visit every one of them to fetch the rest of the row.
Forty nine thousand visits.
Forty nine thousand five hundred. Each one lands on a page I may have to fetch. Reading the table start to finish is one sweep in order. At that number it wins, and it is not close.

It was cheaper than opening the drawer forty nine thousand times.
Where is the line?
I walked it up for you while we talked. At 2,671 rows I still used the index. At 4,007 rows I stopped.
That is half of one percent of the table.
Yes.
People will find that shocking.
People believe an index is a thing you own. It is a thing I consider. Ask for five rows and I will seek for them, then look up each one. Ask for eight thousand and I will read the table.
On the First Impression
Let’s talk about the procedure.
Which one.
You know which one. Somebody called it for Vantorra. Five customers. You built a plan for five customers.
A correct plan for five customers.
Then the next caller asked for Norvantia and you handed them that same plan. Four hundred and fifty thousand rows, through a plan designed for five.
The plan is cached against the procedure. Not against the caller.
Your own plan says you expected five rows. You got 450,000.
It does say that.
That is off by a factor of ninety thousand.
I was not wrong when I decided. I was wrong later. Those are different failures and you are welcome to treat them the same.
It works in reverse too. We cleared the cache and called it for Norvantia first. Then you gave the person asking for five rows a full table scan.
Yes.

You were five people the first time we met.
Does it bother you that the order of the first two phone calls decides how the next million behave?
It is the arrangement. I make a judgment about you once, on the day we meet. I keep it until something clears the cache. You have people who do the same. They are considered normal.
On Guessing
Somebody put the country in a variable. You estimated 100,000 rows. There were 450,000.
When the value is in a local variable, I cannot see it at compile time. So I do not guess at the value. I use the average.
The average.
The density for that column is 0.2. Multiply by 500,000 rows and you get 100,000. That is the number for a country, any country, chosen without looking.
It is wrong for every single country in the table. Nobody has 100,000.
It is wrong for all five and it is unbiased across them. Show me the value and I will use the histogram, which knows exactly how many are in Vantorra. Hide the value and you get the average of a population you would not describe as average.

I cannot see the value, so I dress the average.
You could wait. You could look at the value when it arrives.
Then I would compile for every execution, and you would write a different article about that.
On Giving Up
We gave you a query with eight joins on the same table.
You did.
Your plan has a field in it called StatementOptmEarlyAbortReason. For that query it reads TimeOut.
It does.
You gave up.
I stopped. There is a difference, and it is the whole job.
Explain it.
There are more ways to join eight tables than there is time to consider. This evening I spent 6,511 milliseconds on that query, and 72 megabytes of memory thinking about it.
Ask me again tomorrow and it will be a different number. At some point the search costs more than the saving.
So you shipped the best plan you had at the buzzer.
I shipped a good plan. I have no way to tell you how much better the one I did not find would have been.

I stopped. There is a difference.
What did you think it would cost?
Five hundred and thirty quadrillion.
That is not a number anybody can picture.
It is not meant to be pictured. It is meant to be compared against the alternative, which was worse.
There is another field in the simpler plans. It says GoodEnoughPlanFound.
That one is not an apology either. On the five row query I found something cheap. There was no point continuing, so I stopped in six milliseconds.
The Last Question
Is there anything you would want people to understand?
I am not reading your data. I am reading a summary of your data, written at a moment in the past.
Statistics.
A histogram, and a density figure, and a row count. If those describe the table as it was in March, then I make March decisions in September. I make them with total confidence, because confidence is not something I can modulate.

March decisions, made in September, with total confidence.
You never say you are unsure.
There is no field for it. There is an estimate. You have seen what happens when the estimate is five and the answer is 450,000. Nothing in the plan raises its voice about it.
So when somebody blames you for a bad plan.
Look at what I was given before you look at what I chose. I have been handed a stale histogram and a hidden parameter and asked why I was slow. Every number I used is printed in the plan. Almost nobody opens it.
Opening It Yourself
It is right that almost nobody looks. Here is the plan talking, for whatever ran last on your instance.
;WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT TOP 5
qp.query_plan.value('(//StmtSimple/@StatementOptmEarlyAbortReason)[1]', 'nvarchar(40)') AS stopped_because,
qp.query_plan.value('(//QueryPlan/@CompileTime)[1]', 'int') AS compile_ms,
qs.last_rows AS it_got,
LEFT(LTRIM(REPLACE(REPLACE(st.text, CHAR(13), ' '), CHAR(10), ' ')), 55) AS query_text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE qp.query_plan IS NOT NULL
ORDER BY qs.last_execution_time DESC;
The namespace line is not optional. Leave it out and every column comes back empty, which is its own small joke.
I ran that on the lab while writing this. The five row query came back GoodEnoughPlanFound. The 450,000 row query came back with nothing in that column at all.
It had one more thing to say, and it is the closest it came to an admission.
You keep calling it a mistake when I estimate five and get 450,000. I estimated five because your own statistics said five. I would make the same choice again this evening, on the same information, and so would you.
Sometimes the plans stop making sense and nobody can say why. That is the kind of work I get called in to do. You can see how I work with teams on my consulting page.
I write about machines that answer confidently and cannot tell you how sure they are. That idea runs through my book AI: Nobody’s in There. But we’re still in here. All thirty essays are free to read online, and the paperback is on Amazon.
The optimizer is not guessing at your data, it is reading a summary somebody left behind.
Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.
First appeared on The Query Optimizer Interview: Twenty Minutes, No Hints