Composite Non-Clustered Indexes – does the Column Order matter?

When I talk about indexes, people always ask me if the sorting order of the columns in a composite Non-Clustered Index matters? The short answer is always “It depends”. Let’s have a look on it why “it depends”..

Singleton Lookups

When you have queries which perform Singleton Lookups on your tables, the sorting order of the columns in a composite Non-Clustered Index doesn’t really matter. Imagine the following query:

-- Without a supporting Non-Clustered Index we have to scan the complete Clustered Index
SELECT AddressID FROM Person.Address
WHERE StateProvinceID = 79 AND City = 'Bothell'
GO

You can now create a Non-Clustered Index on StateProvinceID and City, or on City and StateProvinceID:

-- Create a supporting Non-Clustered Index
CREATE NONCLUSTERED INDEX idx_Test ON Person.Address(StateProvinceID, City)
GO

-- SQL Server performs a Non-Clustered Index Seek operation in combination with a Seek Predicate
SELECT AddressID FROM Person.Address
WHERE StateProvinceID = 79 AND City = 'Bothell'
GO

-- Change the column ordering
CREATE NONCLUSTERED INDEX idx_Test ON Person.Address(City, StateProvinceID)
WITH (DROP_EXISTING = ON)
GO

-- The column ordering doesn't matter in the Non-Clustered Index
SELECT AddressID FROM Person.Address
WHERE StateProvinceID = 79 AND City = 'Bothell'
GO

The ordering of the columns in the Non-Clustered Index doesn’t really matter here, because SQL Server will just perform a simple Non-Clustered Index Seek operation (in combination with a Seek Predicate) in the execution plan:

There is only a Seek Predicate used

Range Scans

Things change when we talk about Range Scans on tables, where you retrieve a set of data. Imagine you execute the following query:

SELECT AddressID FROM Person.Address
WHERE StateProvinceID BETWEEN 10 AND 12 AND City = 'Bothell'
GO

Again, you have 2 choices for a supporting Non-Clustered Index:

  • Non-Clustered Index on StateProvinceID and City
  • Non-Clustered Index on City and StateProvinceID

Let’s create the first choice:

-- Create a supporting Non-Clustered Index
CREATE NONCLUSTERED INDEX idx_Test ON Person.Address(StateProvinceID, City)
GO

In that case, SQL Server performs for the query a Non-Clustered Index Seek operation on the column StateProvinceID, and has to evaluate a Residual Predicate for the column City as you can see in the generated execution plan:

Now we have a Seek Predicate and a Residual Predicate!

This is not really a perfect execution plan, because you are reading physically more data than you have logically requested. But with the sorting order based on StateProvinceID as a leading column and City as a subsequent column this is the only possible behavior as you can see from the following picture:

Our data is in the wrong sorting order

But let’s try now to create the Non-Clustered Index where we just swap the columns: the City column as a leading column, and the column StateProvinceID as the second one:

-- Non-Clustered Index Seek on StateProvinceID, and a Residual Predicate on column City
SELECT AddressID FROM Person.Address
WHERE StateProvinceID BETWEEN 10 AND 12 AND City = 'Bothell'
GO

When you now execute your query again, you can see that SQL Server again performs a Non-Clustered Index Seek operation. But this time you have *no* Residual Predicate for your query.

Now there is only a Seek Predicate

Therefore you are physically reading just those rows that you logically requested. But how is this now possible? Just have a look at the following picture that shows you how the data is currently sorted in the Non-Clustered Index:

Our data is now in the correct sorting order

As you can see the data is now presorted on City, and within each City group you have presorting on the column StateProvinceID. Therefore you can get straight to those records that you logically requested – and return them without any further evaluating of a Residual Predicate!

Summary

The column ordering in a composite Non-Clustered Index matters – when you have to perform Range Scans! As I’m always saying in my various workshops: almost everything in SQL Server is about Indexing, and Indexing itself is just presorting data! Nothing more! To understand whether SQL Server can directly seek to logically requested records, you also have to visualize in your mind how the data is presorted, and how you can access it by presorting the data efficiently.

I hope that this blog posting gave you a better understanding of how the column ordering in a Non-Clustered Index influences a Seek operation.

Like or share to get the source code.

Thanks for your time,

-Klaus

5 thoughts on “Composite Non-Clustered Indexes – does the Column Order matter?”

  1. Andrzej Jasinski

    Great article, however title is misleading – it`s not about Sorting Order (ASC/DESC), but about column order. It might be interesting to see if sorting order could matter.

    Regards,
    Andrzej

    1. Klaus Aschenbrenner

      Hello Andrzej,

      Thanks for your comment.
      Aaah, you are right, the title of the blog posting could be better 😉
      I have changed it now!

      Thanks for the input,

      -Klaus

Leave a Comment

Your email address will not be published. Required fields are marked *