<?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>Search results matching tag 'nonclustered index'</title><link>http://www2.sqlblog.com/search/SearchResults.aspx?o=DateDescending&amp;tag=nonclustered+index&amp;orTags=0</link><description>Search results matching tag 'nonclustered index'</description><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP2 (Build: 61129.1)</generator><item><title>Geek City: Join With Me!</title><link>http://www2.sqlblog.com/blogs/kalen_delaney/archive/2013/02/14/index-intersection.aspx</link><pubDate>Thu, 14 Feb 2013 22:16:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:47727</guid><dc:creator>Kalen Delaney</dc:creator><description>&lt;p&gt;I remember one of the most surprising changes in SQL Server &lt;strike&gt;2000&lt;/strike&gt; 2005 was how the graphical plans showed the use of a nonclustered index to seek, and the plan included something that looked like a JOIN to find rows in the base table.&amp;nbsp; Here’s an example. Although I used SQL Server 2008R2, the graphical plan will be pretty similar to what it looked like back in SQL Server &lt;strike&gt;2000&lt;/strike&gt; 2005. My code will make a copy of a table in the &lt;em&gt;AdventureWorks2008&lt;/em&gt; database, and then build an index on one of the columns.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face="Courier New"&gt;USE AdventureWorks2008;        &lt;br&gt;GO&lt;br&gt;IF object_id('dbo.Sales') IS NOT NULL         &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP TABLE dbo.Sales;         &lt;br&gt;GO         &lt;br&gt;SELECT * INTO dbo.Sales FROM Sales.SalesOrderHeader;         &lt;br&gt;GO         &lt;br&gt;CREATE INDEX Sales_SalesPersonID_index on dbo.Sales(SalesPersonID);         &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Now look at the estimated graphical execution plan for this query, that searches for a particular value for the &lt;em&gt;SalesPersonID&lt;/em&gt; column:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face="Courier New"&gt;SELECT * FROM dbo.Sales        &lt;br&gt;WHERE SalesPersonID = 280;         &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;You should see something like this:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://sqlblog.com/blogs/kalen_delaney/image_0F9109CC.png"&gt;&lt;img style="border-width:0px;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/kalen_delaney/image_thumb_47634DEF.png" width="507" height="260"&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;It shows a JOIN even though there is only one table involved! What’s with that? It took me a few minutes to figure out what I was seeing, and I must admit that over the years since then I have come to appreciate this graphical representation. You’ll have a problem if you think of a JOIN as only finding matching rows between two tables, but can be solved if you realize that a JOIN can be used when finding matches between any two ROWSETS, i.e. any two sets of rows. There is a set of rows in the nonclustered index on &lt;em&gt;SalesPersonID&lt;/em&gt;, and a set of rows in the table. The index seek finds all the index rows with a &lt;em&gt;SalesPersonID&lt;/em&gt; value of 280, but those index rows only contain an index key value and a row pointer (RowID or RID). Because the query is requesting all the columns in the table, not just the &lt;em&gt;SalesPersonID&lt;/em&gt; value,&amp;nbsp; SQL Server must find the rows in the&lt;em&gt; dbo.Sales&lt;/em&gt; table that have a matching value for the RowID. Internally, it is doing a join, even though there is only one table. It is joining index rows with table rows.&lt;/p&gt;  &lt;p&gt;If the table had a clustered index, the plan would look almost exactly the same, with one difference. In SQL Server, if a table has a clustered index, nonclustered index rows do not contain RowIDs, they contain the clustered key value to be used a pointer to the row. SQL Server can take this clustered key from the nonclustered index row and look up in the clustered index to find the desired row. So I create a clustered index on &lt;em&gt;SalesOrderNumber&lt;/em&gt;:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face="Courier New"&gt;CREATE UNIQUE CLUSTERED INDEX Sales_ident on dbo.Sales(SalesOrderNumber);        &lt;br&gt;GO&lt;/font&gt;       &lt;br&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Now when I run the same query as above, I get the following plan:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://sqlblog.com/blogs/kalen_delaney/image_6D58FE45.png"&gt;&lt;img style="border-width:0px;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/kalen_delaney/image_thumb_7EC95F1D.png" width="513" height="237"&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The only difference in the two plans above is the icon for the lookup into the base table.&amp;nbsp; One is looking up into a heap using a RowID, which is an address containing the File ID, the page number and the slot or row number on the page. The other icon represents taking a key value from the nonclustered index row and looking it up by seeking through the clustered index. If you look again at these two icons, which one looks like a picture of directly addressing a row, and which looks like a picture of following another index into the table itself?&lt;/p&gt;    &lt;p&gt;&lt;a href="http://sqlblog.com/blogs/kalen_delaney/image_7DF0F933.png"&gt;&lt;img style="border-width:0px;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/kalen_delaney/image_thumb_4B20F2BF.png" width="220" height="88"&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The icon on the left is used for a RID lookup, and the icon on the right is used for a clustered index key lookup. I know that nobody asked me, but I think they got these backwards.&lt;/p&gt;  &lt;p&gt;But wait, there’s more… &lt;/p&gt;  &lt;p&gt;Prior to SQL Server 7, there was basically a rule of thumb with query plans that SQL Server would use one index (at most) per table per query. It could never (well, hardly ever) use two indexes on the same table in a single query. That changed in SQL Server 7 when a new algorithm called Index Intersection was introduced. &lt;/p&gt;  &lt;p&gt;I’m going to build another index on &lt;em&gt;SalesOrderDate&lt;/em&gt;:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face="Courier New"&gt;CREATE INDEX Sales_OrderDate_index on dbo.Sales(OrderDate);        &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;This index will be used if I look for all orders place on July 1, 2002. The query plan for the following query is shown:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;     &lt;br&gt;&lt;font face="Courier New"&gt;SELECT * FROM dbo.Sales        &lt;br&gt;WHERE OrderDate = '2002-07-01';&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;&lt;a href="http://sqlblog.com/blogs/kalen_delaney/image_4A488CD5.png"&gt;&lt;img style="border-width:0px;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/kalen_delaney/image_thumb_09A64066.png" width="491" height="238"&gt;&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;We saw that the index on &lt;em&gt;SalesPersonID&lt;/em&gt; is useful when looking for the value 280, and the index on &lt;em&gt;SalesOrderDate&lt;/em&gt; is useful when looking for '2002-07-01'. But what if one query looks for rows that satisfy both conditions? &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face="Courier New"&gt;SELECT * FROM dbo.Sales       &lt;br&gt;WHERE OrderDate = '2002-07-01'         &lt;br&gt;AND SalesPersonID = 280&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Here’s the plan:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://sqlblog.com/blogs/kalen_delaney/image_68B2CDBE.png"&gt;&lt;img style="border-width:0px;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/kalen_delaney/image_thumb_07F57492.png" width="573" height="239"&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;We see two JOINs ! First, SQL Server finds the sets of index rows meeting each of the WHERE filter conditions and those two index row sets are joined together, using the clustered key value as the join column. Then only after SQL Server finds rows that meets both conditions, it takes the common clustered index key and uses it to find the row in the underlying table. &lt;/p&gt;  &lt;p&gt;But wait, there’s more!&lt;/p&gt;  &lt;p&gt;Both of these queries use a table scan, because the filter conditions are not selective enough. There are way more rows with a &lt;em&gt;SalesPersonID&lt;/em&gt; value of 289 than there were for a value of 280, and there are more rows with an &lt;em&gt;OrderDate&lt;/em&gt; of March 1, 2004 than there are with an &lt;em&gt;OrderDate&lt;/em&gt; of July 1, 2002.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face="Courier New"&gt;SELECT * FROM dbo.Sales        &lt;br&gt;WHERE OrderDate = '2004-03-01' ;        &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Courier New"&gt;SELECT * FROM dbo.Sales        &lt;br&gt;WHERE SalesPersonID = 289;         &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;    &lt;p&gt;Both of the above queries show the same plan. Because the table now has a clustered index, the plan shows a clustered index scan, but it is exactly the same work as a table scan:&lt;/p&gt;    &lt;p&gt;&lt;a href="http://sqlblog.com/blogs/kalen_delaney/image_18F9A275.png"&gt;&lt;img style="border:0px currentColor;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/kalen_delaney/image_thumb_787262C2.png" width="368" height="98"&gt;&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;However, if you look at the plan for a query that uses both filter conditions, we don’t get a clustered index scan.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face="Courier New"&gt;SELECT * FROM dbo.Sales        &lt;br&gt;WHERE OrderDate = '2004-03-01'         &lt;br&gt;AND SalesPersonID = 289;&lt;/font&gt; &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;&lt;a href="http://sqlblog.com/blogs/kalen_delaney/image_65BD690B.png"&gt;&lt;img style="border:0px currentColor;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/kalen_delaney/image_thumb_44C9F664.png" width="568" height="243"&gt;&lt;/a&gt;&lt;/p&gt;      &lt;p&gt;Even though each filter alone is not very restrictive, they can both be used for index intersection. The reason SQL Server usually doesn’t choose index seeks on non-restrictive filters is because the cost of then accessing the base table for all the qualifying rows can be very high. But even though each individual index returns a lot of rows, the optimizer’s estimate of the number of rows that meet BOTH conditions is very small, and therefore the number of lookups into the base table is quite small and affordable. &lt;/p&gt;  &lt;p&gt;So, what can you learn? Just because you think an column might not be selective enough to build a nonclustered index on, don’t discount it.&amp;nbsp; If that column will frequently be used in queries with multiple conditions, it just might turn out to be more useful than you thought! &lt;/p&gt;  &lt;p&gt;So make sure you test many possible choices of indexes and combinations of indexes before you know what the best choices are.&lt;/p&gt;  &lt;p&gt;Have fun!&lt;/p&gt;  &lt;p&gt;&lt;font color="#8064a2" size="4"&gt;&lt;strong&gt;~Kalen&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;</description></item><item><title>Geek City: Too Many Indexes!</title><link>http://www2.sqlblog.com/blogs/kalen_delaney/archive/2009/01/18/too-many-indexes.aspx</link><pubDate>Sun, 18 Jan 2009 20:37:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:11244</guid><dc:creator>Kalen Delaney</dc:creator><description>&lt;P&gt;I wrote about &lt;A href="http://sqlblog.com/blogs/kalen_delaney/archive/2008/09/20/too-many-columns.aspx" target=_blank&gt;"Too Many Columns"&lt;/A&gt; last September, and along with changes in SQL Server 2008 that allow lots of columns, there is also the ability to create more than the old maximum of 249 nonclustered indexes on a table. I knew this fact, but somehow I overlooked it when updating the chapter on table structures in my new book. My omission also got past all the editors, and I just realized my mistake when reviewing the Indexes chapter, which was being updated for SQL 2008 by Kimberly Tripp.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;I found that Books Online has not been completely updated (and I have filed a doc bug on this already). This page still says the limit is 249&amp;nbsp; nonclustered indexes per table:&lt;/P&gt;
&lt;P&gt;&lt;A title=http://msdn.microsoft.com/en-us/library/ms190197.aspx href="http://msdn.microsoft.com/en-us/library/ms190197.aspx"&gt;http://msdn.microsoft.com/en-us/library/ms190197.aspx&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;However, the page for CREATE TABLE at &lt;A title=http://msdn.microsoft.com/en-us/library/ms174979.aspx href="http://msdn.microsoft.com/en-us/library/ms174979.aspx"&gt;http://msdn.microsoft.com/en-us/library/ms174979.aspx&lt;/A&gt; does say:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;Each table can contain a maximum of 999 nonclustered indexes, and 1 clustered index. These include the indexes generated to support any PRIMARY KEY and UNIQUE constraints defined for the table.&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;You can take my script to create a table with lots of columns, and edit it to create a UNIQUE constraint on every column. 
&lt;P&gt;&lt;FONT face="Courier New" size=2&gt;DECLARE @create varchar(max);&lt;BR&gt;DECLARE @tabname sysname;&lt;BR&gt;DECLARE @numcols int; &lt;BR&gt;DECLARE @col int;&lt;BR&gt;SELECT @numcols = 900;&lt;BR&gt;SELECT @tabname = 'wide' + CONVERT(varchar, @numcols); &lt;/FONT&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=2&gt;SELECT @create = 'CREATE TABLE ' + @tabname + &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ' (ID int IDENTITY, '; &lt;/FONT&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=2&gt;SELECT @col = 1;&lt;BR&gt;WHILE @col &amp;lt; @numcols BEGIN&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IF (@col % 3) = 0 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT @create = @create + 'col' + CONVERT(varchar, @col) +&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ' int UNIQUE,';&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IF (@col % 3) = 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &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; SELECT @create = @create + 'col' + CONVERT(varchar, @col) +&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; ' char(5) UNIQUE,';&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; IF (@col % 3) = 2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &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; SELECT @create = @create + 'col' + CONVERT(varchar, @col) +&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; ' varchar(25) UNIQUE,';&lt;BR&gt;SELECT @col = @col + 1;&lt;BR&gt;END;&lt;BR&gt;SELECT @create = @create + 'col' + CONVERT(varchar, @col) + &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ' int default 0);'&lt;BR&gt;PRINT @create&amp;nbsp; &lt;BR&gt;EXECUTE (@create) &lt;/FONT&gt;
&lt;P&gt;After the table is created, you can examine &lt;EM&gt;sys.indexes&lt;/EM&gt;: 
&lt;P&gt;&lt;FONT face="Courier New" size=2&gt;SELECT name, index_id, type_desc&lt;BR&gt;FROM sys.indexes&lt;BR&gt;WHERE object_id = object_id('wide900');&lt;/FONT&gt; 
&lt;P&gt;You'll definitely see more than 249 indexes! In addition, you might note that there is a gap in the &lt;EM&gt;index_id&lt;/EM&gt; values. The values 251 - 255 are not used, and this is for backward compatibility. In much older versions, &lt;EM&gt;index_id&lt;/EM&gt; (or &lt;EM&gt;indid&lt;/EM&gt;) 255 was reserved for the LOB data belonging to a table, and the values from 251 - 254 were reserved. So that range is still not used. But you can see that I have &lt;EM&gt;index_id&lt;/EM&gt; values from 256 - 905 in this table. 
&lt;P&gt;So, thanks Kim~ 
&lt;P&gt;One more place that Books Online could use a little tweaking is in the section on Programmability Enhancements: 
&lt;P&gt;&lt;A title=http://msdn.microsoft.com/en-us/library/cc645577.aspx href="http://msdn.microsoft.com/en-us/library/cc645577.aspx"&gt;http://msdn.microsoft.com/en-us/library/cc645577.aspx&lt;/A&gt; 
&lt;P&gt;The section on "Wide Tables" seems to imply that you need to have an XML column set in order to get 1000 indexes. It doesn't actually say that, so it's not really a bug, but it does seem to imply it: 
&lt;BLOCKQUOTE&gt;
&lt;P&gt;Wide tables are tables that contain one or more column sets. A wide table can contain up to 30000 columns, 1000 indexes, and 30000 statistics.&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Because you can only reach the 30000 column limit with a wide table, it seems to imply that the 1000 index limit is only for wide tables. But as my script shows, it is not. You can have up to 1024 columns in a non-wide table, and you can now have indexes on every one of those columns. 
&lt;P&gt;But do you want that many? That's for another post, another time... 
&lt;P&gt;Have fun! 
&lt;P&gt;&lt;FONT color=#ff00ff size=4&gt;~Kalen&lt;/FONT&gt;&lt;/P&gt;</description></item><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><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;</description></item><item><title>Geek City: String Statistics</title><link>http://www2.sqlblog.com/blogs/kalen_delaney/archive/2008/02/13/string-statistics.aspx</link><pubDate>Wed, 13 Feb 2008 22:18:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:5065</guid><dc:creator>Kalen Delaney</dc:creator><description>&lt;P&gt;If you’ve ever run DBCC SHOW_STATISTICS,&amp;nbsp; you know you get 3 sections of information back. The first section is basic information about the last time the statistics were updated, the number of rows, the number of steps, etc. The second section is density information for each left-based subset of columns. The third section is the histogram for the first column in the statistics. I won't be going into detail on what any of these things mean (i.e. steps, density, histogram), but you can get lots more information from this whitepaper: 
&lt;P&gt;&lt;A href="http://www.microsoft.com/technet/prodtechnol/sql/2005/qrystats.mspx"&gt;Statistics Used by the Query Optimizer in Microsoft SQL Server 2005&lt;/A&gt; 
&lt;P&gt;What you might &lt;STRONG&gt;not&lt;/STRONG&gt; know is that you can get each of the three sections independently by adding an option to the DBCC command: 
&lt;P&gt;DBCC SHOW_STATISTICS ('AdventureWorks.Person.Contact',&lt;BR&gt;'IX_Contact_EmailAddress') &lt;B&gt;WITH STAT_HEADER&lt;/B&gt;; 
&lt;P&gt;DBCC SHOW_STATISTICS ('AdventureWorks.Person.Contact',&lt;BR&gt;'IX_Contact_EmailAddress') &lt;B&gt;WITH DENSITY_VECTOR&lt;/B&gt;; 
&lt;P&gt;DBCC SHOW_STATISTICS ('AdventureWorks.Person.Contact',&lt;BR&gt;'IX_Contact_EmailAddress') &lt;B&gt;WITH HISTOGRAM&lt;/B&gt;; 
&lt;P&gt;These options are documented as part of the DBCC SHOW_STATISTICS command for SQL Server 2005. These options were actually available in SQL Server 2000, but they just aren’t documented.&amp;nbsp; 
&lt;P&gt;In SQL Server 2005, the first section contains a column of output called "String Index", which I like to call "String Statistics". I just wrote an article for &lt;A href="http://www.sqlcommunity.com/"&gt;www.SQLCommunity.com&lt;/A&gt; about what these string statistics do for you. 
&lt;P&gt;&lt;A title=http://www.sqlcommunity.com/default.aspx?tabid=77&amp;amp;id=178 href="http://www.sqlcommunity.com/default.aspx?tabid=77&amp;amp;id=178"&gt;http://www.sqlcommunity.com/default.aspx?tabid=77&amp;amp;id=178&lt;/A&gt; 
&lt;P&gt;Have fun! 
&lt;P&gt;&lt;FONT color=#ff00ff&gt;~Kalen&lt;/FONT&gt;&lt;/P&gt;</description></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><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;</description></item></channel></rss>