{"id":467,"date":"2011-07-15T08:47:00","date_gmt":"2011-07-15T15:47:00","guid":{"rendered":"\/blogs\/kimberly\/post\/RemovingDuplicateIndexes.aspx"},"modified":"2021-11-11T15:49:07","modified_gmt":"2021-11-11T23:49:07","slug":"removing-duplicate-indexes","status":"publish","type":"post","link":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/removing-duplicate-indexes\/","title":{"rendered":"(OLD): Removing duplicate indexes"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"467\" class=\"elementor elementor-467\">\n\t\t\t\t\t\t<div class=\"elementor-inner\">\n\t\t\t\t<div class=\"elementor-section-wrap\">\n\t\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-2e0c1d2f elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"2e0c1d2f\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t\t\t<div class=\"elementor-row\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-4f30adf6\" data-id=\"4f30adf6\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-column-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t<div class=\"elementor-widget-wrap\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-7f413419 elementor-widget elementor-widget-text-editor\" data-id=\"7f413419\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-text-editor elementor-clearfix\">\n\t\t\t\t<h2>IMPORTANT UPDATE:<\/h2><p>PLEASE REVIEW THIS POST FOR THE UPDATED SCRIPTS: <a href=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/use-these-updates-to-sqlskills-index-procedures\/\">USE THESE: Updates to SQLskills Index Procedures<\/a><\/p><p><span style=\"color: var( --e-global-color-text ); font-family: var( --e-global-typography-text-font-family ), Sans-serif; font-weight: var( --e-global-typography-text-font-weight );\">****************************************************<\/span><\/p><p>SQL Server has a horrible habit of letting bad habits move forward (for the sake of backward compatibility [e.g. autoshrink]). And, I do understand *some* of the reasons for allowing some of these &#8220;features\/options&#8221; to move forward. However, there are a few that frustrate me and I&#8217;ve talked about this one quite often.<\/p><p><strong>SQL Server lets you create completely redundant and totally <span style=\"text-decoration: underline;\">duplicate<\/span> indexes.<\/strong><\/p><p>Yes, you can even create the same index 999 times! Try it:<\/p><p><pre class=\"brush: sql; title: ; notranslate\" title=\"\">&lt;br \/&gt;SET NOCOUNT ON&lt;br \/&gt;GO&lt;\/p&gt;&lt;p&gt;CREATE TABLE TestTable&lt;br \/&gt;(&lt;br \/&gt;col1 int identity&lt;br \/&gt;);&lt;br \/&gt;GO&lt;\/p&gt;&lt;p&gt;DECLARE @IndexID smallint,&lt;br \/&gt;@ExecStr nvarchar(500);&lt;\/p&gt;&lt;p&gt;SELECT @IndexID = 1;&lt;\/p&gt;&lt;p&gt;WHILE @IndexID &amp;lt;= 999&lt;br \/&gt;BEGIN&lt;br \/&gt;SELECT @ExecStr = &#039;CREATE INDEX [Test&#039; + right(&#039;00&#039; + convert(varchar(5), @IndexID), 3) + &#039;] ON TestTable (col1)&#039;;&lt;\/p&gt;&lt;p&gt;EXEC(@ExecStr);&lt;\/p&gt;&lt;p&gt;SELECT @IndexID = @IndexID + 1;&lt;br \/&gt;END&lt;br \/&gt;GO&lt;\/p&gt;&lt;p&gt;SELECT count(*)&lt;br \/&gt;FROM sys.indexes&lt;br \/&gt;WHERE object_id = object_id(&#039;TestTable&#039;);&lt;br \/&gt;GO&lt;\/p&gt;&lt;p&gt;--DROP TABLE TestTable&lt;br \/&gt;GO&lt;br \/&gt;<\/pre><\/p><p>For somewhat obvious reasons having 999 duplicate indexes does seem completely useless&#8230; so, why does SQL Server allow it at all? And, what&#8217;s the backward compatibility argument? Well, I&#8217;ve written about that before on our <a href=\"http:\/\/www.sqlmag.com\/blogcontent\/seriespath\/sql-server-questions-answered-28\" target=\"_blank\" rel=\"noopener\">SQL Server Magazine Q&amp;A blog (<em>Kimberly &amp; Paul &#8211; Questions Answered<\/em>)<\/a>: <a href=\"http:\/\/www.sqlmag.com\/sponsoredblog\/sql-server-questions-answered-28\/sql-server\/why-sql-server-lets-you-create-redundant-indexes-137128\" target=\"_blank\" rel=\"noopener\">Why SQL Server Lets You Create Redundant Indexes<\/a>.<\/p><p>Having said that &#8211; and, for those of you that are only checking YOUR code, you don&#8217;t use index hints\u00a0and\/or you don&#8217;t care about backward compat &#8211; then, you can use my proc to help you find duplicate indexes (and drop them)! It&#8217;s all based on my &#8220;sp_helpindex rewrites&#8221; stored procedures\u00a0but I had to do some &#8220;tweaking&#8221; to get the procedures to understand that the order of the columns in the INCLUDE list are irrelevant (in terms of order). So, you can&#8217;t use the sps as written. They give you the exact (and correct) structure &#8211; and, that&#8217;s exactly how SQL Server stores them. For example, the following two indexes have a different structure. However, they are completely redundant and you do NOT need both.<\/p><p><pre class=\"brush: sql; title: ; notranslate\" title=\"\">&lt;br \/&gt;CREATE INDEX Test1 ON dbo.Member (LastName)&lt;br \/&gt;INCLUDE (FirstName, MiddleInitial);&lt;\/p&gt;&lt;p&gt;CREATE INDEX Test2 ON Member (LastName)&lt;br \/&gt;INCLUDE (MiddleInitial, FirstName);&lt;br \/&gt;GO&lt;br \/&gt;<\/pre><\/p><p><strong><u>Here&#8217;s what you need:<\/u><\/strong><\/p><ol><li>First, my modified sp_ that exposes all of the columns in all levels of the index. This one does NOT replace the one needed by sp_SQLskills_SQL2008_helpindex. You need both. This version is used to reorder the INCLUDEd columns so that the &#8220;finddupes&#8221; proc can remove indexes that are different solely because of the order of the columns in the INCLUDE clause (which is irrelevant for navigation\/index usage).<p><strong>Install this one first:<br \/><a href=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2011\/7\/20110715_sp_sqlskills_exposecolsinindexlevels_include_unordered.sql\" class=\"broken_link\">20110715_sp_SQLskills_ExposeColsInIndexLevels_INCLUDE_UNORDERED.sql (6.95 kb)<\/a><\/strong><\/p><\/li><li>Second, my modifed sp_helpindex that is used SOLELY by finddupes. Basically, it&#8217;s just modified to use the UNORDERED version of &#8220;exposing the columns&#8221; in the nonclustered indexes. Again, this does NOT replace the other sp &#8211; you need both.<p><strong>Install this one second:<br \/><a href=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2011\/7\/20110715_sp_sqlskills_sql2008_finddupes_helpindex.sql\" class=\"broken_link\">20110715_sp_SQLskills_SQL2008_finddupes_helpindex.sql (10.95 kb)<br \/><\/a><\/strong><\/p><\/li><li>Finally, here&#8217;s the &#8220;finddupes&#8221; procedure<p><strong>Install this one last:<\/strong><br \/><strong><a href=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-content\/uploads\/2011\/7\/20110720_sp_sqlskills_sql2008_finddupes.sql\" class=\"broken_link\">20110720_sp_SQLskills_SQL2008_finddupes.sql (5.30 kb)<\/a><\/strong><\/p><\/li><li>NOTES: At some point in the not-too-distant future I&#8217;ll modify this for the following:<p>1) One set of code that will work for 2005\/2008<br \/>2) I&#8217;ll combine the two versions of the &#8220;expose columns in index levels&#8221; so that there&#8217;s only one.<br \/>3) I&#8217;ll make this work for indexed views<\/p><\/li><\/ol><p><strong><u>Finally, how can you use this:<\/u><\/strong><\/p><p>To find the duplicates for just one table &#8211; you can use one-part or two-part naming:<\/p><p><pre class=\"brush: plain; title: ; notranslate\" title=\"\">&lt;br \/&gt;USE [AdventureWorks2008];&lt;br \/&gt;GO&lt;\/p&gt;&lt;p&gt;EXECUTE sp_SQLskills_SQL2008_finddupes @ObjName=&#039;Production.Document&#039;;&lt;br \/&gt;GO&lt;br \/&gt;<\/pre><\/p><p>OR\u2026 to find all duplicates in the entire database:<\/p><p><pre class=\"brush: sql; title: ; notranslate\" title=\"\">&lt;br \/&gt;USE [AdventureWorks2008];&lt;br \/&gt;GO&lt;\/p&gt;&lt;p&gt;EXECUTE sp_SQLskills_SQL2008_finddupes;&lt;br \/&gt;GO&lt;br \/&gt;<\/pre><\/p><p>OR&#8230; if you&#8217;re really motivated, you can run it for all databases &#8211; but, before you do &#8211; see Aaron Bertrand&#8217;s comment to this post for his replacement for sp_msforeachdb and use that instead!!<\/p><p><pre class=\"brush: sql; title: ; notranslate\" title=\"\">&lt;br \/&gt;EXECUTE sp_msforeachdb &#039;USE ?; exec sp_SQLskills_SQL2008_finddupes&#039;;&lt;br \/&gt;GO&lt;br \/&gt;<\/pre><\/p><p><strong>Have fun and let me know how many duplicates you find?<\/strong><br \/>I found one in AdventureWorks2008 and a few in Northwind. I suspect you&#8217;ll find a few! This isn&#8217;t something that will find tons of indexes (to drop) but if there are even a couple of dupes (especially on large tables) you&#8217;ll have savings in database modification statements, logging, caching, maintenance, storage (and therefore backups, etc.) and this is why dupes are HORRIBLE! I do wish that SQL Server had an option to prevent their being created! Maybe someday?!<\/p><p><strong>Enjoy,<\/strong><\/p><p>kt<\/p>\t\t\t\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>IMPORTANT UPDATE: PLEASE REVIEW THIS POST FOR THE UPDATED SCRIPTS: USE THESE: Updates to SQLskills Index Procedures **************************************************** SQL Server has a horrible habit of letting bad habits move forward (for the sake of backward compatibility [e.g. autoshrink]). And, I do understand *some* of the reasons for allowing some of these &#8220;features\/options&#8221; to move forward. [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[23,36,48,63],"tags":[],"class_list":["post-467","post","type-post","status-publish","format-standard","hentry","category-database-maintenance","category-indexes","category-nonclustered-indexes","category-sp_helpindex-rewrites"],"_links":{"self":[{"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/posts\/467","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/comments?post=467"}],"version-history":[{"count":0,"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/posts\/467\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/media?parent=467"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/categories?post=467"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/kimberly\/wp-json\/wp\/v2\/tags?post=467"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}