Well, to answer your question why SQL Server does this, the answer is that the query is not compiled in a logical order, each statement is compiled in its own worth, therefore, when forming the query plan for the select statement, the optimizer does not know that @ val1 and @ Val2 will become "val1" and "val2, respectively.
When SQL Server does not know the value, it should better understand how many times this variable appears in the table, which can sometimes lead to suboptimal plans. I want to say that the same query with different values ββcan generate different plans. Imagine this simple example:
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL DROP TABLE
All I did here is create a simple table and add 1000 rows with values ββ1-10 for the val
column, however 1 appears 991 times and the remaining 9 appear only once. The package is as follows:
SELECT COUNT(Filler) FROM
It would be more efficient to scan the entire table than use the index to search, and then do 991 bookmark searches to get the value for Filler
, however with only one line the following query:
SELECT COUNT(Filler) FROM
will be more efficient to search the index, and search on one tab to get the value for Filler
(and doing these two queries will ratify it)
I'm pretty sure that cut-off search and bookmark search do indeed vary depending on the situation, but it is pretty low. Using an example table with a small amount of trial and error, I found that I needed a val
column to have 38 rows with a value of 2 before the optimizer went to a full table scan to search the index and search for bookmarks:
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL DROP TABLE
So, for this example, the limit is 3.7% of matching rows.
Since the query does not know how many rows will match when you use the variable that it should guess, and the easiest way is to find out the total number of rows and divide it by the total number of different values ββin the column, so in this example the estimated number of rows for WHERE val = @Val
is 1000/10 = 100. The actual algorithm is more complex than that, but for example, it will be done. Therefore, when we look at the execution plan for:
DECLARE @i INT = 2; SELECT COUNT(Filler) FROM

We can see here (with the source data) that the estimated number of rows is 100, but the actual rows are 1. In the previous steps, we know that with more than 38 rows, the optimizer will select a cluster index scan over the index search, so since The best guess for the number of rows above this, the plan for an unknown variable is a cluster index scan.
Just for further proof of the theory, if we create a table with 1000 rows of numbers 1-27 uniformly distributed (so the estimated row counter will be approximately 1000/27 = 37.037)
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL DROP TABLE
Then run the query again, we get the plan with the index query:
DECLARE @i INT = 2; SELECT COUNT(Filler) FROM

So, hopefully, this pretty fully explains why you get this plan. Now I assume that the next question is how you are imposing another plan, and the answer is to use the OPTION (RECOMPILE)
query hint OPTION (RECOMPILE)
to force the query to compile at run time when the parameter value is known. Returning to the original data, where the best plan for Val = 2
is a search, but using a variable, a plan with indexing is issued, we can run:
DECLARE @i INT = 2; SELECT COUNT(Filler) FROM

We see that the latter uses index search and key search, because it checked the value of the variable at run time, and the most suitable plan for this particular value is selected. The problem with OPTION (RECOMPILE)
is that you cannot take advantage of cached query plans, so you need to compile the query every time.