From: Gert-Jan Strik on
Ron,

In case it wasn't already clear from Jay's response: the clustered index
is seeked because the predicate ApplicationID=99 uses the leading column
of this index, and because it is the clustered index, the index entries
include the rest of the row data, so they include INDEX6.

What might not be clear when you read the query plan, is that the index
seek is only used to locate the first row (with ApplicationID=99). From
that point on, it will scan through the index to validate your other
predicate (INDEX6 LIKE '%W%'). It will do this until ApplicationID > 99
or until 5000 matching rows are found.

--
Gert-Jan
SQL Server MVP


Ron wrote:
>
> Hi, yes, here is the full query, exec plan (SHOWPLAN_TEXT), table and Index.
> (I tried to reduce it down to an example for simplicity before, and fell into
> the trap of providing less than useful info). I've tried to lay this out as
> best as possible....
> Just to repeat, my question is why is there a clustered index seek, when
> index6 isn't in the clustered index (and is not SARGable?). Any thoughts
> greatly appreciated.
>
> ----Execution Plan----
>
> |--Top(TOP EXPRESSION:((5000)))
> |--Clustered Index Seek(OBJECT:([_archive].[dbo].[Docs].[IX_DOCUMENTS]
> AS [D]), SEEK:([D].[ApplicationID]=(99)),
> WHERE:([dbtest].[dbo].[Docs].[INDEX6] as [D].[INDEX6] like '%W%') ORDERED
> FORWARD)
>
> ----Query
>
> SELECT TOP 5000 [DocumentID], [ImportDate] FROM Docs AS D
> WHERE APPLICATIONID=99
> AND D.INDEX6 LIKE '%W%'
>
> ----Index
>
> CREATE CLUSTERED INDEX [IX_DOCUMENTS] ON [dbo].[Docs]
> (
> [ApplicationID] ASC,
> [IsTest] ASC,
> [BatchID] ASC,
> [ImportDate] DESC
> )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB =
> OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS
> = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
> GO
>
> ----Table
>
> CREATE TABLE [dbo].[Docs](
> [ID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
> [DocumentID] [uniqueidentifier] NOT NULL,
> [ApplicationID] [smallint] NOT NULL,
> [BatchID] [int] NOT NULL,
> [ImportDate] [datetime] NULL,
> [ValidFrom] [datetime] NOT NULL,
> [ValidTo] [datetime] NULL,
> [IsTest] [bit] NOT NULL,
> [DocumentName] [varchar](255) NOT NULL,
> [Extension] [varchar](50) NOT NULL,
> [IsCompressed] [bit] NOT NULL,
> [IsAttachmentOnly] [bit] NOT NULL,
> [Document] [image] NULL,
> [INDEX1] [varchar](255) NULL,
> [INDEX2] [varchar](255) NULL,
> [INDEX3] [varchar](255) NULL,
> [INDEX4] [varchar](255) NULL,
> [INDEX5] [varchar](255) NULL,
> [INDEX6] [varchar](255) NULL,
> [INDEX7] [varchar](255) NULL,
> [INDEX8] [varchar](255) NULL,
> [INDEX9] [varchar](255) NULL,
> [INDEX10] [varchar](255) NULL,
> [INDEX11] [varchar](255) NULL,
> [INDEX12] [varchar](255) NULL,
> [INDEX13] [varchar](255) NULL,
> [INDEX14] [varchar](255) NULL,
> [INDEX15] [varchar](255) NULL,
> [UsageCount] [int] NOT NULL,
> [PageJump] [int] NULL,
> [DocumentSize] [int] NULL,
> [IsApproved] [bit] NOT NULL,
> [CustomProperties] [xml] NOT NULL,
> [IsEnabled] [bit] NOT NULL,
> [msrepl_tran_version] [uniqueidentifier] NOT NULL,
> CONSTRAINT [PK_Docs] PRIMARY KEY NONCLUSTERED
> (
> [ID] ASC
> )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
> OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
> CONSTRAINT [DocumentID] UNIQUE NONCLUSTERED
> (
> [DocumentID] ASC
> )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
> OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
> ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
From: Jay on
I think you just hit the nail on the head Gert-Jan and are answering the
actual question. Let me elaborate.

In the original post Ron says: "As COL3 is not in the index", which implies
the OP thinks that the data pages for the table need to be read too - which
isn't the case for a clustered index.

Clustered indexes physically change the way data is stored, in that indexes
no longer point to data pages. The data is the leaf node of the index
itself.

Do a search in BOL for "Clustered Index Structures" and "Nonclustered Index
Structures", or use these links for 2008 BOL. There are diagrams that show
you what is happening very clearly.

ms-help://MS.SQLCC.v10/MS.SQLSVR.v10.en/s10de_0evalplan/html/26b28045-c3c2-465a-b564-bf2189e93fdc.htm
ms-help://MS.SQLCC.v10/MS.SQLSVR.v10.en/s10de_0evalplan/html/1efeba1f-f848-4861-9af3-594e5ab3b597.htm

Jay

"Gert-Jan Strik" <sorrytoomuchspamalready(a)xs4all.nl> wrote in message
news:4B1A50D6.9945F462(a)xs4all.nl...
> Ron,
>
> In case it wasn't already clear from Jay's response: the clustered index
> is seeked because the predicate ApplicationID=99 uses the leading column
> of this index, and because it is the clustered index, the index entries
> include the rest of the row data, so they include INDEX6.
>
> What might not be clear when you read the query plan, is that the index
> seek is only used to locate the first row (with ApplicationID=99). From
> that point on, it will scan through the index to validate your other
> predicate (INDEX6 LIKE '%W%'). It will do this until ApplicationID > 99
> or until 5000 matching rows are found.
>
> --
> Gert-Jan
> SQL Server MVP
>
>
> Ron wrote:
>>
>> Hi, yes, here is the full query, exec plan (SHOWPLAN_TEXT), table and
>> Index.
>> (I tried to reduce it down to an example for simplicity before, and fell
>> into
>> the trap of providing less than useful info). I've tried to lay this out
>> as
>> best as possible....
>> Just to repeat, my question is why is there a clustered index seek, when
>> index6 isn't in the clustered index (and is not SARGable?). Any thoughts
>> greatly appreciated.
>>
>> ----Execution Plan----
>>
>> |--Top(TOP EXPRESSION:((5000)))
>> |--Clustered Index
>> Seek(OBJECT:([_archive].[dbo].[Docs].[IX_DOCUMENTS]
>> AS [D]), SEEK:([D].[ApplicationID]=(99)),
>> WHERE:([dbtest].[dbo].[Docs].[INDEX6] as [D].[INDEX6] like '%W%') ORDERED
>> FORWARD)
>>
>> ----Query
>>
>> SELECT TOP 5000 [DocumentID], [ImportDate] FROM Docs AS D
>> WHERE APPLICATIONID=99
>> AND D.INDEX6 LIKE '%W%'
>>
>> ----Index
>>
>> CREATE CLUSTERED INDEX [IX_DOCUMENTS] ON [dbo].[Docs]
>> (
>> [ApplicationID] ASC,
>> [IsTest] ASC,
>> [BatchID] ASC,
>> [ImportDate] DESC
>> )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB =
>> OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF,
>> ALLOW_ROW_LOCKS
>> = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
>> GO
>>
>> ----Table
>>
>> CREATE TABLE [dbo].[Docs](
>> [ID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
>> [DocumentID] [uniqueidentifier] NOT NULL,
>> [ApplicationID] [smallint] NOT NULL,
>> [BatchID] [int] NOT NULL,
>> [ImportDate] [datetime] NULL,
>> [ValidFrom] [datetime] NOT NULL,
>> [ValidTo] [datetime] NULL,
>> [IsTest] [bit] NOT NULL,
>> [DocumentName] [varchar](255) NOT NULL,
>> [Extension] [varchar](50) NOT NULL,
>> [IsCompressed] [bit] NOT NULL,
>> [IsAttachmentOnly] [bit] NOT NULL,
>> [Document] [image] NULL,
>> [INDEX1] [varchar](255) NULL,
>> [INDEX2] [varchar](255) NULL,
>> [INDEX3] [varchar](255) NULL,
>> [INDEX4] [varchar](255) NULL,
>> [INDEX5] [varchar](255) NULL,
>> [INDEX6] [varchar](255) NULL,
>> [INDEX7] [varchar](255) NULL,
>> [INDEX8] [varchar](255) NULL,
>> [INDEX9] [varchar](255) NULL,
>> [INDEX10] [varchar](255) NULL,
>> [INDEX11] [varchar](255) NULL,
>> [INDEX12] [varchar](255) NULL,
>> [INDEX13] [varchar](255) NULL,
>> [INDEX14] [varchar](255) NULL,
>> [INDEX15] [varchar](255) NULL,
>> [UsageCount] [int] NOT NULL,
>> [PageJump] [int] NULL,
>> [DocumentSize] [int] NULL,
>> [IsApproved] [bit] NOT NULL,
>> [CustomProperties] [xml] NOT NULL,
>> [IsEnabled] [bit] NOT NULL,
>> [msrepl_tran_version] [uniqueidentifier] NOT NULL,
>> CONSTRAINT [PK_Docs] PRIMARY KEY NONCLUSTERED
>> (
>> [ID] ASC
>> )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
>> OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
>> CONSTRAINT [DocumentID] UNIQUE NONCLUSTERED
>> (
>> [DocumentID] ASC
>> )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
>> OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
>> ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]


From: Ron on
Thanks Gert and Jay for your answers. Yes I see what you both mean, and
understand. Your time appreciated.

I suppose that when I see a 'seek' here, it doesn't necessarily mean that
there isn't still a gain to be made somewhere. Ie the 'scan' you mention on
index6 after the 'seek' on application id might benefit from a nonclustered
index on index6?(specifically if the LIKE was SARGable ie (INDEX6 LIKE 'W%'))

Any thoughts?

"Gert-Jan Strik" wrote:

> Ron,
>
> In case it wasn't already clear from Jay's response: the clustered index
> is seeked because the predicate ApplicationID=99 uses the leading column
> of this index, and because it is the clustered index, the index entries
> include the rest of the row data, so they include INDEX6.
>
> What might not be clear when you read the query plan, is that the index
> seek is only used to locate the first row (with ApplicationID=99). From
> that point on, it will scan through the index to validate your other
> predicate (INDEX6 LIKE '%W%'). It will do this until ApplicationID > 99
> or until 5000 matching rows are found.
>
> --
> Gert-Jan
> SQL Server MVP
>
>
> Ron wrote:
> >
> > Hi, yes, here is the full query, exec plan (SHOWPLAN_TEXT), table and Index.
> > (I tried to reduce it down to an example for simplicity before, and fell into
> > the trap of providing less than useful info). I've tried to lay this out as
> > best as possible....
> > Just to repeat, my question is why is there a clustered index seek, when
> > index6 isn't in the clustered index (and is not SARGable?). Any thoughts
> > greatly appreciated.
> >
> > ----Execution Plan----
> >
> > |--Top(TOP EXPRESSION:((5000)))
> > |--Clustered Index Seek(OBJECT:([_archive].[dbo].[Docs].[IX_DOCUMENTS]
> > AS [D]), SEEK:([D].[ApplicationID]=(99)),
> > WHERE:([dbtest].[dbo].[Docs].[INDEX6] as [D].[INDEX6] like '%W%') ORDERED
> > FORWARD)
> >
> > ----Query
> >
> > SELECT TOP 5000 [DocumentID], [ImportDate] FROM Docs AS D
> > WHERE APPLICATIONID=99
> > AND D.INDEX6 LIKE '%W%'
> >
> > ----Index
> >
> > CREATE CLUSTERED INDEX [IX_DOCUMENTS] ON [dbo].[Docs]
> > (
> > [ApplicationID] ASC,
> > [IsTest] ASC,
> > [BatchID] ASC,
> > [ImportDate] DESC
> > )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB =
> > OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS
> > = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
> > GO
> >
> > ----Table
> >
> > CREATE TABLE [dbo].[Docs](
> > [ID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
> > [DocumentID] [uniqueidentifier] NOT NULL,
> > [ApplicationID] [smallint] NOT NULL,
> > [BatchID] [int] NOT NULL,
> > [ImportDate] [datetime] NULL,
> > [ValidFrom] [datetime] NOT NULL,
> > [ValidTo] [datetime] NULL,
> > [IsTest] [bit] NOT NULL,
> > [DocumentName] [varchar](255) NOT NULL,
> > [Extension] [varchar](50) NOT NULL,
> > [IsCompressed] [bit] NOT NULL,
> > [IsAttachmentOnly] [bit] NOT NULL,
> > [Document] [image] NULL,
> > [INDEX1] [varchar](255) NULL,
> > [INDEX2] [varchar](255) NULL,
> > [INDEX3] [varchar](255) NULL,
> > [INDEX4] [varchar](255) NULL,
> > [INDEX5] [varchar](255) NULL,
> > [INDEX6] [varchar](255) NULL,
> > [INDEX7] [varchar](255) NULL,
> > [INDEX8] [varchar](255) NULL,
> > [INDEX9] [varchar](255) NULL,
> > [INDEX10] [varchar](255) NULL,
> > [INDEX11] [varchar](255) NULL,
> > [INDEX12] [varchar](255) NULL,
> > [INDEX13] [varchar](255) NULL,
> > [INDEX14] [varchar](255) NULL,
> > [INDEX15] [varchar](255) NULL,
> > [UsageCount] [int] NOT NULL,
> > [PageJump] [int] NULL,
> > [DocumentSize] [int] NULL,
> > [IsApproved] [bit] NOT NULL,
> > [CustomProperties] [xml] NOT NULL,
> > [IsEnabled] [bit] NOT NULL,
> > [msrepl_tran_version] [uniqueidentifier] NOT NULL,
> > CONSTRAINT [PK_Docs] PRIMARY KEY NONCLUSTERED
> > (
> > [ID] ASC
> > )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
> > OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
> > CONSTRAINT [DocumentID] UNIQUE NONCLUSTERED
> > (
> > [DocumentID] ASC
> > )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
> > OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
> > ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
> .
>
From: Erland Sommarskog on
Ron (Ron(a)discussions.microsoft.com) writes:
> I suppose that when I see a 'seek' here, it doesn't necessarily mean
> that there isn't still a gain to be made somewhere. Ie the 'scan' you
> mention on index6 after the 'seek' on application id might benefit from
> a nonclustered index on index6?(specifically if the LIKE was SARGable ie
> (INDEX6 LIKE 'W%'))

First of all, if you have an index on INDEX6 and do

SELECT ... FROM tbl WHERE INDEX6 LIKE '%W%'

you may actually see an Index Seek on this index. Of course, that is
no real index seek, the predicate includes a RangeSeek function, and in
practice it is a scan.

For the query:

SELECT TOP 5000 [DocumentID], [ImportDate] FROM Docs AS D
WHERE APPLICATIONID=99
AND D.INDEX6 LIKE 'W%'

If we have a clustered index on APPLICATION and add an NC-index on INDEX6,
will that help? Maybe. The optimizer will have a choice and look at
statistics. If it estimates that there are pitiful rows with ID = 99,
it will of course use the clustered index. On the other hand, if there are
few rows with INDEX6 starting with W, this is where the optimizer will
go.

If they are few of both, I would expect the optimizer to use the non-
clustered index, since it's faster to scan. (Recall that the indexes
in the clustered index is in the NC-index too.) But if there are
many of both, I would expect the clustered index to be used, since
then there is no over head to read the data rows.

--
Erland Sommarskog, SQL Server MVP, esquel(a)sommarskog.se

Links for SQL Server Books Online:
SQL 2008: http://msdn.microsoft.com/en-us/sqlserver/cc514207.aspx
SQL 2005: http://msdn.microsoft.com/en-us/sqlserver/bb895970.aspx
SQL 2000: http://www.microsoft.com/sql/prodinfo/previousversions/books.mspx

From: Gert-Jan Strik on
Erland,

I am not sure if you made a typo, or have had different experiences, but
I would NEVER expect to see a clustered index SEEK for the query

> SELECT ... FROM tbl WHERE INDEX6 LIKE '%W%'

because the pattern starts with a '%'. As you know, this disqualifies an
index seek.

Ron,

If you are running SQL Server 2005 or later, and the index on INDEX6
supports String Summary Statistics (which you can verify with the DBCC
SHOW_STATISTICS if you look at the String Index value in the
STAT_HEADER), then an index on INDEX6 might be beneficial. But it will
only be beneficial if the number of qualifying rows is very low for
INDEX6 LIKE '%W%', the number of qualifying rows for ApplicationID=99 is
very high and the rows are very wide (many bytes of storage).

If the optimizer then decides to use the index on INDEX6, then it will
be scanned (full scan). So there is a very low probability that it will
help performance. But of course there is only one way to find out: test
it!

--
Gert-Jan
SQL Server MVP


Erland Sommarskog wrote:
>
> Ron (Ron(a)discussions.microsoft.com) writes:
> > I suppose that when I see a 'seek' here, it doesn't necessarily mean
> > that there isn't still a gain to be made somewhere. Ie the 'scan' you
> > mention on index6 after the 'seek' on application id might benefit from
> > a nonclustered index on index6?(specifically if the LIKE was SARGable ie
> > (INDEX6 LIKE 'W%'))
>
> First of all, if you have an index on INDEX6 and do
>
> SELECT ... FROM tbl WHERE INDEX6 LIKE '%W%'
>
> you may actually see an Index Seek on this index. Of course, that is
> no real index seek, the predicate includes a RangeSeek function, and in
> practice it is a scan.
>
> For the query:
>
> SELECT TOP 5000 [DocumentID], [ImportDate] FROM Docs AS D
> WHERE APPLICATIONID=99
> AND D.INDEX6 LIKE 'W%'
>
> If we have a clustered index on APPLICATION and add an NC-index on INDEX6,
> will that help? Maybe. The optimizer will have a choice and look at
> statistics. If it estimates that there are pitiful rows with ID = 99,
> it will of course use the clustered index. On the other hand, if there are
> few rows with INDEX6 starting with W, this is where the optimizer will
> go.
>
> If they are few of both, I would expect the optimizer to use the non-
> clustered index, since it's faster to scan. (Recall that the indexes
> in the clustered index is in the NC-index too.) But if there are
> many of both, I would expect the clustered index to be used, since
> then there is no over head to read the data rows.
>
> --
> Erland Sommarskog, SQL Server MVP, esquel(a)sommarskog.se
>
> Links for SQL Server Books Online:
> SQL 2008: http://msdn.microsoft.com/en-us/sqlserver/cc514207.aspx
> SQL 2005: http://msdn.microsoft.com/en-us/sqlserver/bb895970.aspx
> SQL 2000: http://www.microsoft.com/sql/prodinfo/previousversions/books.mspx