{"id":1964,"date":"2015-03-10T00:00:00","date_gmt":"2015-03-10T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/identifying-and-solving-index-scan-problems\/"},"modified":"2026-03-18T14:22:25","modified_gmt":"2026-03-18T14:22:25","slug":"identifying-and-solving-index-scan-problems","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/identifying-and-solving-index-scan-problems\/","title":{"rendered":"SQL Server Index Scan: How to Find &#038; Fix Scan Problems"},"content":{"rendered":"<p>An index scan means SQL Server is reading every row in an index\u2019s leaf level to find the data a query needs &#8211; for a clustered index, that\u2019s a full table scan. In most cases, you want seeks (using the index tree to jump directly to matching rows), not scans. To identify scan problems, query the plan cache using sys.dm_exec_query_stats with sys.dm_exec_query_plan to find execution plans containing index scan operators. Then fix them by creating or modifying indexes so the query optimizer can seek instead of scan &#8211; typically by adding missing key columns, creating covering indexes with INCLUDE, or rewriting queries to make predicates SARGable. This guide walks through the full diagnostic and fix workflow using the AdventureWorks database.<\/p>\n<p><em>Last updated 7 April 2015<\/em><\/p>\n<div id=\"pretty\">\n<p>Index Scans generally aren&#8217;t the sort of thing you&#8217;d want to see in a query plan. An index scan means that all the leaf-level of the index was searched to find the information for the query: When the index is a clustered index, this is the same as scanning the entire table. With only a few exceptions, this isn&#8217;t good; we need to try to turn scans into seeks, which means retrieving the data by just using the index tree.<\/p>\n<p>In a previous article, <a href=\"https:\/\/www.simple-talk.com\/sql\/database-administration\/exploring-query-plans-in-sql\/\">&#8216;Exploring Query Plans in SQL&#8217;<\/a>, I explained how to find query plans that are doing index scans in the SQL Server query plan cache. So, what now? Now that we found the plans, what should we do?<\/p>\n<p>I will not attempt to explain the often complex task of optimizing query performance since there are many books about this subject. Instead, I&#8217;ll just give you a few preliminary guidelines on reducing the number of scans that you found in the plan cache after following the <a href=\"https:\/\/www.simple-talk.com\/sql\/database-administration\/exploring-query-plans-in-sql\/\">&#8216;Exploring Query Plans&#8217;<\/a> article.<\/p>\n<h1>Test Environment<\/h1>\n<p>We will use the <strong>AdventureWorks2012<\/strong> database but with a few new tables created by Adam Machanic in his blog post &#8216;Thinking Big (Adventure)&#8217;.We will simulate a few queries and retrieve them from the query plan cache, exactly as it would happen in your development or integration database-environment. Please be aware that it is wrong\u00a0 to do this sort of performance work on a production database because of the resources needed to check through the plan cache.<\/p>\n<p>To retrieve the queries from the plan cache, we will use some of the techniques that I explained in &#8216;Exploring Query Plans&#8217; article, but I&#8217;ll also use the table-valued function that returns all index scans in the plan cache that happened in a specific database, from &#8216;Checking on Plan Cache Warnings for a SQL Server Database&#8217;. With this combination, it will be easier to select only those scans from queries in AdventurWorks2012 database. Here is the function:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tCREATE FUNCTION ScanInCacheFromDatabase \n\t\t(\u00a0\u00a0\u00a0\u00a0 \n\t\t\u00a0\u00a0\u00a0\u00a0\u00a0 -- Add the parameters for the function here\n\t\t\u00a0\u00a0\u00a0\u00a0\u00a0 @DatabaseName varchar(50)\n\t\t)\n\t\tRETURNS TABLE \n\t\tAS\n\t\tRETURN \n\t\t(\n\t\twith XMLNAMESPACES\n\t\t(default 'http:\/\/schemas.microsoft.com\/sqlserver\/2004\/07\/showplan')\n\t\tselect qp.query_plan,qt.text, \n\t\tstatement_start_offset, statement_end_offset,\n\t\tcreation_time, last_execution_time,\n\t\texecution_count, total_worker_time,\n\t\tlast_worker_time, min_worker_time,\n\t\tmax_worker_time, total_physical_reads,\n\t\tlast_physical_reads, min_physical_reads,\n\t\tmax_physical_reads, total_logical_writes,\n\t\tlast_logical_writes, min_logical_writes,\n\t\tmax_logical_writes, total_logical_reads,\n\t\tlast_logical_reads, min_logical_reads,\n\t\tmax_logical_reads, total_elapsed_time,\n\t\tlast_elapsed_time, min_elapsed_time,\n\t\tmax_elapsed_time, total_rows,\n\t\tlast_rows, min_rows,\n\t\tmax_rows from sys.dm_exec_query_stats\n\t\tCROSS APPLY sys.dm_exec_sql_text(sql_handle) qt\n\t\tCROSS APPLY sys.dm_exec_query_plan(plan_handle) qp\n\t\twhere \n\t\tqp.query_plan.exist('\/\/RelOp[@LogicalOp=\"Index Scan\"\n\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 or @LogicalOp=\"Clustered Index Scan\"\n\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 or @LogicalOp=\"Table Scan\"]')=1\n\t\tand \n\t\tqp.query_plan.exist('\/\/ColumnReference[fn:lower-case(@Database)=fn:lower-case(sql:variable(\"@DatabaseName\"))]')=1\n\t\t)\n\t\tGO\n\t\t<\/pre>\n<p>All the concepts used in this function were explained in the previous articles.<\/p>\n<p>After creating the function we will run some queries and use the function to retrieve the plans from the cache. Below are the queries:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect\u00a0 TransactionID,ProductID,\n\t\tTransactionDate,Quantity,ActualCost \n\t\tfrom bigtransactionhistory \n\t\twhere\u00a0 month(transactionDate)=6 and year(transactiondate)=2008\n\t\tgo\n\t\tselect * from bigproduct where listprice between 100 and 200\n\t\tgo\n\t\tselect * from bigproduct where name like '%LL Road Rear%'\n\t\tgo\n\t\tselect firstname,lastname,PersonType from person.person\n\t\twhere firstname like '%Brianna%' and lastname like '%Bryant%'\n\t\tgo\n\t<\/pre>\n<p>Notice that we have a &#8216;Go&#8217; instruction after each query, otherwise we would have only one plan in the cache for the entire batch, instead of one plan for each query.<\/p>\n<p>Let&#8217;s take a look in the cache using the function we just created:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect query_plan,[text],total_worker_time \n\t\tfrom dbo.ScanInCacheFromDatabase('[AdventureWorks2012]')\n\t\torder by [total_worker_time] desc\n\t\t\n<\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(162)-97d901d5-1622-4c22-8564-0d44bdc9c087.png\" alt=\"2156-Screenshot%20(162)-97d901d5-1622-4c\" \/><\/p>\n<p>By ordering by <strong>Total_Worker_Time<\/strong> field, our queries have been returned as the first four rows in the results. We have the text of the queries and if we click the link in the <strong>query_plan<\/strong> field we can see the query plan of each query.<\/p>\n<p>Now we need to analyze each of the queries returned. Each query was selected because it had one or more index scans in it. SQL Server can use only one index for each table that is present in the query, so one query can use multiple indexes. This means it can have index seeks and scans in the same query.<\/p>\n<p>You will need to analyze the guidelines below for each index scan in the query, trying to turn the scan into an index seek.<\/p>\n<p>Let&#8217;s use the questions bellow to analyze the predicate of the index scans in each query.<\/p>\n<p><strong>Read also: <\/strong><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/understanding-and-using-heaps-in-sql-server\/\">Understanding Heaps\u00a0in SQL Server<\/a><\/p>\n<h1>Is the predicate a &#8216;sarg&#8217;?<\/h1>\n<p>&#8216;Sarg&#8217; is an abbreviation for &#8216;Search Argument&#8217;, it&#8217;s a word created by Kalen Delaney to mean an argument that can use an index seek for the search. Not all predicate expressions are &#8216;sarg&#8217;s, and there are a few rules that sargs need to follow. Grant Fritchey explained two of the rules of a &#8216;sarg&#8217; in his great article <a href=\"https:\/\/www.simple-talk.com\/sql\/performance\/the-seven-sins-against-tsql-performance\/\">&#8216;The seven sins against TSQL Performance<\/a>&#8216;.<\/p>\n<p>First we need to find the predicate for the scan in the query: After that we need to identify if it&#8217;s a &#8216;sarg&#8217; or not.<\/p>\n<p>The predicate can appear in the WHERE clause, a filter over a field of the table that&#8217;s suffering the scan, or a join, in this case the JOIN expression is the predicate.<br \/><strong><br \/><\/strong><strong>Read also: <\/strong><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/computed-column-performance-in-sql-server\/\">C<span data-sheets-root=\"1\">omputed column performance in SQL Server<\/span><\/a><\/p>\n<h1>Making queries Sargable<\/h1>\n<p>Let&#8217;s see the rules of a sarg:<\/p>\n<h3><strong>First Rule: <\/strong><strong>You can&#8217;t use <\/strong><strong>a <\/strong><strong>function in the left side (th<\/strong><strong>e field side) of the predicate.<\/strong><\/h3>\n<p>This is very well demonstrated in Grant&#8217;s article.<\/p>\n<p>The following query disobeys this rule:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect\u00a0 TransactionID,ProductID,\n\t\tTransactionDate,Quantity,ActualCost \n\t\tfrom bigtransactionhistory \n\t\twhere\u00a0 month(transactionDate)=6 and year(transactiondate)=2008\n\t<\/pre>\n<p>We can make this query better doing some changes to the predicate. Just writing the same predicate in a new way we will solve this problem:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect TransactionID,ProductID,\n\t\tTransactionDate,Quantity,ActualCost \n\t\t\u00a0from bigTransactionHistory \n\t\twhere transactionDate between '2008\/06\/01' and '2008\/06\/30'\n\t<\/pre>\n<p>As you can see in the new query plan below, the problem wasn&#8217;t solved with this query yet, but we are in the right path.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(129)-86bb8942-8cab-422c-93e8-8af33bf19713.png\" alt=\"2156-Screenshot%20(129)-86bb8942-8cab-42\" width=\"502\" height=\"158\" \/><\/p>\n<p>An interesting point is the fact that the scan is an index scan, not a clustered index scan. This happens because the non-clustered index is a covering index for this query.<\/p>\n<p>A covering index is a non-clustered index that has all the fields the query needs in its leaf level pages. When using a regular non-clustered index that doesn&#8217;t cover the query, the database engine would need to do an additional operation called &#8216;key lookup&#8217;. This means that, after the data is found in the leaf level of the index, SQL Server needs to retrieve from the clustered index any additional fields that are not present in the non-clustered index.<\/p>\n<p>We call an index &#8216;covering&#8217; for a query when it has, in the leaf level, all the fields needed for the query. The fields became part of the index when they are part of the index key, they are either part of the clustered key or they are included with &#8216;include&#8217; keyword when the index was created. A covering index doesn&#8217;t require key lookups and that&#8217;s better for performance.<\/p>\n<p>What&#8217;s important to notice in the above query plan is the Index Scan element without a Key Lookup element, which denotes a covering index.<\/p>\n<p>In fact the index<strong> IX_ProductID_TransactionDate<\/strong> is a composite index with <strong>ProductID <\/strong>and <strong>TransactionDate<\/strong> as the keys and <strong>Quantity<\/strong> and <strong>ActualCost <\/strong>included. That&#8217;s all the fields in the query, so the index become a covering index for this query, there is no need of key lookups in the query.<\/p>\n<p>We will see some examples later in this article because the use of covering index is one of the solutions for the scan problem. Joe Webb also talked about covering index in his great article <a href=\"https:\/\/www.simple-talk.com\/sql\/learn-sql-server\/using-covering-indexes-to-improve-query-performance\/\">&#8216;Using Covering Indexes to improve query performance&#8217;<\/a>.<\/p>\n<h3><strong>Second Rule: <\/strong><strong>The operators need to be of the same type<\/strong><\/h3>\n<p>Grant also covered this subject in his article, but what could actually cause this kind of mistake?<\/p>\n<p>The answer: ORMs. I&#8217;ve experienced cases where ORMs such as NHibernate were creating &#8216;WHERE&#8217; clauses that compared VARCHAR fields with NVARCHAR values, resulting in the same problem. Therefore, the ORMs are potential point of performance problems. I&#8217;m not saying to not use ORMs, I&#8217;m saying to be careful, check the queries that are being generated and configure the ORM correctly to avoid this mistake.<\/p>\n<h3><strong>Third Rule: <\/strong><strong>You can&#8217;t search the informa<\/strong><strong>tion in the middle of the field<\/strong><\/h3>\n<p>This expression isn&#8217;t a &#8216;sarg&#8217;:<\/p>\n<pre>name like '%jackson'<\/pre>\n<p>whereas this other expression is a sarg:<\/p>\n<pre>name like 'peter%'<\/pre>\n<p>So, LIKE expressions that require a search through the string to locate the substring will never be a &#8216;sarg&#8217;. We need to add more predicates to the query or change this like expression so the index can be used.Two of the queries have this problem. Let&#8217;s see the first one:<\/p>\n<pre>select * from bigproduct where name like '%LL Road Rear%'<\/pre>\n<p>The query plan for this query is:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(131)-0f92e74c-9c27-4819-b1ca-394b228ebd25.png\" alt=\"2156-Screenshot%20(131)-0f92e74c-9c27-48\" width=\"448\" height=\"122\" \/><\/p>\n<p>In this plan we have a simple clustered index scan. You can identify in the predicate the &#8216;%&#8217; sign in the beginning of the string, causing the problem.<\/p>\n<p>The second query is the following:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect firstname,lastname,PersonType from person.person\n\t\twhere firstname like '%Brianna%' and lastname like '%Bryant%'\n\t\n<\/pre>\n<p>This is the query plan for this query:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(130)-c0bb61f8-3649-4a0a-b4d3-6d05e0156922.png\" alt=\"2156-Screenshot%20(130)-c0bb61f8-3649-4a\" width=\"451\" height=\"193\" \/><\/p>\n<p>This time we have a non-clustered index scan, but we also have a Key Lookup element, so this is not a covering index for this query.<\/p>\n<p>The scan is over the non-clustered index instead the clustered index because the predicate is part of the non-clustered key: <strong>FirstName<\/strong> and <strong>LastName<\/strong>, while the key is <strong>FirstName<\/strong>, <strong>LastName<\/strong> and <strong>MiddleName<\/strong>. The non-clustered index has shorter rows than the clustered, so it&#8217;s better to do the non-clustered scan.<\/p>\n<p>We can demonstrate this difference by checking the number of pages each index has in its leaf-level. First let&#8217;s use the following query to find the <strong>index_id<\/strong> of each index:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect * from bigproduct where name like '%LL Road Rear%'\n\t<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(154)-6d88b5a1-ac2b-46c8-9259-0d1d0406ca51.png\" alt=\"2156-Screenshot%20(154)-6d88b5a1-ac2b-46\" width=\"432\" height=\"178\" \/><\/p>\n<p>As we can see, the clustered index has <strong>index_id<\/strong> 1 (one) and the <strong>IX_Person_LastName_FirstName_MiddleName<\/strong> non-clustered index, which is the index our query is using, has <strong>index_id<\/strong> 2 (two).<\/p>\n<p>Now that we know the IDs, we will use the DMF sys.dm_db_index_physical_status to check the number of pages both indexes, the clustered and the non-clustered, have. The queries will be these:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tSELECT index_level,index_type_desc,alloc_unit_type_desc,page_count \n\t\tFROM sys.dm_db_index_physical_stats\n\t\t\u00a0\u00a0\u00a0  (DB_ID(N'AdventureWorks2012'), OBJECT_ID(N'Person.Person'), 2 , NULL , NULL);\n\t\t\n\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \n\t\tSELECT index_level,index_type_desc,alloc_unit_type_desc,page_count \n\t\tFROM sys.dm_db_index_physical_stats\n\t\t\u00a0\u00a0\u00a0  (DB_ID(N'AdventureWorks2012'), OBJECT_ID(N'Person.Person'), 1, NULL , NULL);\n\t\t\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(155)-1a6ed08f-00c8-42f5-bcae-99ec3cbfcf3c.png\" alt=\"2156-Screenshot%20(155)-1a6ed08f-00c8-42\" width=\"438\" height=\"150\" \/><\/p>\n<p>As you can see in the above image, while the non-clustered index has only 106 pages in the leaf level, the clustered has 3810. So, as our predicate has only the fields in the non-clustered index, is much better to do a non-clustered index scan than a clustered index scan.<\/p>\n<h2>Now that we analyzed the queries, how to solve the problem?<\/h2>\n<p>Both queries are a common kind of query used in applications to search for information, using &#8216;%&#8217; in both sides of the search string. There is no way to make this query better without changing the functionality. We would need to remove the first &#8216;%&#8217; sign in each predicate to make this query better but we need to decide if the performance boost worth the functionality change, but most times it is. Most times the additional &#8216;%&#8217; sign in the start of the string isn&#8217;t worth the performance difference, or you should consider the use of a full text index instead.<\/p>\n<p>By removing the first &#8216;%&#8217; sign, the first query will become this:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">select * from bigproduct where name like 'LL Road Rear%'\n<\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(133)-4d58f9a4-e1d9-450f-95cd-c732a13e8fe0.png\" alt=\"2156-Screenshot%20(133)-4d58f9a4-e1d9-45\" \/><\/p>\n<p>The problem of this query, in table <strong>bigproduct<\/strong>, hasn&#8217;t been solved yet. Let&#8217;s continue the analysis to find the problem.<\/p>\n<p>Let&#8217;s see the second query:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect firstname,lastname,PersonType from person.person\n\t\twhere firstname like 'Brianna%' and lastname like 'Bryant%'\n\t\t\n<\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(138)-dd46a4cd-95d2-46a0-90cb-779103a2c90b.png\" alt=\"2156-Screenshot%20(138)-dd46a4cd-95d2-46\" \/><\/p>\n<p>The scan problem is solved for this query, now we have an index seek.<\/p>\n<p>The key lookup continues in the plan to retrieve the additional fields. We can check Key Lookup element to identify which field is being retrieved:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(139)-442560d0-1e3c-4f9a-bb6e-ae381c05ee64.png\" alt=\"2156-Screenshot%20(139)-442560d0-1e3c-4f\" \/><\/p>\n<p>As you can see in the above image, the Key Lookup is present in the query plan to retrieve the <strong>PersonType<\/strong> field from the clustered index.<\/p>\n<p>If the key lookup were too bad for the query, SQL Server would keep the clustered index scan. Therefore, it&#8217;s not too bad in this case, but if we could avoid it, it would be better. The solution to avoid the lookup is to include the additional fields needed by the query in the index, so the key lookup become unnecessary. The question you need to ask is: &#8216;Is this query so important and is it consuming so much resources that you need to change the index to make it a little (not much) better?&#8217; Most times not, but there are exceptions you will need to identify.<\/p>\n<p>Checking the &#8216;sarg&#8217; in your queries can solve many problems, but sometimes this isn&#8217;t enough. Let&#8217;s continue to the next step of our analysis.<\/p>\n<h3>Is the index a composite index including the sarg field?<\/h3>\n<p>There&#8217;s an important rule about a composite index: You need to pay attention to the first column.<\/p>\n<p>This is because the histogram within statistics is only created on the first column of the index. And the histogram of the statistics is one of the most important drivers for the choices that the optimizer makes. So, for example, if we have an index with key columns on <b>ID<\/b> and <b>Name<\/b>, a search on the <b>Name<\/b> column is unlikely to use the index at all, or may just scan the index. This is because there is no information about the <b>Name<\/b> column in the histogram. Also, because the data is stored with<b> ID<\/b> first, when something has to be looked up within the index, it needs to have that <b>ID<\/b> column, otherwise it has to go to the page and do a scan. To properly use the index you either have to search on <b>ID<\/b> or <b>ID<\/b> and <b>Name<\/b>. .<\/p>\n<p>One of the queries in the plan cache has this problem:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect TransactionID,ProductID,TransactionDate,Quantity,ActualCost\n\t\t\u00a0from bigTransactionHistory \n\t\twhere transactionDate between '2008\/06\/01'\n\t\t\u00a0\u00a0\u00a0\u00a0\u00a0 and '2008\/06\/30'\n\t<\/pre>\n<p>We solved the first problem, the wrong sarg, changing the predicate, but the scan continues because the index IX_ProductID_TransactionDate is a composite index over <strong>productid<\/strong> and <strong>transactionDate<\/strong>.<\/p>\n<p>These are your options in this case:<\/p>\n<ul>\n<li>You can add the <strong>ProductID<\/strong> field in the query<\/li>\n<li>You can create another index (non-clustered) over the field <strong>transactionDate<\/strong>. In this case you should question if the composite clustered index is the correct option for your table.\n<p>One new index will increase database maintenance work and decrease performance for INSERTs and UPDATEs, so you need to identify if not only this query is important enough to warrant this solution, but also to check whether this query isn&#8217;t really a development\/analysis mistake, since only one more predicate added to the query would solve the problem. If you decide so, a new non-clustered index on <strong>transactionDate<\/strong> will solve the problem and the query will do index seeks.<\/p>\n<\/li>\n<li>&#8211; You can change the order of the fields in the index\n<p>That&#8217;s expected that the indexes that are already on the table were planned based on your business. Should you really change the order of the fields?<\/p>\n<p>The index IX_ProductID_TransactionDate, used in this query, has the keys ProductID and TransactionDate, but also has an include of Quantity and Cost. These four fields are all the fields in this table, so this is not only a composite index but also a covering index for most of the queries on this table. Someone planned this.<\/p>\n<\/li>\n<\/ul>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(156)-637ffb7e-85ff-4e8c-b5aa-97607fcb84a8.png\" alt=\"2156-Screenshot%20(156)-637ffb7e-85ff-4e\" \/><\/p>\n<ul>\n<li>\n<p>Was the first analysis that created this index wrong? Or the business needs changed?<\/p>\n<p>Generally, the first column in a composite index should probably be the most selective column, the column that is most unique. This again goes back to statistics and the histogram. Since the histogram makes the most determination for how many rows will be returned by a given value, it&#8217;s usually helpful to have that be the value that returns the most accurate number of rows that the compound index will represent. Most of the time, that&#8217;s the most selective column. For example, an address can be indexed on <b>Country<\/b>, <b>State<\/b> and <b>City<\/b>. But that order is probably not ideal. Most of the time you would want <b>City<\/b> then <b>State<\/b> and <b>Country<\/b> as that order reflects the degree of selectivity. Just remember, this is a guideline, not even a rule. There are good reasons to go with a less selective column first, especially if that&#8217;s the column that is most frequently used in filtering the data.<\/p>\n<p>In our demonstration, <strong>productid<\/strong> and <strong>transactionDate<\/strong> have similar selectivity (a product is sold in several dates and several products are sold in a single date). In this case, the question that needs to be answered before taking this action is if, after changing the index, other queries that search by <strong>productid<\/strong><strong>,<\/strong> and uses this index, will no longer do an index seek. That&#8217;s like &#8216;covering all bases with a short blanket&#8217; and in this case you should create a new index, or accept the &#8216;short blanket&#8217; and decide which queries are more important.<\/p>\n<\/li>\n<\/ul>\n<p>To demonstrate a solution, let&#8217;s choose the first option and add a new predicate with the other field in the query. This new query would do an index seek:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect TransactionID,ProductID,TransactionDate,Quantity,ActualCost\n\t\t\u00a0from bigTransactionHistory \n\t\twhere transactionDate between '2008\/06\/01'\n\t\t\u00a0and '2008\/06\/30' and productid=1370\n\t<\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(142)-df7eace3-9d5c-45b9-a142-9f25757c354d.png\" alt=\"2156-Screenshot%20(142)-df7eace3-9d5c-45\" \/><\/p>\n<p>It&#8217;s difficult to predict which one will be the best choice for you. If you arrive at this point, the analysis of your query finishes. However, if that&#8217;s not your case, you don&#8217;t have a composite index over the &#8216;sarg&#8217;, let&#8217;s continue the analysis.<\/p>\n<h3><strong>Do you have a non-<\/strong><strong>clustered index over the <\/strong><strong>&#8216;<\/strong><strong>sarg<\/strong><strong>&#8216;<\/strong><strong>?<\/strong><\/h3>\n<p>If you don&#8217;t have a non-clustered index over the sarg, that&#8217;s your problem. You are querying over a field that doesn&#8217;t have an index.<\/p>\n<p>If this is a heavy and frequently-used query, you should probably create a non-clustered index for this query, but you will need to consider the number of indexes the table already have.<\/p>\n<p>The point is that each index in the table makes<strong> INSERT<\/strong> and <strong>UPDATE<\/strong> operations slower. Let&#8217;s see a small demonstration of this.<\/p>\n<p>In a new query window in SSMS, run the following script:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tuse AdventureWorks2012\n\t\tbegin transaction\n\t\tinsert into humanresources.employee (BusinessEntityID,NationalIDNumber,LoginID,\n\t\t\u00a0\u00a0\u00a0\u00a0 BirthDate, MaritalStatus,Gender, HireDate,JobTitle)\n\t\t\u00a0\u00a0\u00a0\u00a0 values (11390,'134969119','adventure-works\\gail3','1981-03-27','M','M','2003-03-12','Design Engineer')\n\t\t\n<\/pre>\n<p>Notice that we didn&#8217;t commit the transaction. In a different window, run the following query:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect resource_type,db_name(resource_database_id) [database],request_mode,\n\t\trequest_session_id, request_status, resource_associated_entity_id, indexes.name index_name\n\t\t\u00a0from sys.dm_tran_locks\n\t\t\u00a0left join sys.partitions on partitions.hobt_id = dm_tran_locks.resource_associated_entity_id\n\t\tjoin sys.indexes on indexes.object_id = partitions.object_id and indexes.index_id = partitions.index_id\n\t\t\u00a0where db_name(resource_database_id)='AdventureWorks2012'\n<\/pre>\n<p>This query shows every lock in AdventureWorks2012 database, including the name of the index that owns the locked object. You can notice in the <strong>index_name<\/strong> column in the image below that a single insert locked keys and pages in all the indexes of the table, because all of them need to receive the new row. Almost the same happens with updates, but only the indexes that contain the changed fields are affected.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(157)-75242361-2da9-488b-a9f7-ec8757fdef4c.png\" alt=\"2156-Screenshot%20(157)-75242361-2da9-48\" \/><\/p>\n<p>After this demonstration, it&#8217;s clear that each index makes INSERT and UPDATE operations slower. To finish the demonstration, run a &#8216;ROLLBACK TRANSACTION&#8217; in the first window.<\/p>\n<p>To decide if you should or shouldn&#8217;t create a new index you will need to analyze the following points:<\/p>\n<ul>\n<li>If you don&#8217;t have too many indexes over the table, there&#8217;s no doubt that you need to create the non-clustered index.<\/li>\n<li>If you have too many indexes over the table, are all the indexes being used? You can check if an index is being used using the following query:\n<pre class=\"theme:ssms2012 lang:tsql\">\t\t\tSELECT\u00a0\u00a0 OBJECT_NAME(S.[OBJECT_ID]) AS [OBJECT NAME], \n\t\t\t\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0I.[NAME] AS [INDEX NAME], \n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 USER_SEEKS, \n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 USER_SCANS, \n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 USER_LOOKUPS, \n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 USER_UPDATES \n\t\t\tFROM\u00a0\u00a0\u00a0\u00a0 SYS.DM_DB_INDEX_USAGE_STATS AS S \n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INNER JOIN SYS.INDEXES AS I \n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ON I.[OBJECT_ID] = S.[OBJECT_ID] \n\t\t\t\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND I.INDEX_ID = S.INDEX_ID \n\t\t\tWHERE\u00a0\u00a0 object_name(s.[object_id])='bigproduct'\n\t\t<\/pre>\n<p>Notice that we are using &#8216;<strong>bigproduct<\/strong>&#8216; table as an example. You can check in the result of how many seeks, scans or lookups this index has, and identify if the index is or isn&#8217;t being used.<\/p>\n<p>If you have indexes over the table that are being less used and are less important than the one you are missing, then you should consider dropping those indexes and creating the new one.<\/p>\n<\/li>\n<li>If you can&#8217;t drop any index, you need to consider this: Is the query you are analyzing so important that you should create a new index even knowing that you will reduce update and insert performance?<\/li>\n<\/ul>\n<p>In our examples we have two queries with this problem:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tselect * from bigproduct where name like 'LL Road Rear%'<\/pre>\n<pre class=\"theme:ssms2012 lang:tsql\">\tselect * from bigproduct where listprice between 100 and 200<\/pre>\n<p>The query plan does a clustered index scan because there isn&#8217;t any index for <strong>listprice<\/strong> field or <strong>name <\/strong>field. After analyzing all the details above, we will create two new indexes, one for <strong>listprice<\/strong>, another for <strong>name<\/strong>:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tcreate index indprice on bigproduct(listprice)\n\t\t\n\t\tcreate index indname on bigproduct(name)\n\t<\/pre>\n<p>After creating the indexes, the query plan for the first query will be like this:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(143)-ba49a25e-5c33-4cb4-9ad4-d0d929a14b31.png\" alt=\"2156-Screenshot%20(143)-ba49a25e-5c33-4c\" \/><\/p>\n<p>The problem is solved for the first query because it&#8217;s already doing an index seek. Notice the Key Lookup in the query. We can make this query even better removing the key lookup. Let&#8217;s return to this in a few minutes.<\/p>\n<p>The query plan for the second query will be this:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(144)-86d54c5f-6a2d-49a0-885f-f3681fdc1d1a.png\" alt=\"2156-Screenshot%20(144)-86d54c5f-6a2d-49\" \/><\/p>\n<p>The second query is still using the clustered index scan. Why is SQL Server refusing to use our index? Let&#8217;s continue the analysis to find out.<br \/><strong>Read also: <\/strong><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/how-to-avoid-conditional-joins-in-t-sql\/\">How to avoid conditional JOINs in T-SQL<\/a><\/p>\n<h2><strong>How<\/strong><strong> many rows does your query return?<\/strong><\/h2>\n<p>We saw the use of the Key Lookup when we use a non-clustered index. The problem is that the Key Lookup is only a good option for when there are just a few rows. If your query is returning too many rows, a high percentage of the total rows in the table, SQL Server will not use the index.<\/p>\n<p>This happens because the cost of the key lookup for each row can be bigger than the cost of an entire scan.<\/p>\n<p>The previous query that we faced is an example of this. The large number of bookmark lookups that the query would need to do in order to return the rows make the non-clustered index seek a more expensive operation than the clustered index scan.<\/p>\n<p>We don&#8217;t need to do a lot of calculations to identify this. The index exists and is not being used, so SQL Server already did the calculations and identified that the use of the index for this query would not have a good result.<\/p>\n<p>If we&#8217;re still in doubt, we can enable &#8216;<em>statistics <\/em><em>io<\/em><em>&#8216; <\/em>setting and force the use of the index to compare the queries.<\/p>\n<p>First, let&#8217;s test the <strong>&#8216;statistics <\/strong><strong>io<\/strong><strong>&#8216;<\/strong> for the query:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tset statistics io on\n\t\tselect * from bigproduct where listprice between 100 and 200\n\t<\/pre>\n<p>The result is the following:<\/p>\n<pre>Table 'bigProduct'. Scan count 1, logical reads 604, physical reads 1, read-ahead reads 602<\/pre>\n<p>Now let&#8217;s for the use of our index:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tselect * from bigproduct with (index=indprice) where listprice between 100 and 200\n\t<\/pre>\n<p>The result:<\/p>\n<pre>Table 'bigProduct'. Scan count 1, logical reads 2593, physical reads 0, read-ahead reads 9<\/pre>\n<p>It becomes clear that SQL Server is using the correct index for this situation.<\/p>\n<p>The first possible solution for this problem is to reduce the number of fields returned by the query and now we can clearly see the big problem that &#8216;*&#8217; in the select can bring to us: retrieving more fields than we need will probably prevent us from taking advantage of covering indexes or even from creating one.<\/p>\n<p>We should never use &#8216;*&#8217; in our queries. The first step to correct the problem is to get rid of the &#8216;*&#8217; and choose which fields we really need to return.<\/p>\n<p>Most times it will not be difficult to identify the fields really needed for the query. Our new query will be like this:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect ProductID,Name,ProductNumber,ListPrice\n\t\t\u00a0from bigproduct where listprice between 100 and 200\n\t\t\n<\/pre>\n<p>Of course this doesn&#8217;t solve the problem yet, because we don&#8217;t have a covering index for this query. We will need to drop the index (<strong>indprice<\/strong>) that we just created and create it again:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tdrop index indprice\n\t\tgo\n\t\tcreate index indprice on bigproduct(listprice) include (name, Productnumber)\n\t\t\n<\/pre>\n<p>Now we can execute our query again and the problem will be solved, the query plan will do an index seek.<\/p>\n<p>Let&#8217;s return to the other query in <strong>bigproduct<\/strong> table:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect * from bigproduct where name like 'LL Road Rear%'\n\t\t\n<\/pre>\n<p>The problem was solved, but we still have the key lookup, remember?<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(143)-b1a5a933-f66d-456b-b3a7-c7c6ba29f313.png\" alt=\"2156-Screenshot%20(143)-b1a5a933-f66d-45\" \/><\/p>\n<p>Once again, our problem is the big villain &#8216;*&#8217;. The key lookup is retrieving the fields that aren&#8217;t part of the index. The query will become better if we select only the fields we really need:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tselect ProductID,Name,ProductNumber,ListPrice from bigproduct where name like 'LL Road Rear%'\n\t<\/pre>\n<p>The problem isn&#8217;t solved yet, but now we just need to check the key lookup in the query plan to identify the output list and solve the problem:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(159)-5166ddb3-642f-4bc6-b721-1464d9889668.png\" alt=\"2156-Screenshot%20(159)-5166ddb3-642f-4b\" \/><\/p>\n<p>Let&#8217;s change the index to eliminate the key lookup:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tdrop index bigprodut.indname\n\t\tgo\n\t\tcreate index indname on bigproduct(name)\n\t\tinclude  (productNumber,ListPrice)\n\t<\/pre>\n<p>This is the resulting query plan:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2156-Screenshot%20(160)-2e2c6d0a-f18d-406c-a94b-bb156fda985d.png\" alt=\"2156-Screenshot%20(160)-2e2c6d0a-f18d-40\" \/><\/p>\n<p>Until now we reduced the number of fields in the queries and changed the indexes, all with the objective of turning an index into a covering index for the query, or creating a covering index if needed. The other solution for this problem is to change the query to return fewer rows. The question you would need to ask is &#8220;Do I really need all these rows?&#8221;. A new analysis could lead to new predicates in the query.<\/p>\n<h1><strong>The Result<\/strong><\/h1>\n<p>After the entire analysis, these are our new four queries:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\tselect TransactionID,ProductID,TransactionDate,Quantity,ActualCost\n\t\u00a0from bigTransactionHistory \n\twhere transactionDate between '2008\/06\/01'\n\t\u00a0and '2008\/06\/30' and productid=1370\n\tgo\n\tselect ProductID,Name,ProductNumber,ListPrice\n\t\u00a0from bigproduct where listprice between 100 and 200\n\tgo\n\tselect ProductID,Name,ProductNumber,ListPrice \n\tfrom bigproduct where name like 'LL Road Rear%'\n\tgo\n\tselect firstname,lastname,PersonType from person.person\n\twhere firstname like 'Brianna%' and lastname like 'Bryant%'\n\tgo\n\t\n<\/pre>\n<p>We changed all the four queries and identified two new indexes to solve the scan problems.<\/p>\n<h1><strong>Conclusion<\/strong><\/h1>\n<p>In this article I presented to you a few steps you can take to analyze and solve index scan problems that we found in SQL Server query plan cache. Of course this isn&#8217;t a complete guide, but is intended as an introduction to the many ways that you will solve a lot of problems by following this analysis in development.<\/p>\n<p><strong>Read also:<\/strong> <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/hands-on-with-columnstore-indexes-part-3-maintenance-and-additional-options\/\">C<span data-sheets-root=\"1\">olumnstore index maintenance<\/span><\/a><\/p>\n<\/div>\n<h2>Further reading<\/h2>\n<ul class=\"reference-list\">\n<li><a href=\"https:\/\/www.simple-talk.com\/sql\/performance\/execution-plan-basics\/\">Execution Plan Basics<\/a> <em>by Grant Fritchey<\/em><\/li>\n<li><a href=\"https:\/\/www.simple-talk.com\/sql\/performance\/the-seven-sins-against-tsql-performance\/\">The Seven Sins against TSQL Performance<\/a> <em>by Grant Fritchey<\/em><\/li>\n<li><a href=\"https:\/\/www.simple-talk.com\/sql\/performance\/simple-query-tuning-with-statistics-io-and-execution-plans\/\">Simple Query tuning with STATISTICS IO and Execution plans<\/a> <em>by Josef Richberg<\/em><\/li>\n<li><a href=\"https:\/\/www.simple-talk.com\/sql\/performance\/why-developers-need-to-understand-execution-plans\/\">Why Developers Need to Understand Execution Plans<\/a> <em>by Grant Fritchey<\/em><\/li>\n<li><a href=\"https:\/\/www.simple-talk.com\/sql\/performance\/graphical-execution-plans-for-simple-sql-queries\/https:\/\/www.simple-talk.com\/sql\/performance\/graphical-execution-plans-for-simple-sql-queries\/\">Graphical Execution Plans for Simple SQL Queries <\/a> <em>by Grant Fritchey<\/em><\/li>\n<li><a href=\"https:\/\/www.simple-talk.com\/sql\/performance\/understanding-more-complex-query-plans\/https:\/\/www.simple-talk.com\/sql\/performance\/understanding-more-complex-query-plans\/\"> Understanding More Complex Query Plans<\/a> <em>by Grant Fritchey<\/em><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/sql-server-deadlocks-by-example\/\">SQL Server deadlocks by example <\/a><\/li>\n<\/ul>\n\n\n<section id=\"my-first-block-block_aaa767835979ec693369945d148b01d7\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Fast, reliable and consistent SQL Server development&#8230;<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            &#8230;with SQL Toolbelt Essentials. 10 ingeniously simple tools for accelerating development, reducing risk, and standardizing workflows.                                    <\/div>\n            <\/div>\n                            <a href=\"https:\/\/www.red-gate.com\/products\/sql-toolbelt-essentials\/\" class=\"btn btn--secondary btn--lg\">Learn more &amp; try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: How to identify and solve index scan problems in SQL Server<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is the difference between an index scan and an index seek in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>An index seek uses the index\u2019s B-tree structure to navigate directly to the rows that match the query\u2019s search predicate &#8211; it reads only what it needs. An index scan reads every row at the leaf level of the index, which for a clustered index means reading the entire table. Seeks are almost always preferred because they read far fewer pages and use less IO. A scan may be appropriate when you need to return a large percentage of the table\u2019s rows, but in most OLTP queries, a scan indicates a missing or ineffective index.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. How do you find index scans in the SQL Server plan cache?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Query sys.dm_exec_query_stats cross-applied with sys.dm_exec_query_plan and filter the XML query plans for RelOp elements where LogicalOp equals \u201cIndex Scan\u201d or \u201cClustered Index Scan.\u201d You can build a table-valued function that returns all scan operations for a specific database, along with execution counts and resource usage, so you can prioritize which scans to fix based on their impact.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. How do you convert an index scan to an index seek?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Three main strategies: (1) Create a covering index that includes the columns in the WHERE clause as key columns and the SELECT columns in the INCLUDE clause. (2) Rewrite the query to use SARGable predicates &#8211; avoid wrapping indexed columns in functions, using LIKE with a leading wildcard, or implicit type conversions. (3) Add missing index columns suggested by the execution plan\u2019s missing index warnings. Sometimes the fix requires both a new index and a query rewrite.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. When is an index scan acceptable in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>An index scan is acceptable when the query needs to return a large percentage of the table\u2019s rows (typically above 15\u201320%), when the table is very small (a few hundred rows), or when you\u2019re running a batch aggregation that must process all rows. In these cases, the optimizer correctly chooses a scan because the overhead of individual seeks for each row would exceed the cost of a single sequential scan.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Find and fix SQL Server index scan problems using plan cache analysis. Covers identifying scans with DMVs, converting scans to seeks, and creating effective covering indexes.&hellip;<\/p>\n","protected":false},"author":50808,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143529],"tags":[4168,4206,4150,4151],"coauthors":[6810],"class_list":["post-1964","post","type-post","status-publish","format-standard","hentry","category-performance-sql-server","tag-database","tag-performance","tag-sql","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1964","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/50808"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1964"}],"version-history":[{"count":10,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1964\/revisions"}],"predecessor-version":[{"id":109370,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1964\/revisions\/109370"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1964"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1964"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1964"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1964"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}