<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://www2.sqlblog.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Kalen Delaney : sort</title><link>http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/sort/default.aspx</link><description>Tags: sort</description><dc:language>en</dc:language><generator>CommunityServer 2.1 SP2 (Build: 61129.1)</generator><item><title>Geek City: Using a Nonclustered Index to Avoid a Sort</title><link>http://www2.sqlblog.com/blogs/kalen_delaney/archive/2008/12/02/using-a-nonclustered-index-to-avoid-a-sort.aspx</link><pubDate>Wed, 03 Dec 2008 03:20:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:10245</guid><dc:creator>Kalen Delaney</dc:creator><slash:comments>15</slash:comments><comments>http://www2.sqlblog.com/blogs/kalen_delaney/comments/10245.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/kalen_delaney/commentrss.aspx?PostID=10245</wfw:commentRss><description>&lt;P&gt;Most of you are probably aware that having a clustered index on the column(s) in an ORDER BY clause means that SQL Server can avoid having to sort your data, because it is already logically stored in order of the clustered index, and SQL Server can just access the data in order to get the sorted data .&lt;/P&gt;
&lt;P&gt;For example, consider the &lt;EM&gt;SalesOrderHeader&lt;/EM&gt; table in the &lt;EM&gt;AdventureWorks&lt;/EM&gt; database. The clustered index is on &lt;EM&gt;SalesOrderID&lt;/EM&gt;, so this query doesn't need to do a sort, just a clustered index scan:&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;SELECT * FROM Sales.SalesOrderHeader&lt;BR&gt;ORDER BY SalesOrderID&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;A href="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityUsingaNonclusteredIndextoAvoidaS_E2B4/image_2.png"&gt;&lt;IMG style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height=70 alt=image src="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityUsingaNonclusteredIndextoAvoidaS_E2B4/image_thumb.png" width=272 border=0&gt;&lt;/A&gt; &lt;/P&gt;
&lt;P&gt;But what about a nonclustered index? Its leaf level stores the index key values in order, so it can help avoid a sort if it completely covers the query, i.e. all the data your query needs is in the nonclustered index. The following query is covered by the nonclustered index on &lt;EM&gt;CustomerID&lt;/EM&gt;, because the nonclustered index always includes the clustered key, in this case, &lt;EM&gt;SalesOrderID&lt;/EM&gt;.&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;SELECT CustomerID, SalesOrderID &lt;BR&gt;FROM Sales.SalesOrderHeader&lt;BR&gt;ORDER BY CustomerID&lt;/FONT&gt; 
&lt;P&gt;&lt;A href="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityUsingaNonclusteredIndextoAvoidaS_E2B4/image_4.png"&gt;&lt;IMG style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height=57 alt=image src="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityUsingaNonclusteredIndextoAvoidaS_E2B4/image_thumb_1.png" width=244 border=0&gt;&lt;/A&gt; 
&lt;P&gt;But what about if the query is not covered? What if we wanted every column returned:&lt;/P&gt;&lt;FONT face="Courier New" size=1&gt;SELECT * FROM Sales.SalesOrderHeader&lt;BR&gt;ORDER BY CustomerID&lt;/FONT&gt; 
&lt;P&gt;The default plan for this query will be to perform a sort.&lt;/P&gt;
&lt;P&gt;&lt;A href="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityUsingaNonclusteredIndextoAvoidaS_E2B4/image_6.png"&gt;&lt;IMG style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height=54 alt=image src="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityUsingaNonclusteredIndextoAvoidaS_E2B4/image_thumb_2.png" width=244 border=0&gt;&lt;/A&gt; &lt;/P&gt;
&lt;P&gt;However, the leaf level of the nonclustered index on &lt;EM&gt;CustomerID&lt;/EM&gt; does have all the &lt;EM&gt;CustomerID&lt;/EM&gt; values already sorted, so why can't that index be used? The answer is, it CAN be used, but it just isn't the default. SQL Server's optimizer tries to find the plan that will run to completion in the least amount of time. That sounds good, right? But with the SORT operator in the plan, everything stops while the sorting is taking place, and no data can be returned until all the data is sorted. &lt;/P&gt;
&lt;P&gt;However,&amp;nbsp; another alternative would be to scan the nonclustered index, where the &lt;EM&gt;CustomerID&lt;/EM&gt; values are already in order. For each row, SQL Server would have to do a key lookup into the clustered index and the total time to do a key lookup for every row would probably be more than the time required to sort all the data. The first few rows can be returned very quickly. How can we get such a plan? SQL Server provides us with a hint called FASTFIRSTROW that tells the optimizer to come up with a plan that returns the first row in a minimum amount of time. It's a table hint, so it looks like this:&lt;/P&gt;&lt;FONT face="Courier New" size=1&gt;SELECT * FROM Sales.SalesOrderHeader WITH (FASTFIRSTROW)&lt;BR&gt;ORDER BY CustomerID&lt;/FONT&gt; 
&lt;P&gt;The plan looks like this:&lt;/P&gt;
&lt;P&gt;&lt;A href="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityUsingaNonclusteredIndextoAvoidaS_E2B4/image_8.png"&gt;&lt;IMG style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height=66 alt=image src="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityUsingaNonclusteredIndextoAvoidaS_E2B4/image_thumb_3.png" width=244 border=0&gt;&lt;/A&gt; &lt;/P&gt;
&lt;P&gt;So you have to decide what is most important to you.&amp;nbsp; Do you want the total processing time to be minimized, or do you want the time to have the first row returned to be minimized? It's up to you. The default is to minimize total processing time, but you can&amp;nbsp; use the FASTFIRSTROW hint if you want to take advantage of the nonclustered index to avoid the sort, and have the first few rows returned quickly.&lt;/P&gt;
&lt;P&gt;Be careful if you try to do a cost comparison of queries with and without the FASTFIRSTROW hint. Look at the two plans below:&lt;/P&gt;
&lt;P&gt;&lt;A href="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityUsingaNonclusteredIndextoAvoidaS_E2B4/image_10.png"&gt;&lt;IMG style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height=106 alt=image src="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityUsingaNonclusteredIndextoAvoidaS_E2B4/image_thumb_4.png" width=244 border=0&gt;&lt;/A&gt; &lt;/P&gt;
&lt;P&gt;Comparing the two plans makes it look like the query with the hint is infinitely faster the query without the hint. However, if you look at the details for the nonclustered index scan, as shown in the pop-up properties box, you can see that the way the optimizer comes up with this plan is by assuming only one row will be returned. It optimizes as if only one row will be accessed, which is why the nonclustered index is chosen, but during execution it will retrieve all the rows. So of course a plan for accessing one row will be considered MUCH faster than a plan to access the entire table (31465 rows).&lt;/P&gt;
&lt;P&gt;The documentation for this hint can be a little misleading. You might think using the hint indicates that the plan should get the first row quickly, and then get all the rest of rows using perhaps a different access method, so they will ALL come back as quickly as possible (which would then mean a sort), it actually means that the optimizer should just come up with a plan for getting the first row as quickly as possible. Period. (Then whatever plan was chosen for the first row will also be used for all the rest of the rows.)&lt;/P&gt;
&lt;P&gt;This FASTFIRSTROW hint is listed in the SQL Server Books Online as a deprecated feature, which means it may be removed in a future version. However, it is still available in SQL Server 2008.&amp;nbsp; Microsoft recommends using the FAST N query hint (in the OPTION clause): &lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;SELECT * FROM Sales.SalesOrderHeader &lt;BR&gt;ORDER BY CustomerID&lt;BR&gt;OPTION (FAST 1)&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;With this hint, you can specify an value for N, and the optimizer just assumes there are N rows, and comes up with the best plan as if there were that number. As an exercise, you might want to try determining at what value for N the optimizer will switch from using an nonclustered index scan to using a clustered scan plus SORT.&lt;/P&gt;
&lt;P&gt;Today, a reader asked me a question about the FASTFIRSTROW hint and wanted to know if we should "use such a hint for large data sets [e.g. OLTP queries]".&lt;/P&gt;
&lt;P&gt;Of course, the answer is the usual: It depends.&lt;/P&gt;
&lt;P&gt;On the one hand, using this hint to avoid a sort can be a good thing, because sorting a large data set can use a lot of system resources (time and &lt;EM&gt;tempdb&lt;/EM&gt;). But on the other hand, if you're really running OLTP queries, there should only be a few rows you're dealing with in any query, and then the value of this hint might not be as noticeable. So you should run your own tests and see if you like the results. Note that FAST N or FASTFIRSTROW is not the default, and that is probably for a good reason. Try running your queries first with whatever plan the optimizer comes us with, and only if you're not satisfied with the performance, you can try using a hint.&lt;/P&gt;
&lt;P&gt;Have fun!&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#ff00ff size=4&gt;~Kalen&lt;/FONT&gt;&lt;/P&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=10245" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/index+hints/default.aspx">index hints</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/nonclustered+index/default.aspx">nonclustered index</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/sort/default.aspx">sort</category></item><item><title>Geek City QUIZ: What happens when you change a column in an index from a key column to an included column?</title><link>http://www2.sqlblog.com/blogs/kalen_delaney/archive/2008/11/24/change-a-column-in-an-index-from-a-key-column-to-an-included-column.aspx</link><pubDate>Mon, 24 Nov 2008 07:15:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:10065</guid><dc:creator>Kalen Delaney</dc:creator><slash:comments>5</slash:comments><comments>http://www2.sqlblog.com/blogs/kalen_delaney/comments/10065.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/kalen_delaney/commentrss.aspx?PostID=10065</wfw:commentRss><description>&lt;P&gt;This quiz is not really a generic question about changing an index key column to be an included column; it's about a behavior noticed by a reader in one particular query.&amp;nbsp; Dejan Nakarada-Kordic from New Zealand sent me a very interesting puzzle. He had a reproducible query for which he thought an existing nonclustered index should be used, and it only ended up being used if the column was defined as an INCLUDED column instead of a key column. Here is the script Dejan sent me:&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;-- First, create the table&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=1&gt;IF&amp;nbsp; EXISTS (SELECT * FROM sys.tables &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE schema_id = 1 and name = 'Table1') &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP TABLE dbo.Table1;&lt;BR&gt;GO&lt;BR&gt;CREATE TABLE dbo.Table1(&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Transaction_Serial_No [bigint IDENTITY(1,1) NOT NULL,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Channel] char(4) NOT NULL,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Retry_Counter int NOT NULL,&lt;BR&gt;CONSTRAINT PK_Table1 PRIMARY KEY CLUSTERED &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (Transaction_Serial_No)); &lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=1&gt;GO &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;--populate the table with 10000 rows&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=1&gt;SET NOCOUNT ON;&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=1&gt;declare @counter int;&lt;BR&gt;set @counter = 0;&lt;BR&gt;while (@counter &amp;lt; 10000)&lt;BR&gt;begin&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; set @counter = @counter + 1;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; insert into&amp;nbsp; dbo.Table1(channel, retry_counter)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; values('ch1', 0);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; insert into&amp;nbsp; dbo.Table1(channel,&amp;nbsp; retry_counter)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; values('ch2', 0);&lt;BR&gt;end;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;-- Now build a nonclustered index on &lt;BR&gt;IF&amp;nbsp; EXISTS (SELECT * FROM sys.indexes &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE object_id = OBJECT_ID('dbo.Table1') AND name = 'IX_Channel')&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP INDEX IX_Channel ON dbo.Table1;&lt;BR&gt;GO&lt;BR&gt;CREATE NONCLUSTERED INDEX IX_Channel ON dbo.Table1 (Channel, Retry_Counter);&lt;BR&gt;GO&lt;BR&gt;&lt;BR&gt;-- Examine the query plan for this query:&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=1&gt;SELECT TOP(20) Transaction_Serial_No&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM dbo.Table1&amp;nbsp; &lt;BR&gt;WHERE Channel = 'ch2'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; AND Retry_Counter &amp;lt;= 10&amp;nbsp; &lt;BR&gt;ORDER BY Transaction_Serial_No;&lt;/FONT&gt;&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;The plan should show you a Clustered Index Scan being performed, even though the nonclustered index appears to be a covering index. &lt;/P&gt;
&lt;P&gt;&lt;A href="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityQUIZWhathappenswhenyouchangeacol_13A9B/image_2.png"&gt;&lt;IMG style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height=67 alt=image src="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityQUIZWhathappenswhenyouchangeacol_13A9B/image_thumb.png" width=261 border=0&gt;&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;The query contains all 3 columns used in the query. The columns &lt;EM&gt;Channel&lt;/EM&gt; and &lt;EM&gt;Retry_Counter&lt;/EM&gt; are defined index keys, and because the table has a clustered index, the clustered key &lt;EM&gt;Transaction_Serial_No&lt;/EM&gt; is also part of the index. &lt;/P&gt;
&lt;P&gt;If you rebuild the index to use &lt;EM&gt;Retry_Counter&lt;/EM&gt; as an INCLUDED column, the plan changes.&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;CREATE NONCLUSTERED INDEX IX_Channel ON dbo.Table1 (Channel)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; INCLUDE ( Retry_Counter)&amp;nbsp; WITH&amp;nbsp;&amp;nbsp; (DROP_EXISTING = ON);&lt;/FONT&gt; 
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;-- Examine the plan again:&lt;/FONT&gt; 
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;SELECT TOP(20) Transaction_Serial_No&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM dbo.Table1&amp;nbsp; &lt;BR&gt;WHERE Channel = 'ch2'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; AND Retry_Counter &amp;lt;= 10&amp;nbsp; &lt;BR&gt;ORDER BY Transaction_Serial_No;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;The plan now shows a nonclustered index seek:&lt;/P&gt;
&lt;P&gt;&lt;A href="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityQUIZWhathappenswhenyouchangeacol_13A9B/image_4.png"&gt;&lt;IMG style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height=48 alt=image src="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityQUIZWhathappenswhenyouchangeacol_13A9B/image_thumb_1.png" width=244 border=0&gt;&lt;/A&gt; &lt;/P&gt;
&lt;P&gt;WHY should changing the column from a key column to an INCLUDED column change the plan?&lt;/P&gt;
&lt;P&gt;You can try to figure out the answer for yourself, or read on. &lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;-------------------------------------------------------------------------------------------------------------------------&lt;/P&gt;
&lt;P&gt;It turns out that there are two extra factors to consider. &lt;/P&gt;
&lt;P&gt;1) Although covering indexes are a great thing, and the optimizer will choose them over other possible indexes most of the time, the cost of sorting can sometimes outweigh the benefit of a covering index.&lt;/P&gt;
&lt;P&gt;2) When a column is an INCLUDED column in an index, it comes after all the key columns and after the clustered index key that is always part of a nonclustered index on a table that has a clustered index.&lt;/P&gt;
&lt;P&gt;So, the original index on (Channel, Retry_Counter) is the same as an index on (Channel, Retry_Counter, Transaction_Serial_No), but moving &lt;EM&gt;Retry_Counter&lt;/EM&gt; to be an INCLUDED column changes the index to be on the columns (Channel,&amp;nbsp; Transaction_Serial_No), with Retry_Counter in last place, and unsorted.&lt;/P&gt;
&lt;P&gt;The query wants the data sorted on &lt;EM&gt;Transaction_Serial_No&lt;/EM&gt;, and scanning the clustered index gives us the data in &lt;EM&gt;Transaction_Serial_No&lt;/EM&gt; order.&amp;nbsp; The first&amp;nbsp; nonclustered index is sorted first by &lt;EM&gt;Channel&lt;/EM&gt;, then by &lt;EM&gt;Retry_Counter&lt;/EM&gt;, then by &lt;EM&gt;Transaction_Serial_No&lt;/EM&gt; so that index is not useful for the sort.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;The second nonclustered index is sorted first by &lt;EM&gt;Channel&lt;/EM&gt;, then by &lt;EM&gt;Transaction_Serial_No. &lt;/EM&gt;BUT the query has limited the data to only data with the &lt;EM&gt;Channel&lt;/EM&gt; value equal to 'ch2', so since the first column values we're retrieving are all the same, returning data in the nonclustered index order will return the data in &lt;EM&gt;Transaction_Serial_No&lt;/EM&gt; order and no sorting needs to be done. &lt;/P&gt;
&lt;P&gt;You might notice in this data set that all the values for &lt;EM&gt;Retry_Counter&lt;/EM&gt; are also identical, so you might then think we should be able to use the first nonclustered index to avoid having to sort the data, but the optimizer cannot be sure that the &lt;EM&gt;Retry_Counter&lt;/EM&gt; values are all identical. The statistics might indicate that all the values are the same, but the optimizer can't be sure the statistics are 100% up to date. &lt;/P&gt;
&lt;P&gt;Here are some things to try:&lt;/P&gt;
&lt;P&gt;1) If you remove the ORDER BY, you will see that the first index, with no INCLUDED column, is used.&lt;/P&gt;
&lt;P&gt;2) If you change the second condition to searching for a constant (AND Retry_Counter = 0), the first index can be used. If both of the first two index columns are constants in the retrieved data, it will be sorted by the third column (&lt;EM&gt;Transaction_Serial_No&lt;/EM&gt;).&lt;/P&gt;
&lt;P&gt;3) If you force the query to use the nonclustered index when &lt;EM&gt;Retry_Counter&lt;/EM&gt; is a key, you'll see that SQL Server has to do a sort:&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;DROP INDEX IX_Channel ON dbo.Table1;&lt;BR&gt;GO&lt;BR&gt;CREATE NONCLUSTERED INDEX IX_Channel ON dbo.Table1 (Channel, Retry_Counter);&lt;BR&gt;GO&lt;BR&gt;SELECT TOP(20) Transaction_Serial_No&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM dbo.Table1 WITH (index = ix_channel)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE Channel = 'ch2'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; AND Retry_Counter &amp;lt;=&amp;nbsp; 10&lt;BR&gt;ORDER BY Transaction_Serial_No;&lt;BR&gt;GO&lt;/FONT&gt; 
&lt;P&gt;&lt;BR&gt;&lt;A href="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityQUIZWhathappenswhenyouchangeacol_13A9B/image_6.png"&gt;&lt;IMG style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height=47 alt=image src="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityQUIZWhathappenswhenyouchangeacol_13A9B/image_thumb_2.png" width=244 border=0&gt;&lt;/A&gt; &lt;/P&gt;
&lt;P&gt;Notice that the SORT operation is estimated to be 95% of the total query cost, so SQL Server would definitely like to avoid that if possible.&amp;nbsp;&lt;/P&gt;
&lt;P&gt;So the issue wasn't really because of INCLUDED columns, it was because of the order the columns appeared in the index, and the fact that SQL Server wants to try to avoid sorting whenever possible.&lt;/P&gt;
&lt;P&gt;Have fun!&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#ff00ff size=4&gt;~Kalen&lt;/FONT&gt;&lt;/P&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=10065" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/sort/default.aspx">sort</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/included+columns/default.aspx">included columns</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/indexes/default.aspx">indexes</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/query+tuning/default.aspx">query tuning</category></item><item><title>Did You Know? Forcing a nonclustered index scan to avoid sorting</title><link>http://www2.sqlblog.com/blogs/kalen_delaney/archive/2007/07/22/did-you-know-forcing-a-nonclustered-index-scan-to-avoid-sorting.aspx</link><pubDate>Sun, 22 Jul 2007 17:13:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:1876</guid><dc:creator>Kalen Delaney</dc:creator><slash:comments>3</slash:comments><comments>http://www2.sqlblog.com/blogs/kalen_delaney/comments/1876.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/kalen_delaney/commentrss.aspx?PostID=1876</wfw:commentRss><description>&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Hopefully, if you know enough about&amp;nbsp;SQL Server to understand most of my posts here, you're aware of the fact that using a&amp;nbsp;nonclustered index is not always an optimal choice for accessing data, particularly if there are thousands&amp;nbsp;(or more) rows of data you need to retrieve.&amp;nbsp;&amp;nbsp;However, we should never say 'never'. &lt;/P&gt;
&lt;P&gt;In general, if you have a query that is sorting on a column that has a nonclustered index, and needs to retrieve columns that are not part of the index, the optimizer will choose to scan the table (or clustered index, which is the same thing) and sort the data. The optimizer usually estimates that the time needed to scan the table plus the time to sort is going to be less than the time to follow each bookmark individually from the nonclustered index. And in some cases it might be right. &lt;/P&gt;
&lt;P&gt;In other cases, it might not be, and the only way to verify that is to force SQL Server to use the nonclustered index, with a Lookup into the table or clustered index for each row.&amp;nbsp; However, if you try to force this behavior with an INDEX hint, you might be surprised. Using a hint that tells the optimizer what index to use does not tell the optimizer HOW to use that index.&amp;nbsp; It might perform a scan with lookup for every single row, or it might scan the nonclustered index, sort the index rows in the order of the clustered index&amp;nbsp;and then access the clustered index using the sorted data. I've seen other plans as well and in this post, I'm not going to what the different plans actually mean. &lt;/P&gt;
&lt;P&gt;What I want to tell you now is a way to force SQL Server to perform a scan of the nonclustered index and then a bookmark lookup for every row. You can do that with a table hint called FASTFIRSTROW. This hint tells the optimizer to choose a plan that returns the first row as quickly as possible. The default plan has to sort the entire table before any rows are returned at all. However, the nonclustered index already has the nonclustered key values sorted, and if that is the column used in the ORDER BY, following the leaf level of the nonclustered index should give us the rows already in correct sorted order. Yes, in some cases it might take longer to get ALL the rows in the table, following the bookmark for each row, but the first row will always be available fast. &lt;/P&gt;
&lt;P&gt;It turns out that there are some cases where the FASTFIRSTROW hint can actually return all the rows more quickly than the default SORT. If you have lots of rows in your table (hundreds of thousands, or millions) you might find the FASTFIRSTROW hint ends up being faster. The only way to know for sure is to test it with your data. &lt;/P&gt;
&lt;P&gt;Itzik Ben-Gan did some testing with different ways of getting sorted data, and if you are a subscriber to SQL Server Magazine, you can read about his tests here:&lt;/P&gt;
&lt;P&gt;&lt;A title=http://www.sqlmag.com/Articles/Index.cfm?ArticleID=94775&amp;amp;DisplayTab=Article href="http://www.sqlmag.com/Articles/Index.cfm?ArticleID=94775&amp;amp;DisplayTab=Article"&gt;http://www.sqlmag.com/Articles/Index.cfm?ArticleID=94775&amp;amp;DisplayTab=Article&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;If you want to run some tests of your own, here is some code to get you started.&lt;/P&gt;
&lt;P&gt;-- First, make a copy of the Sales.SalesOrderDetail table in the AdventureWorks database&lt;/P&gt;
&lt;P&gt;USE AdventureWorks&lt;BR&gt;GO&lt;BR&gt;IF OBJECT_ID('dbo.details', 'U') IS NOT NULL&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP TABLE dbo.details &lt;BR&gt;GO&lt;BR&gt;CREATE TABLE details (&lt;BR&gt;[SalesOrderID] [int] NOT NULL,&lt;BR&gt;[SalesOrderDetailID] [int] NOT NULL,&lt;BR&gt;[CarrierTrackingNumber] [nvarchar](25) NULL,&lt;BR&gt;[OrderQty] [smallint] NOT NULL,&lt;BR&gt;[ProductID] [int] NOT NULL,&lt;BR&gt;[SpecialOfferID] [int] NOT NULL,&lt;BR&gt;[UnitPrice] [money] NOT NULL,&lt;BR&gt;[UnitPriceDiscount] [money] NOT NULL CONSTRAINT [DF_SalesOrderDetail_UnitPriceDiscount] DEFAULT ((0.0)),&lt;BR&gt;[LineTotal] AS (isnull(([UnitPrice]*((1.0)-[UnitPriceDiscount]))*[OrderQty],(0.0))),&lt;BR&gt;[rowguid] [uniqueidentifier] ROWGUIDCOL NOT NULL CONSTRAINT [DF_SalesOrderDetail_rowguid] DEFAULT (newid()),&lt;BR&gt;[ModifiedDate] [datetime] NOT NULL CONSTRAINT [DF_SalesOrderDetail_ModifiedDate] DEFAULT (getdate()),&lt;BR&gt;)&lt;BR&gt;GO 
&lt;P&gt;-- Next, insert data into the new table 
&lt;P&gt;INSERT INTO details &lt;BR&gt;(SalesOrderID, SalesOrderDetailID, CarrierTrackingNumber, OrderQty, ProductID, SpecialOfferID,&lt;BR&gt;UnitPrice, UnitPriceDiscount, rowguid, ModifiedDate) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT SalesOrderID, SalesOrderDetailID, CarrierTrackingNumber, OrderQty, ProductID, SpecialOfferID,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; UnitPrice, UnitPriceDiscount, rowguid, ModifiedDate&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM Sales.SalesOrderDetail&lt;BR&gt;GO 
&lt;P&gt;-- Now create a clustered and nonclustered index on the new details table 
&lt;P&gt;CREATE CLUSTERED INDEX&lt;BR&gt;idx_cl_col1 ON dbo.details(SalesOrderID);&lt;BR&gt;CREATE NONCLUSTERED INDEX&lt;BR&gt;idx_nc_col2 ON dbo.details(ProductID);&lt;BR&gt;GO&lt;/P&gt;
&lt;P&gt;-- First look at the plan with no hints&lt;/P&gt;
&lt;P&gt;SELECT * FROM dbo.details&amp;nbsp;&amp;nbsp;&lt;BR&gt;&amp;nbsp; &amp;nbsp;ORDER BY ProductID &lt;/P&gt;
&lt;P&gt;-- Now look at the plans with the two different index hints.&lt;/P&gt;
&lt;P&gt;SELECT * FROM dbo.details &lt;BR&gt;WITH (FASTFIRSTROW) &lt;BR&gt;ORDER BY ProductID 
&lt;P&gt;SELECT * FROM dbo.details &lt;BR&gt;WITH&amp;nbsp; (INDEX (idx_nc_col2)) &lt;BR&gt;ORDER BY ProductID 
&lt;P&gt;-- As you add more rows to your table, the plan for the query using the INDEX hint might change. &lt;BR&gt;-- You can add more rows by just rerunning the INSERT statement from above, as many times as you would like. 
&lt;P&gt;Have fun!&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#ff00ff&gt;~Kalen&lt;/FONT&gt;&lt;/P&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=1876" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/index+hints/default.aspx">index hints</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/nonclustered+index/default.aspx">nonclustered index</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/sort/default.aspx">sort</category></item></channel></rss>