{"id":46730,"date":"2026-09-08T13:32:57","date_gmt":"2026-09-08T11:32:57","guid":{"rendered":"https:\/\/www.dbi-services.com\/blog\/?p=46730"},"modified":"2026-09-08T13:33:00","modified_gmt":"2026-09-08T11:33:00","slug":"sql-server-stop-ordering-index-columns-by-selectivity","status":"publish","type":"post","link":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/","title":{"rendered":"SQL Server: Stop ordering index columns by selectivity"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In September 2026 <a href=\"https:\/\/www.linkedin.com\/in\/brentozar\/\">Brent Ozar<\/a> published <a href=\"https:\/\/lnkd.in\/p\/eNQKx_tV\">a post<\/a> arguing that the classic interview question about composite index column order has an answer everybody gets wrong, and that for a query filtering two columns <strong>with equality the order makes no difference whatsoever. <\/strong>I wanted to test that claim in logical reads on my own instance and dig into the B-tree architecture to see what actually happens.<\/p>\n\n\n\n<h2 id=\"h-building-the-test\" class=\"wp-block-heading\">Building the test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">One table, five million rows, and two indexes holding the same two columns in opposite order. The distribution is deliberately <strong>skewed<\/strong>, the way real order tables always are.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nCREATE TABLE dbo.Orders (\n    OrderID     INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,\n    Status      VARCHAR(12)   NOT NULL,\n    CustomerID  INT           NOT NULL,\n    OrderDate   DATETIME2(0)  NOT NULL,\n    Amount      DECIMAL(10,2) NOT NULL\n);\nGO\n\nINSERT INTO dbo.Orders (Status, CustomerID, OrderDate, Amount)\nSELECT TOP 3000000\n    &#039;Shipped&#039;,\n    ABS(CHECKSUM(NEWID())) % 100000 + 1,\n    DATEADD(MINUTE, -ABS(CHECKSUM(NEWID())) % 1000000, SYSDATETIME()),\n    ABS(CHECKSUM(NEWID())) % 50000 \/ 100.0\nFROM sys.all_columns a CROSS JOIN sys.all_columns b;\n\nINSERT INTO dbo.Orders (Status, CustomerID, OrderDate, Amount)\nSELECT TOP 1500000\n    &#039;Pending&#039;,\n    ABS(CHECKSUM(NEWID())) % 100000 + 1,\n    DATEADD(MINUTE, -ABS(CHECKSUM(NEWID())) % 1000000, SYSDATETIME()),\n    ABS(CHECKSUM(NEWID())) % 50000 \/ 100.0\nFROM sys.all_columns a CROSS JOIN sys.all_columns b;\n\nINSERT INTO dbo.Orders (Status, CustomerID, OrderDate, Amount)\nSELECT TOP 500000\n    &#039;Cancelled&#039;,\n    ABS(CHECKSUM(NEWID())) % 100000 + 1,\n    DATEADD(MINUTE, -ABS(CHECKSUM(NEWID())) % 1000000, SYSDATETIME()),\n    ABS(CHECKSUM(NEWID())) % 50000 \/ 100.0\nFROM sys.all_columns a CROSS JOIN sys.all_columns b;\n\nCREATE NONCLUSTERED INDEX IX_Status_Customer ON dbo.Orders (Status, CustomerID);\nCREATE NONCLUSTERED INDEX IX_Customer_Status ON dbo.Orders (CustomerID, Status);\nGO\n\nUPDATE STATISTICS dbo.Orders WITH FULLSCAN;\nGO\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\"><code>Status<\/code> holds three distinct values across five million rows and <code>CustomerID<\/code> holds a hundred thousand. Anyone applying the textbook rule puts <code>CustomerID<\/code> first, since it slices the table thirty-three thousand times more finely, and writes off <code>IX_Status_Customer<\/code> as a beginner&#8217;s mistake.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Both indexes cover a query that selects only <code>OrderID<\/code>, <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/indexes\/clustered-and-nonclustered-indexes-described?view=sql-server-ver17\" data-type=\"link\" data-id=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/indexes\/clustered-and-nonclustered-indexes-described?view=sql-server-ver17\">because the clustering key sits in every nonclustered leaf<\/a>. That keeps key lookups out of the read counts and leaves nothing but index access to measure.<\/p>\n\n\n\n<h2 id=\"h-test-1-both-columns-in-the-where-clause\" class=\"wp-block-heading\">Test 1: both columns in the WHERE clause<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s run the same query against both indexes, with equality predicates on <code>Status<\/code> and <code>CustomerID<\/code>, and compare what comes back.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSET STATISTICS IO ON;\n\nSELECT OrderID\nFROM dbo.Orders WITH (INDEX(IX_Status_Customer))\nWHERE Status = &#039;Cancelled&#039; AND CustomerID = 42731;\n<\/pre><\/div>\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nTable &#039;Orders&#039;. Scan count 1, logical reads 3, physical reads 0\n<\/pre><\/div>\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"195\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-6-1024x195.png\" alt=\"\" class=\"wp-image-46734\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-6-1024x195.png 1024w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-6-300x57.png 300w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-6-767x146.png 767w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-6.png 1372w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT OrderID\nFROM dbo.Orders WITH (INDEX(IX_Customer_Status))\nWHERE Status = &#039;Cancelled&#039; AND CustomerID = 42731;\n<\/pre><\/div>\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nTable &#039;Orders&#039;. Scan count 1, logical reads 3, physical reads 0\n\n<\/pre><\/div>\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"190\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-7-1024x190.png\" alt=\"\" class=\"wp-image-46735\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-7-1024x190.png 1024w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-7-300x56.png 300w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-7-766x142.png 766w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-7.png 1370w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Three logical reads each. Same plan shape, same cost, same everything, from two indexes whose leading columns differ in cardinality by a factor of thirty-three thousand.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The seek predicates settle it: both say <strong>Prefix<\/strong>, and neither plan carries a residual <code>Predicate<\/code> section, which means both indexes reach the rows with a single descent on the full composite key rather than filtering one column after the other :<\/p>\n\n\n\n<div class=\"wp-block-columns are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"524\" height=\"101\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-10.png\" alt=\"\" class=\"wp-image-46738\" style=\"aspect-ratio:5.188766788766789;width:369px;height:auto\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-10.png 524w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-10-300x58.png 300w\" sizes=\"auto, (max-width: 524px) 100vw, 524px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"557\" height=\"95\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-11.png\" alt=\"\" class=\"wp-image-46739\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-11.png 557w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-11-300x51.png 300w\" sizes=\"auto, (max-width: 557px) 100vw, 557px\" \/><\/figure>\n<\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The word that matters is <strong>Prefix<\/strong>, and it appears on both sides. SQL Server uses that label when the seek keys form a leading portion of the index key and can be resolved in a single descent through the tree.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is the heart of the whole thing. A composite index does not store two sorted columns side by side, waiting to be consulted in sequence. It stores one sorted key made of both columns, and <code>Cancelled | 42731<\/code> occupies exactly one position inside it. The engine descends once and arrives, and the picture most of us carry, in which the engine narrows the search using the first column, hands a smaller set of rows over to the second and narrows it again, describes something SQL Server never does.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That mental picture is precisely what makes the selectivity rule feel so obviously right. Sort by the column that eliminates the most rows first, the reasoning goes, and the second column has less work left to do. It sounds like common sense, and the only trouble with it is that it describes a two-stage filter which the seek predicates above show to be nowhere in either plan, because the filtering it imagines was already finished the moment the descent landed.<\/p>\n\n\n\n<h2 id=\"h-deep-dive-into-the-nonclustered-b-trees\" class=\"wp-block-heading\">Deep-dive into the nonclustered B-trees<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The plans agree. Now let&#8217;s see whether the physical structures agree too.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT\n    index_level,\n    page_count,\n    record_count,\n    avg_page_space_used_in_percent\nFROM sys.dm_db_index_physical_stats(\n        DB_ID(),\n        OBJECT_ID(&#039;dbo.Orders&#039;),\n        INDEXPROPERTY(OBJECT_ID(&#039;dbo.Orders&#039;), &#039;IX_Status_Customer&#039;, &#039;IndexID&#039;),\n        NULL,\n        &#039;DETAILED&#039;)\nORDER BY index_level DESC;\n\nSELECT\n    index_level,\n    page_count,\n    record_count,\n    avg_page_space_used_in_percent\nFROM sys.dm_db_index_physical_stats(\n        DB_ID(),\n        OBJECT_ID(&#039;dbo.Orders&#039;),\n        INDEXPROPERTY(OBJECT_ID(&#039;dbo.Orders&#039;), &#039;IX_Customer_Status&#039;, &#039;IndexID&#039;),\n        NULL,\n        &#039;DETAILED&#039;)\nORDER BY index_level DESC;\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">IX_Status_Customer:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"632\" height=\"114\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-15.png\" alt=\"\" class=\"wp-image-46745\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-15.png 632w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-15-300x54.png 300w\" sizes=\"auto, (max-width: 632px) 100vw, 632px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">IX_Customer_Status:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"635\" height=\"111\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-16.png\" alt=\"\" class=\"wp-image-46746\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-16.png 635w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-16-300x52.png 300w\" sizes=\"auto, (max-width: 635px) 100vw, 635px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Start with the number that ties back to test 1. Both indexes have <strong>three levels<\/strong>, and both queries reported three logical reads. Root page, intermediate page, leaf page. The execution plan and the physical structure confirm each other through two completely independent measurements, and neither leaves room for a second lookup step hiding somewhere. Five million rows on both sides, same depth, same cost, and the number of distinct values in the leading column changes neither one.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now read the tables diagonally. The <code>record_count<\/code> of any level equals the <code>page_count<\/code> of the level beneath it: 57 records at level 2 against 57 pages at level 1, then 15&#8217;605 records at level 1 against 15&#8217;605 pages at level 0. The second index does the same with its own figures, 58 and 58, then 15&#8217;589 and 15&#8217;589. An upper level therefore holds one entry per child page. One per page, whatever that page happens to contain, which is why the size of these levels tracks the number of pages underneath them and stays completely indifferent to how many distinct values live in the column you chose to lead with.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And that brings us to the comparison the whole article was built for:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><\/th><th>IX_Status_Customer<\/th><th>IX_Customer_Status<\/th><\/tr><\/thead><tbody><tr><td>Leading column<\/td><td>Status<\/td><td>CustomerID<\/td><\/tr><tr><td>Distinct values in the leading column<\/td><td>3<\/td><td>100&#8217;000<\/td><\/tr><tr><td>Levels<\/td><td>3<\/td><td>3<\/td><\/tr><tr><td>Root page entries<\/td><td>57<\/td><td>58<\/td><\/tr><tr><td>Level 1 pages<\/td><td>57<\/td><td>58<\/td><\/tr><tr><td>Leaf pages<\/td><td>15&#8217;605<\/td><td>15&#8217;589<\/td><\/tr><tr><td>Rows<\/td><td>5&#8217;000&#8217;000<\/td><td>5&#8217;000&#8217;000<\/td><\/tr><tr><td>Logical reads, equality seek<\/td><td>3<\/td><td>3<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 id=\"h-inside-the-root-pages\" class=\"wp-block-heading\">Inside the root pages<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The DMV tells us each root holds fifty-seven and fifty-eight entries. Let&#8217;s look at what those entries actually are.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT\n    allocated_page_file_id,\n    allocated_page_page_id,\n    page_type_desc,\n    page_level\nFROM sys.dm_db_database_page_allocations(\n        DB_ID(),\n        OBJECT_ID(&#039;dbo.Orders&#039;),\n        INDEXPROPERTY(OBJECT_ID(&#039;dbo.Orders&#039;), &#039;IX_Status_Customer&#039;, &#039;IndexID&#039;),\n        NULL,\n        &#039;DETAILED&#039;)\nWHERE page_type_desc = &#039;INDEX_PAGE&#039;\nORDER BY page_level DESC;\n\nSELECT\n    allocated_page_file_id,\n    allocated_page_page_id,\n    page_type_desc,\n    page_level\nFROM sys.dm_db_database_page_allocations(\n        DB_ID(),\n        OBJECT_ID(&#039;dbo.Orders&#039;),\n        INDEXPROPERTY(OBJECT_ID(&#039;dbo.Orders&#039;), &#039;IX_Customer_Status&#039;, &#039;IndexID&#039;),\n        NULL,\n        &#039;DETAILED&#039;)\nWHERE page_type_desc = &#039;INDEX_PAGE&#039;\nORDER BY page_level DESC;\n\n--Run once per index, using the page_level 2 page id returned above\nDBCC TRACEON(3604);\nDBCC PAGE(&#039;IndexOrderDemo&#039;, 1, &lt;root_page_id_for_both_index&gt;, 3);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">IX_Status_Customer:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"447\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-17-1024x447.png\" alt=\"\" class=\"wp-image-46749\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-17-1024x447.png 1024w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-17-300x131.png 300w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-17-767x335.png 767w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-17.png 1047w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">IX_Customer_Status:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"443\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-18-1024x443.png\" alt=\"\" class=\"wp-image-46750\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-18-1024x443.png 1024w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-18-767x332.png 767w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-18-300x130.png 300w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-18.png 1049w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Several things show up in these dumps, and each of them says the same thing about how the key is treated.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Row 0 carries <code>NULL<\/code> in every key column, which means an index record holding no key whatsoever, pointing at the <strong>leftmost child page<\/strong> and covering every row that sorts below the first boundary anywhere in the page.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Every other entry carries all three key columns, <code>OrderID<\/code> included, because the clustering key forms part of every nonclustered key. No entry ever names a single column. Each one identifies one specific row out of five million, which is exactly what a boundary between two pages has to do.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In <code>IX_Status_Customer<\/code>, the same status value appears in entry after entry. Half a million <code>Cancelled<\/code> rows span roughly 1&#8217;560 leaf pages and therefore about six intermediate pages, so the value contributes six separate boundaries.<\/p>\n\n\n\n<h2 id=\"h-what-about-statistics\" class=\"wp-block-heading\">What about statistics?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Navigation <\/strong>is one thing, <strong>description <\/strong>is another. Run <code>DBCC SHOW_STATISTICS<\/code> on both indexes and the density vectors tell the story of this whole article in five numbers:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nDBCC SHOW_STATISTICS(&#039;dbo.Orders&#039;, &#039;IX_Status_Customer&#039;);\n<\/pre><\/div>\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"291\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-19-1024x291.png\" alt=\"\" class=\"wp-image-46768\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-19-1024x291.png 1024w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-19-768x218.png 768w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-19-300x85.png 300w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-19.png 1433w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nDBCC SHOW_STATISTICS(&#039;dbo.Orders&#039;, &#039;IX_Customer_Status&#039;);\n<\/pre><\/div>\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"330\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-20-1024x330.png\" alt=\"\" class=\"wp-image-46769\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-20-1024x330.png 1024w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-20-766x247.png 766w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-20-300x97.png 300w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-20.png 1408w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The single-column entries are worlds apart, 0.3333333 for <code>Status<\/code> against 1E-05 for <code>CustomerID<\/code>. The two-column entries are identical to the last digit, 3.340538E-06 on both, <strong>because<a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/t-sql\/database-console-commands\/dbcc-show-statistics-transact-sql?view=sql-server-ver17\" data-type=\"link\" data-id=\"https:\/\/learn.microsoft.com\/en-us\/sql\/t-sql\/database-console-commands\/dbcc-show-statistics-transact-sql?view=sql-server-ver17\"> the density<\/a> of the full prefix counts the same combinations<\/strong> whichever way round you write them. Furthermore, both previous execution plans above show 7 of 17, seven rows actually returned against seventeen expected, and seventeen is not a coincidence: 5&#8217;000&#8217;000 multiplied by the two-column density of 3.340538E-06 gives 16.7. Since that density is identical in both indexes, so is the estimate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The histograms for the first column have nothing in common. <code>IX_Status_Customer<\/code> gets three steps with exact row counts, 500&#8217;000 Cancelled, 1&#8217;500&#8217;000 Pending, 3&#8217;000&#8217;000 Shipped. <code>IX_Customer_Status<\/code> gets 21 steps across a hundred thousand customer numbers, with around 50 rows per step. <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/t-sql\/database-console-commands\/dbcc-show-statistics-transact-sql?view=sql-server-ver17\" data-type=\"link\" data-id=\"https:\/\/learn.microsoft.com\/en-us\/sql\/t-sql\/database-console-commands\/dbcc-show-statistics-transact-sql?view=sql-server-ver17\">A histogram only ever describes the leading column<\/a>, so each index is blind to the other&#8217;s.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That matters the moment a query supplies one column instead of both, and it is worth knowing before you assume the two indexes are interchangeable for every purpose. It changes nothing for equality on the full key, which is what this article measured.<\/p>\n\n\n\n<h2 id=\"h-what-to-take-from-this\" class=\"wp-block-heading\">What to take from this<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>For equality predicates covering the full key<\/strong>, the two indexes are interchangeable. Three logical reads each, three levels each, and root pages holding 57 entries against 58, from two indexes whose leading columns sit thirty-three thousand apart in cardinality. Brent was right, and the plan, the DMV and the pages all say so independently.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The reason sits in the seek predicates. <code>Prefix<\/code> on both sides, no residual predicate on either, one descent to one composite key. SQL Server treats the columns as a single sorted value, so there is no first column doing the heavy lifting and no second column mopping up.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">None of which makes column order free. Drop the leading column and the two part company immediately: <code>WHERE CustomerID = 42731<\/code> alone seeks on one index and scans all 15&#8217;605 leaf pages on the other. <strong>Add an inequality and only the leading range predicate stays seekable<\/strong>, since a range cannot be resolved by a single descent. The seek finds the boundary and reads forward, everything after that column becomes a residual predicate evaluated row by row, and you can watch it appear as a <code>Predicate<\/code> section under the seek that was absent from both plans above.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So order your key columns by what your queries supply. <strong><span style=\"text-decoration: underline\">Equality columns first, then the single range predicate that eliminates the most rows, because only the first of them gets a real seek.<\/span><\/strong> That is the one place selectivity earns its keep.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One last thing worth saying plainly: this article compared two indexes <strong><span style=\"text-decoration: underline\">on a single query,<\/span><\/strong> and no index ever exists to serve a single query. The right order for <code>IX_Status_Customer<\/code> or <code>IX_Customer_Status<\/code> depends on everything else your application runs against that table. Which columns appear alone, which appear together, which carry ranges. That is the analysis worth doing, and counting distinct values will never be a substitute for it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next time you are ordering the columns of a composite index, write down the two or three query shapes that actually matter in your workload, check which of them the key prefix can serve, and let that decide the order. If you find yourself counting distinct values instead, stop, because you are answering a question nobody asked.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Column order does not affect equality seeks in SQL Server. Measured on 5M rows, then proven inside the B-tree pages.<\/p>\n","protected":false},"author":157,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":"","_members_access_role":[],"_members_access_error":""},"categories":[368,99],"tags":[91,51],"type_dbi":[],"class_list":["post-46730","post","type-post","status-publish","format-standard","hentry","category-development-performance","category-sql-server","tag-index","tag-sql-server"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v28.4 (Yoast SEO v28.4) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>SQL Server: Stop ordering index columns by selectivity - dbi Blog<\/title>\n<meta name=\"description\" content=\"Column order does not affect equality seeks in SQL Server. Measured on 5M rows, then proven inside the B-tree pages.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"SQL Server: Stop ordering index columns by selectivity\" \/>\n<meta property=\"og:description\" content=\"Column order does not affect equality seeks in SQL Server. Measured on 5M rows, then proven inside the B-tree pages.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/\" \/>\n<meta property=\"og:site_name\" content=\"dbi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-09-08T11:32:57+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-08T11:33:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-6.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1372\" \/>\n\t<meta property=\"og:image:height\" content=\"261\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Louis Tochon\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Louis Tochon\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/\"},\"author\":{\"name\":\"Louis Tochon\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#\\\/schema\\\/person\\\/e4195b0cb120295b3407a502c23e75b6\"},\"headline\":\"SQL Server: Stop ordering index columns by selectivity\",\"datePublished\":\"2026-09-08T11:32:57+00:00\",\"dateModified\":\"2026-09-08T11:33:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/\"},\"wordCount\":1436,\"commentCount\":0,\"image\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2026\\\/09\\\/image-6-1024x195.png\",\"keywords\":[\"index\",\"SQL Server\"],\"articleSection\":[\"Development &amp; Performance\",\"SQL Server\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/\",\"url\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/\",\"name\":\"SQL Server: Stop ordering index columns by selectivity - dbi Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2026\\\/09\\\/image-6-1024x195.png\",\"datePublished\":\"2026-09-08T11:32:57+00:00\",\"dateModified\":\"2026-09-08T11:33:00+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#\\\/schema\\\/person\\\/e4195b0cb120295b3407a502c23e75b6\"},\"description\":\"Column order does not affect equality seeks in SQL Server. Measured on 5M rows, then proven inside the B-tree pages.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2026\\\/09\\\/image-6.png\",\"contentUrl\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/2\\\/2026\\\/09\\\/image-6.png\",\"width\":1372,\"height\":261},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/sql-server-stop-ordering-index-columns-by-selectivity\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"SQL Server: Stop ordering index columns by selectivity\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/\",\"name\":\"dbi Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/#\\\/schema\\\/person\\\/e4195b0cb120295b3407a502c23e75b6\",\"name\":\"Louis Tochon\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/ce0ee48c64e763e6c4076e21c80729d15bc4493288aeb8695125c69082100e10?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/ce0ee48c64e763e6c4076e21c80729d15bc4493288aeb8695125c69082100e10?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/ce0ee48c64e763e6c4076e21c80729d15bc4493288aeb8695125c69082100e10?s=96&d=mm&r=g\",\"caption\":\"Louis Tochon\"},\"url\":\"https:\\\/\\\/www.dbi-services.com\\\/blog\\\/author\\\/louistochon\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"SQL Server: Stop ordering index columns by selectivity - dbi Blog","description":"Column order does not affect equality seeks in SQL Server. Measured on 5M rows, then proven inside the B-tree pages.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/","og_locale":"en_US","og_type":"article","og_title":"SQL Server: Stop ordering index columns by selectivity","og_description":"Column order does not affect equality seeks in SQL Server. Measured on 5M rows, then proven inside the B-tree pages.","og_url":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/","og_site_name":"dbi Blog","article_published_time":"2026-09-08T11:32:57+00:00","article_modified_time":"2026-09-08T11:33:00+00:00","og_image":[{"width":1372,"height":261,"url":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-6.png","type":"image\/png"}],"author":"Louis Tochon","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Louis Tochon","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/#article","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/"},"author":{"name":"Louis Tochon","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/e4195b0cb120295b3407a502c23e75b6"},"headline":"SQL Server: Stop ordering index columns by selectivity","datePublished":"2026-09-08T11:32:57+00:00","dateModified":"2026-09-08T11:33:00+00:00","mainEntityOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/"},"wordCount":1436,"commentCount":0,"image":{"@id":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/#primaryimage"},"thumbnailUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-6-1024x195.png","keywords":["index","SQL Server"],"articleSection":["Development &amp; Performance","SQL Server"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/","url":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/","name":"SQL Server: Stop ordering index columns by selectivity - dbi Blog","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/#primaryimage"},"image":{"@id":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/#primaryimage"},"thumbnailUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-6-1024x195.png","datePublished":"2026-09-08T11:32:57+00:00","dateModified":"2026-09-08T11:33:00+00:00","author":{"@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/e4195b0cb120295b3407a502c23e75b6"},"description":"Column order does not affect equality seeks in SQL Server. Measured on 5M rows, then proven inside the B-tree pages.","breadcrumb":{"@id":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/#primaryimage","url":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-6.png","contentUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/09\/image-6.png","width":1372,"height":261},{"@type":"BreadcrumbList","@id":"https:\/\/www.dbi-services.com\/blog\/sql-server-stop-ordering-index-columns-by-selectivity\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/www.dbi-services.com\/blog\/"},{"@type":"ListItem","position":2,"name":"SQL Server: Stop ordering index columns by selectivity"}]},{"@type":"WebSite","@id":"https:\/\/www.dbi-services.com\/blog\/#website","url":"https:\/\/www.dbi-services.com\/blog\/","name":"dbi Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/e4195b0cb120295b3407a502c23e75b6","name":"Louis Tochon","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/ce0ee48c64e763e6c4076e21c80729d15bc4493288aeb8695125c69082100e10?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/ce0ee48c64e763e6c4076e21c80729d15bc4493288aeb8695125c69082100e10?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/ce0ee48c64e763e6c4076e21c80729d15bc4493288aeb8695125c69082100e10?s=96&d=mm&r=g","caption":"Louis Tochon"},"url":"https:\/\/www.dbi-services.com\/blog\/author\/louistochon\/"}]}},"_links":{"self":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/46730","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/users\/157"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/comments?post=46730"}],"version-history":[{"count":44,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/46730\/revisions"}],"predecessor-version":[{"id":46821,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/46730\/revisions\/46821"}],"wp:attachment":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/media?parent=46730"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/categories?post=46730"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/tags?post=46730"},{"taxonomy":"type","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/type_dbi?post=46730"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}