{"id":1546,"date":"2012-12-18T00:00:00","date_gmt":"2012-12-18T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/hypothetical-indexes-on-sql-server\/"},"modified":"2021-08-24T13:40:01","modified_gmt":"2021-08-24T13:40:01","slug":"hypothetical-indexes-on-sql-server","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/hypothetical-indexes-on-sql-server\/","title":{"rendered":"Hypothetical Indexes on SQL Server"},"content":{"rendered":"<div class=\"article-content\">\n<p class=\"start\">If we want to predict how well a query will perform as a result of adding a new index on the table, we need to wait while the index is created before we can test it. On larger tables, the creation of the index can take a significant amount of time and if you are trying a number of alternative indexing strategies, the wait can become very tedious. Furthermore, it is a common frustration to find that, after waiting for many minutes for the creation of the index, you realize that it is not using the index when you go to look at the query plan. <\/p>\n<p>So wouldn&#8217;t it be nice if we could try a hypothetical index just to test if the index really will be useful for the query. That is possible, but not straightforward; The reason that the technique exists is that it is used by the DTA (Database Tuning Advisor) to recommend a missing index. In this article I&#8217;ll present you some undocumented commands that are used to do it. <\/p>\n<h2>Creating a hypothetical index<\/h2>\n<p>There is a special syntax of the CREATE INDEX command that allows us to create a hypothetical index. This is an index that creates the metadata of the index on sysindexes and a statistic associated to the index, but does not create the index itself. <\/p>\n<p>Suppose we have the following query from AdventureWorks2012 database: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT SalesOrderID, OrderDate, Status, TerritoryID\n  FROM Sales.SalesOrderHeader\n WHERE OrderDate = '20050701'\n<\/pre>\n<p>If we want to create a hypothetical index on <strong>SalesOrderHeader<\/strong> table we could run: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE INDEX ixOrderDate ON Sales.SalesOrderHeader (OrderDate) WITH STATISTICS_ONLY = -1\n<\/pre>\n<p>The relational index option STATISTICS_ONLY = -1, which is undocumented, means that the index itself will not be created, but only the statistic associated with the index. This index be neither considered nor used by the query optimizer unless you run a query in AUTOPILOT mode. <\/p>\n<h2>DBCC AUTOPILOT and AUTOPILOT MODE<\/h2>\n<p>There is command called &#8220;<strong>SET AUTOPILOT ON<\/strong>&#8221; used to enable support to hypothetical indexes, and this is used with other DBCC command called &#8220;<strong>DBCC AUTOPILOT<\/strong>&#8220;. <\/p>\n<p>First let&#8217;s see them working together and then I&#8217;ll give you more details about it: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT dbid = DB_ID(),\n       objectid = \nobject_id,\n       indid = index_id\n  FROM sys.indexes\n WHERE \nobject_id = \nOBJECT_ID('Sales.SalesOrderHeader')\n   AND is_hypothetical = 1\n\/*\n  Results:\n  |dbid   |objectid\t   |indid |\n  |8\t   |1266103551   |15    |\n*\/\n\n-- Use typeId 0 to enable a specifc index on AutoPilot mode\nDBCC AUTOPILOT(0, 8, 1266103551, 15)\nGO\nSET AUTOPILOT ON\nGO\nSELECT SalesOrderID, OrderDate, Status, TerritoryID\n  FROM Sales.SalesOrderHeader\n WHERE OrderDate = '20050701'\nGO\nSET AUTOPILOT OFF<\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1705-1-c490d761-2232-4322-804d-546dc0c7441d.png\" alt=\"1705-1-c490d761-2232-4322-804d-546dc0c74\" \/><\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1705-1-2019f5e2-e642-47c2-8e42-3c06710ab9d3.png\" alt=\"1705-1-2019f5e2-e642-47c2-8e42-3c06710ab\" \/><\/p>\n<p>When running on autopilot mode, SQL Server doesn&#8217;t execute the query but it returns an estimated execution plan that considers all indexes enabled by DBCC AUTOPILOT command, including the hypothetical ones. <\/p>\n<h2>DBCC AUTOPILOT<\/h2>\n<p>There are a few things you could do with this command, first let&#8217;s find out what the syntax is. We can find out the syntax of all undocumented commands by using the trace flag 2588 and then running DBCC HELP to see:<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">DBCC TRACEON (2588)\nDBCC HELP('AUTOPILOT')\n<\/pre>\n<pre class=\"listing\">DBCC AUTOPILOT (typeid [, dbid [, {maxQueryCost | tabid [, indid [, pages [, flag [, rowcounts]]]]} ]])\n<\/pre>\n<h2>Making AUTOPILOT easier to use<\/h2>\n<p>The parameters that you have to use are not straightforward. This means that, if you are working with a query with lots of tables, it can get boring to write all the DBCC AUTOPILOT commands and this might discourage you from using it. Because of this, I&#8217;ve created a procedure to make it a little easier to use. <\/p>\n<p>Originally I created this procedure after answering a student&#8217;s question about how to make it easier to use hypothetical indexes on SQL Server. So I thought you may like it. <\/p>\n<p>Unfortunately it relies on a CLR stored procedure to SET the AUTOPILOT, but if you don&#8217;t mind to use it in a develop environment (which is something normal to do) then you can use it, following is the CLR code, and if you are interested you can<a href=\"https:\/\/skydrive.live.com\/redir?resid=52EFF7477E74CAA6!2050\"> download the project code here:<\/a> <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">-- CLR Proc\n\/*\nusing System;\nusing System.Data;\nusing System.Data.SqlClient;\nusing System.Data.SqlTypes;\nusing Microsoft.SqlServer.Server;\n\n\npublic partial class StoredProcedures\n{\n    [Microsoft.SqlServer.Server.SqlProcedure]\n    public static void CLR_GetAutoPilotShowPlan\n    (\n         SqlString SQL,\n         out SqlXml PlanXML\n    )\n    {\n        \/\/Prep connection\n        SqlConnection cn = new SqlConnection(\"Context Connection = True\");\n\n        \/\/Set command texts\n        SqlCommand cmd_SetAutoPilotOn = new SqlCommand(\"SET AUTOPILOT ON\", cn);\n        SqlCommand cmd_SetAutoPilotOff = new SqlCommand(\"SET AUTOPILOT OFF\", cn);\n        SqlCommand cmd_input = new SqlCommand(SQL.ToString(), cn);\n\n        if (cn.State != ConnectionState.Open)\n        {\n            cn.Open();\n        }\n\n        \/\/Run AutoPilot On\n        cmd_SetAutoPilotOn.ExecuteNonQuery();\n\n        \/\/Run input SQL\n        SqlDataAdapter da = new SqlDataAdapter();\n        DataSet ds = new DataSet();\n\n        da.SelectCommand = cmd_input;\n        ds.Tables.Add(new DataTable(\"Results\"));\n\n        ds.Tables[0].BeginLoadData();\n        da.Fill(ds, \"Results\");\n        ds.Tables[0].EndLoadData();\n\n        \/\/Run AutoPilot Off\n        cmd_SetAutoPilotOff.ExecuteNonQuery();\n\n        if (cn.State != ConnectionState.Closed)\n        {\n            cn.Close();\n        }\n\n        \/\/Package XML as output\n        System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();\n        \/\/XML is in 1st Col of 1st Row of 1st Table\n        xmlDoc.InnerXml = ds.Tables[0].Rows[0][0].ToString();\n        System.Xml.XmlNodeReader xnr = new System.Xml.XmlNodeReader(xmlDoc);\n        PlanXML = new SqlXml(xnr);\n    }\n};\n*\/\n<\/pre>\n<p>And following is the code to compile it on SQL Server and to create another procedure to simulate the hypothetical indexes: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">-- Enabling CLR\nsp_configure 'clr enabled', 1\nGO\nRECONFIGURE\nGO\n-- Publishing Assembly\nIF EXISTS(SELECT * FROM sys.assemblies WHERE name = 'CLR_ProjectAutoPilot')\nBEGIN\n  IF OBJECT_ID('st_CLR_GetAutoPilotShowPlan') IS NOT NULL\n    DROP PROC st_CLR_GetAutoPilotShowPlan\n\n  DROP ASSEMBLY CLR_ProjectAutoPilot\nEND\nGO\nCREATE ASSEMBLY CLR_ProjectAutoPilot FROM 'C:\\Fabiano\\ ProjectAutoPilot\\ProjectAutoPilot\\bin\\Release\\ProjectAutoPilot.dll' WITH PERMISSION_SET = SAFE\nGO\n\nCREATE PROCEDURE st_CLR_GetAutoPilotShowPlan (@Query NVarChar(MAX), @ShowPlan XML OUTPUT)\nAS\n  EXTERNAL NAME CLR_ProjectAutoPilot.StoredProcedures.CLR_GetAutoPilotShowPlan\nGO\n\nIF OBJECT_ID('st_TestHipotheticalIndexes', 'p') IS NOT NULL\n  DROP PROC dbo.st_TestHipotheticalIndexes\nGO\nCREATE PROCEDURE dbo.st_TestHipotheticalIndexes (@SQLIndex NVarChar(MAX), @Query NVarChar(MAX))\nAS\nBEGIN\n  SET NOCOUNT ON;\n  BEGIN TRY\n    BEGIN TRAN\n    DECLARE @CreateIndexCommand NVarChar(MAX),\n            @IndexName NVarChar(MAX),\n            @TableName NVarChar(MAX),\n            @SQLIndexTMP NVarChar(MAX),\n            @SQLDropIndex NVarChar(MAX),\n            @SQLDbccAutoPilot NVarChar(MAX),\n            @i Int,\n            @QuantityIndex Int,\n            @Xml XML\n\n    IF SubString(@SQLIndex, LEN(@SQLIndex), 1) &lt;&gt; ';'\n    BEGIN\n      RAISERROR ('Last character in the index should be ;', -- Message text.\n                 16, -- Severity.\n                 1 -- State.\n                 );\n    END\n\n    SET @SQLDropIndex = '';\n    SET @QuantityIndex = LEN(@SQLIndex) - LEN(REPLACE(@SQLIndex, ';', ''))\n    SELECT @SQLIndexTMP = SUBSTRING(@SQLIndex, 0, CharIndex(';', @SQLIndex))\n    \n    SET @i = 0\n    WHILE @i &lt; @QuantityIndex\n    BEGIN\n      SET @SQLIndexTMP = SUBSTRING(@SQLIndex, 0, CharIndex(';', @SQLIndex))\n      SET @CreateIndexCommand = SUBSTRING(@SQLIndexTMP, 0, CharIndex(' ON ',@SQLIndexTMP))\n      SET @IndexName = REVERSE(SubString(REVERSE(@CreateIndexCommand), 0, CharIndex(' ', REVERSE(@CreateIndexCommand))))\n      SET @TableName = SUBSTRING(REPLACE(@SQLIndexTMP, @CreateIndexCommand + ' ON ', ''), 0, CharIndex(' ', REPLACE(@SQLIndexTMP, @CreateIndexCommand + ' ON ', '')))\n      SET @SQLIndex = REPLACE(@SQLIndex, @SQLIndexTMP + ';', '')\n      --SELECT @SQLIndex, @SQLIndexTMP, @CreateIndexCommand, @TableName, @IndexName\n    \n      -- Creating hypotetical index\n      IF CharIndex('WITH STATISTICS_ONLY =', @SQLIndexTMP) = 0\n      BEGIN\n        SET @SQLIndexTMP = @SQLIndexTMP + ' WITH STATISTICS_ONLY = -1'\n      END\n      -- PRINT @SQLIndexTMP\n      EXEC (@SQLIndexTMP)\n      \n      -- Creating query to drop the hypotetical index\n      SELECT @SQLDropIndex = @SQLDropIndex + 'DROP INDEX ' + @TableName + '.' + @IndexName + '; '\n      -- PRINT @SQLDropIndex\n      \n      -- Executing DBCC AUTOPILOT\n      SET @SQLDbccAutoPilot = 'DBCC AUTOPILOT (0, ' + \n                                               CONVERT(VarChar, DB_ID()) + ', '+ \n                                               CONVERT(VarChar, OBJECT_ID(@TableName),0) + ', ' +\n                                               CONVERT(VarChar, INDEXPROPERTY(OBJECT_ID(@TableName), @IndexName, 'IndexID')) + ')'\n\n      EXEC (@SQLDbccAutoPilot)\n      --PRINT @SQLDbccAutoPilot\n    \n      SET @i = @i + 1\n    END\n    \n    -- Executing Query\n    DECLARE @PlanXML xml\n\n    EXEC st_CLR_GetAutoPilotShowPlan @Query = @Query, \n                                     @ShowPlan = @PlanXML OUT\n    SELECT @PlanXML\n    \n    -- Droping the indexes\n    EXEC (@SQLDropIndex)\n    \n    COMMIT TRAN\n  END TRY\n  BEGIN CATCH\n    ROLLBACK TRAN\n    -- Execute error retrieval routine.\n    SELECT ERROR_NUMBER()    AS ErrorNumber,\n           ERROR_SEVERITY()  AS ErrorSeverity,\n           ERROR_STATE()     AS ErrorState,\n           ERROR_PROCEDURE() AS ErrorProcedure,\n           ERROR_LINE()      AS ErrorLine,\n           ERROR_MESSAGE()   AS ErrorMessage;\n  END CATCH;\nEND\nGO<\/pre>\n<p>The stored procedure <strong>st_TestHipotheticalIndexes<\/strong> expects two input parameters: <\/p>\n<ul>\n<li><strong>@<\/strong><strong>SQLIndex<\/strong>: Here you should specify the command to create the index that you want to try (the hypothetical indexes), if you want to try more than one index, just call it separating many &#8220;create index&#8221; commands by a semicolon. For instance: <\/li>\n<\/ul>\n<pre class=\"lang:tsql theme:ssms2012\">@SQLIndex = 'CREATE INDEX ix_12 ON Products (Unitprice, CategoryID, SupplierID) INCLUDE(ProductName);CREATE INDEX ix_Quantity ON Order_Details (Quantity);',\n<\/pre>\n<ul>\n<li><strong>@Query<\/strong>: Here you should write the query you want to try. <\/li>\n<\/ul>\n<p>Here is a sample of how to call it: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">EXEC dbo.st_TestHipotheticalIndexes @SQLIndex = 'CREATE INDEX ix ON Order_Details(Quantity);', \n@Query = 'SELECT * FROM Order_Details WHERE Quantity &lt; 1'\n<\/pre>\n<p>The results of the query above is an XML datatype with the query plan considering the suggested index: <\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1705-b27d8e96-93c7-44b3-aadb-431ec43a9dd5.png\" alt=\"1705-b27d8e96-93c7-44b3-aadb-431ec43a9dd\" \/><\/p>\n<p>Another sample: <\/p>\n<pre class=\"lang:tsql theme:ssms2012\">-- Sample 2\nEXEC dbo.st_TestHipotheticalIndexes @SQLIndex = 'CREATE INDEX ix_12 ON Products (Unitprice, CategoryID, SupplierID) INCLUDE(ProductName);CREATE INDEX ix_Quantity ON Order_Details (Quantity);', @Query = 'SELECT p.ProductName, p.UnitPrice, s.CompanyName, s.Country, od.quantity FROM Products as P INNER JOIN Suppliers as S ON P.SupplierID = S.SupplierID INNER JOIN order_details as od ON p.productID = od.productid WHERE P.CategoryID in (1,2,3) AND P.Unitprice &lt; 20 AND S.Country = ''uk'' AND od.Quantity &lt; 90'\n<\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1705-3c56c020-53c1-4157-93f6-1d641c00443a.png\" alt=\"1705-3c56c020-53c1-4157-93f6-1d641c00443\" \/><\/p>\n<p>Now it is easier to try out the effect of various indexes. Let me know what do you think and please don&#8217;t mind the clumsy code in the procedure to get the <strong>tablename<\/strong>, i<strong>ndexname<\/strong>. <\/p>\n<h2>Conclusion<\/h2>\n<p>There is a lot of mystery about these undocumented features, but I&#8217;m sure this will be enough to get you started with doing tests using Hypothetical indexes. I am sure I don&#8217;t need to tell you not to use this is in production environment do I? This is undocumented stuff, so nobody can guarantee what it is really doing, and the side-effects unless Microsoft chooses to make it officially public and documented. <\/p>\n<p>That&#8217;s all folks. <\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Sometimes, you have to test out alternative indexing strategies, but the task of creating the candidate indexes would just take too long. Is there another way? Well, yes, why not use the same method the DTA (Database Tuning Advisor) uses, and take the tedium out of the job.&hellip;<\/p>\n","protected":false},"author":65554,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143527],"tags":[4168,4170,4150,4151],"coauthors":[],"class_list":["post-1546","post","type-post","status-publish","format-standard","hentry","category-database-administration-sql-server","tag-database","tag-database-administration","tag-sql","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1546","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\/65554"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1546"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1546\/revisions"}],"predecessor-version":[{"id":92246,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1546\/revisions\/92246"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1546"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1546"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1546"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1546"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}