Database Administrators
Q&A for database professionals who wish to improve their database skills
Latest Questions
1
votes
2
answers
221
views
Why can SQL Server not perform a TOP N SORT between an Index Seek and the Key Lookup?
I am looking into a minor performance issue, where an optimizer tool is basically saying, "hey just go ahead and include all the columns on this table in this index" which is a horrible solution in my opinion. My thought process brought me to think "why don't I go ahead and give it everything in thi...
I am looking into a minor performance issue, where an optimizer tool is basically saying, "hey just go ahead and include all the columns on this table in this index" which is a horrible solution in my opinion.
My thought process brought me to think "why don't I go ahead and give it everything in this existing index to do its sort and TOP(N) operation and cut out this massive 100k key lookup operation, surely SQL Server can do that and change from 100k key lookups to N."
That was not what I saw, what I saw was that nothing changed at all, it still did all the key lookups and sort after that. As below
Very simply removing the select of the other columns not in the index changes it to not need a key lookup at all of course.
I have seen many workarounds using CTE to get around this, but I am using Entity Framework for this query and simply playing around with the query isn't as easy as that.
I would like the primary purpose of this question to be WHY does this happen? Seems like a trivial operation thing to do the sort and top clause prior to the key lookup loop if you are able. It not doing this seems a glaring weakness in the platform.
I am asking why this behavior exists, not how can I improve this query's performance.

Morgeth888
(113 rep)
Oct 2, 2023, 05:16 PM
• Last activity: Oct 5, 2023, 09:16 AM
14
votes
1
answers
837
views
Why am I seeing key lookups for all rows read, instead of all rows matching the where clause?
I have a table such as the following: ``` create table [Thing] ( [Id] int constraint [PK_Thing_Id] primary key, [Status] nvarchar(20), [Timestamp] datetime2, [Foo] nvarchar(100) ) ``` with a non-clustered, non-covering index on the `Status` and `Timestamp` fields: ``` create nonclustered index [IX_S...
I have a table such as the following:
If I rephrase the query with a cte, as follows, I get more or less what I expected the first query to do:
Some statistics from SSMS to compare the 2 queries:
| | Original | With CTE |
|---------------|----------|----------|
| Logical reads | 12265 | 4140 |
| Subtree cost | 9.79 | 3.33 |
| Memory grant | 0 | 3584 KB |
The CTE version seems 'better' at first glance, although I don't know how much weight to place on the fact that it incurs a memory grant for a worktable. (The messages from
create table [Thing]
(
[Id] int constraint [PK_Thing_Id] primary key,
[Status] nvarchar(20),
[Timestamp] datetime2,
[Foo] nvarchar(100)
)
with a non-clustered, non-covering index on the Status
and Timestamp
fields:
create nonclustered index [IX_Status_Timestamp] on [Thing] ([Status], [Timestamp] desc)
If I query for a 'page' of these rows, using offset/fetch as follows,
select * from [Thing]
where Status = 'Pending'
order by [Timestamp] desc
offset 2000 rows
fetch next 1000 rows only
I understand that the query will need to read a total of 3000 rows to find the 1000 that I'm interested in. I would then expect it to perform key lookups for each of those 1000 rows to fetch the fields not included in the index.
However, the execution plan indicates that it is doing key lookups for all 3000 rows. I don't understand why, when the only criteria (filter by [Status] and order by [Timestamp]) are both in the index.

with ids as
(
select Id from [Thing]
where Status = 'Pending'
order by [Timestamp] desc
offset 2000 rows
fetch next 1000 rows only
)
select t.* from [Thing] t
join ids on ids.Id = t.Id
order by [Timestamp] desc

set statistics io on
indicate that there were zero reads of any kind on the worktable)
Am I wrong in saying that the first query should be able to isolate the relevant 1000 rows first (even though that requires reading past 2000 other rows first), and then only do key lookups on those 1000? It seems a bit odd to have to try and 'force' that behaviour with the CTE query.
(As a minor second question: I'm assuming that the last part of the CTE approach needs to do its own order by
on the results of the join, even though the CTE itself had an order by
, as the ordering might be lost during the join. Is this correct?)
Twicetimes
(263 rep)
Feb 7, 2022, 05:35 AM
• Last activity: Feb 7, 2022, 12:28 PM
0
votes
2
answers
236
views
Key Lookup isn't applied by default?
I'm trying to learn about covering indexes. In the Northwind database, I select from the table *Categories*: [![enter image description here][1]][1] As you can see the table has a non-clustered index on the column *CategoryName*. This SQL query: select CategoryName from Categories where Categories.C...
I'm trying to learn about covering indexes. In the Northwind database, I select from the table *Categories*:
As you can see the table has a non-clustered index on the column *CategoryName*.
This SQL query:
select CategoryName from Categories where Categories.CategoryName like 'Beverages'
returns an execution plan with an index seek:
However, this:
select CategoryName ,Description from Categories where Categories.CategoryName like 'Beverages'
returns this execution plan with an index scan using the primary key index, which isn't expected:
I can find the expected behaviour only when I force the query with the non-clustered index:
select CategoryName ,Description from Categories
with(index(CategoryName))
where Categories.CategoryName like 'Beverages'
What is the problem?




A.HADDAD
(119 rep)
Dec 20, 2020, 01:59 PM
• Last activity: Dec 20, 2020, 02:36 PM
18
votes
2
answers
6070
views
Why is table variable forcing an index scan while temp table uses seek and bookmark lookup?
I am trying to understand why using a table variable is preventing the optimizer from using an index seek and then bookmark lookup versus an index scan. Populating the table: CREATE TABLE dbo.Test ( RowKey INT NOT NULL PRIMARY KEY, SecondColumn CHAR(1) NOT NULL DEFAULT 'x', ForeignKey INT NOT NULL )...
I am trying to understand why using a table variable is preventing the optimizer from using an index seek and then bookmark lookup versus an index scan.
Populating the table:
CREATE TABLE dbo.Test
(
RowKey INT NOT NULL PRIMARY KEY,
SecondColumn CHAR(1) NOT NULL DEFAULT 'x',
ForeignKey INT NOT NULL
)
INSERT dbo.Test
(
RowKey,
ForeignKey
)
SELECT TOP 1000000
ROW_NUMBER() OVER (ORDER BY (SELECT 0)),
ABS(CHECKSUM(NEWID()) % 10)
FROM sys.all_objects s1
CROSS JOIN sys.all_objects s2
CREATE INDEX ix_Test_1 ON dbo.Test (ForeignKey)
Populate a table variable with a single record and attempt to lookup the primary key and the second column by searching on the foreign key column:
DECLARE @Keys TABLE (RowKey INT NOT NULL)
INSERT @Keys (RowKey) VALUES (10)
SELECT
t.RowKey,
t.SecondColumn
FROM
dbo.Test t
INNER JOIN
@Keys k
ON
t.ForeignKey = k.RowKey
Below is the execution plan:
Now the same query using a temp table instead:
CREATE TABLE #Keys (RowKey INT NOT NULL)
INSERT #Keys (RowKey) VALUES (10)
SELECT
t.RowKey,
t.SecondColumn
FROM
dbo.Test t
INNER JOIN
#Keys k
ON
t.ForeignKey = k.RowKey
This query plan uses a seek and bookmark lookup:
Why the optimizer is willing do the bookmark lookup with the temp table, but not the table variable?
The table variable is used in this example to represent data coming through a user-defined table type in a stored procedure.
I realize the index seek might not be appropriate if the foreign key value occurred hundreds of thousands of times. In that case, a scan would probably be a better choice. For the scenario I created, there was no row with a value of 10. I still think the behavior is interesting and would like to know if there is a reason for it.
SQL Fiddle
Adding


OPTION (RECOMPILE)
did not change the behavior. The UDDT has a primary key.
@@VERSION
is SQL Server 2008 R2 (SP2) - 10.50.4042.0 (X64) (Build 7601: Service Pack 1) (Hypervisor)
8kb
(2639 rep)
Jul 28, 2015, 06:03 PM
• Last activity: May 7, 2020, 09:27 PM
2
votes
1
answers
230
views
Key lookup still happening after creating covering index
I've implemented a covering index to avoid a key lookup: CREATE INDEX IX_StatusHistory_Covering ON StatusHistory(ID) INCLUDE (Status_ID, StatusComment, StatusReason_ID, StatusReasonComment, UserEnteredStatusDateTime, ChangeDateTime, ChangedBy_UserName, IMWBWagonMass) WITH (ONLINE= ON) But the key lo...
I've implemented a covering index to avoid a key lookup:
CREATE INDEX IX_StatusHistory_Covering ON StatusHistory(ID)
INCLUDE (Status_ID, StatusComment, StatusReason_ID,
StatusReasonComment, UserEnteredStatusDateTime,
ChangeDateTime, ChangedBy_UserName, IMWBWagonMass)
WITH (ONLINE= ON)
But the key lookup is still happening.
Execution Plan
Here is the query causing this:
DECLARE @default_tare DECIMAL(18,2) = 19.94
SELECT TOP 100 *,
[Gross (WI)] - ISNULL([Tare (WX)],@default_tare) AS [Arrived Nett (WI-WX)],
[Gross (WN)] - ISNULL([Tare (WT)],@default_tare) AS [Tipped Nett (WN-WT)],
[Client Weight] - ([Gross (WI)] - ISNULL([Tare (WX)],@default_tare)) AS [Arrived Variance],
[Client Weight] - ([Gross (WN)] - ISNULL([Tare (WT)],@default_tare)) AS [Tipped Variance]
FROM (SELECT CASE WHEN LoadedWeight > 9999 THEN LoadedWeight/1000 ELSE LoadedWeight END [Client Weight]
,CASE WHEN his.Status_Code IN ('WH','WI') THEN his.IMWBWagonMass END AS [Gross (WI)]
,CASE WHEN his.Status_Code ='WN' THEN his.IMWBWagonMass END AS [Gross (WN)]
,CASE WHEN his.Status_Code = 'WX' THEN his.[IMWBWagonMass] ELSE NULL END AS [Tare (WX)]
,CASE WHEN his.Status_Code = 'WT' THEN his.[IMWBWagonMass] ELSE NULL END AS [Tare (WT)]
,dp.Description AS Commodity
,dp.Grade
,Header_Client_Name AS Client
,Header_Destination_Name AS Destination
,Header_Origin_Name AS Origin
,Header_Product_Name AS [Commodity Name]
,Header_ProductSubCategory_Name AS [Commodity Group]
,his.StatusComment AS [Status Comment]
,LEFT(LOWER(Rcd.Header_LastUpdatedByUser_UserName),CASE WHEN Header_LastUpdatedByUser_UserName 'System' THEN CHARINDEX( '@',Rcd.Header_LastUpdatedByUser_UserName)-1 ELSE 999 END) AS [Last updated by]
,ISNULL(Header_StatusDateTime, Header_CreatedDateTime) AS [Last Updated]
,st.Name AS [Status Name]
,StatusReasonComment AS [Reason Comment]
,StatusReasonName AS [Reason Name]
,UserEnteredStatusDateTime AS [Status Actual Event Time]
,ChangeDateTime AS [Status System Change Time]
,ChangedBy_UserName AS [Status Changed by]
,st.Code AS [Status Code]
,sr.Code AS [Reason Code]
,his.ID AS [Status History ID]
,his.StatusReasonDateTime AS [Reason Time]
,his.IMWBWagonMass AS [Wagon Status History Mass]
,rcd.ID AS [Wagon Line ID]
,Wagon_Number AS [Wagon Number]
,GrossWeight AS [Gross (Most Recent)]
,TareWeight AS [Tare (Most Recent)]
,Tarps
,DelayDescription AS [Delay Description]
,rcd.StatusComment AS [Current Wagon Status Comment]
,rcd.StatusDateTime AS [Current Wagon Status Date Time]
,rcd.Status_Code AS [Current Wagon Status Code]
,rcd.Status_Name AS [Current Wagon Status]
,RailConsignment_ID
,Header_Number AS [Consignment]
,Header_DepartureFromClientDateTime AS [Departure Time]
,Header_EstimatedArrivalDateTime AS [Estimated Arrival Time]
,Header_IsInbound
,Header_PermitNumber AS [Permit Nr]
,Header_RailAccountNumber AS [Rail Account Nr]
,Header_Sender_Name AS [Sender]
,Header_SenderSidingNumber AS [Sender Siding Nr]
,Header_Status_Code AS [Train Current Status Code]
,Header_Status_Name AS [Train Current Status]
,Header_StatusDateTime AS [Train Current Status Actual Event Time]
,Header_Train AS [Train]
,Header_CreatedDateTime AS [Created On]
,rcd.dw_timestamp
,CreatedDateTime AS [Wagon Created On]
,Header_Contract_Number AS [Contract Nr]
,rcd.IMWBWagonMass AS [Wagon Mass (Most Recent)]
,Header_WeeklyRailPlan_ID AS [NWB ID]
,NettWeight AS [Nett (Most Recent)]
,CASE WHEN rcd.Status_Code = 'WA' THEN rcd.Header_StatusDateTime ELSE Header_WAChangeDateTime END AS [Wagons Uncoupled From Locomotive]
,CASE WHEN rcd.Status_Code = 'WF' THEN rcd.Header_StatusDateTime ELSE Header_WFChangeDateTime END AS [Consignment Finalised]
,CASE WHEN rcd.Status_Code = 'WG' THEN rcd.Header_StatusDateTime ELSE Header_WGChangeDateTime END AS [Consignment at the Gate]
FROM RailConsignmentDetails AS rcd WITH(NOLOCK)
JOIN FullStatusHistoryRailLine AS his WITH(NOLOCK) on his.ID = rcd.ID
JOIN DimProduct AS dp WITH(NOLOCK) ON dp.ID = rcd.Header_Product_ID
LEFT JOIN DimConsignmentStatus AS st WITH(NOLOCK)ON st.ID = his.Status_ID
LEFT JOIN DimStatusReasons AS sr WITH(NOLOCK) ON st.ID = his.StatusReason_ID
WHERE Header_Deleted = 0
AND rcd.Deleted = 0
AND ISNULL(Header_StatusDateTime, Header_CreatedDateTime) BETWEEN CAST(DATEADD(MONTH,-2,GETDATE()) AS DATE) AND GETDATE()+ 1) AS t
The FullStatusHistoryLine view is:
CREATE VIEW [dbo].[FullStatusHistoryRailLine]
AS
SELECT sh.PK,
sh.ID,
CAST(sh.Number AS VARCHAR(255)) AS Number,
sh.Status_ID,
sh.Status_Name,
sh.StatusComment,
sh.StatusReason_ID,
sh.StatusReasonComment,
dsr.Name AS StatusReasonName,
sh.UserEnteredStatusDateTime,
sh.ChangeDateTime,
sh.ChangedBy_Id,
sh.ChangedBy_UserName,
sh.dw_timestamp,
dcs.code AS Status_Code,
sh.StatusReason_Code,
prev.StatusReasonDateTime AS FromStatusReasonDateTime,
sh.StatusReasonDateTime,
sh.IMWBWagonMass
FROM StatusHistory AS sh
INNER JOIN DimConsignmentStatus AS dcs ON sh.Status_ID = dcs.ID
LEFT JOIN DimStatusReasons AS dsr ON dsr.ID = sh.StatusReason_ID
OUTER APPLY (SELECT TOP 1 StatusReasonDateTime
FROM StatusHistory
WHERE ID = sh.ID
AND Number = sh.Number
AND IsHeader = sh.IsHeader
AND TransportMode_Name = sh.TransportMode_Name
AND StatusReasonDateTime < sh.StatusReasonDateTime
ORDER BY StatusReasonDateTime DESC) AS prev
WHERE sh.TransportMode_Name = 'Rail'
AND sh.IsHeader = 0
UNION ALL
SELECT CAST(rcd.ID AS VARCHAR) + '_' + CAST(ISNULL(CONVERT(VARCHAR(64),rcd.StatusReasonDateTime,126),CONVERT(VARCHAR(64),rcd.LastUpdatedDateTime,126)) AS VARCHAR) + '_L' AS PK,
rcd.ID,
CAST(rcd.Wagon_Number AS VARCHAR(255)) AS Number,
rcd.Status_ID,
rcd.Status_Name,
rcd.StatusComment,
rcd.StatusReason_ID,
'' AS StatusReasonComment,
rcd.StatusReason_Name AS StatusReasonName,
rcd.StatusDateTime AS UserEnteredStatusDateTime,
rcd.StatusSavedDateTime AS ChangeDateTime,
rcd.LastUpdatedByUser_Id AS ChangedBy_Id,
rcd.LastUpdatedByUser_UserName AS ChangedBy_UserName,
rcd.dw_timestamp,
rcd.Status_Code,
rcd.StatusReason_Code,
prev.StatusReasonDateTime AS FromStatusReasonDateTime,
rcd.StatusReasonDateTime,
rcd.IMWBWagonMass
FROM RailConsignmentDetails AS rcd
OUTER APPLY (SELECT TOP 1 StatusReasonDateTime
FROM StatusHistory AS sh
WHERE sh.ID = rcd.ID
AND Number = CAST(rcd.Wagon_Number AS VARCHAR(255))
AND IsHeader = 0
AND TransportMode_Name = 'Rail'
AND StatusReasonDateTime < rcd.Header_StatusReasonDateTime
ORDER BY StatusReasonDateTime DESC) AS prev
And finally the DDL:
CREATE TABLE [dbo].[StatusHistory](
[PK] [varchar](99) NOT NULL,
[ID] [int] NOT NULL,
[Number] [varchar](255) NULL,
[Status_ID] [int] NULL,
[Status_Name] [nvarchar](max) NOT NULL,
[StatusComment] [varchar](255) NULL,
[StatusReason_ID] [int] NULL,
[StatusReasonComment] [varchar](255) NULL,
[UserEnteredStatusDateTime] [datetime] NULL,
[ChangeDateTime] [datetime] NULL,
[ChangedBy_Id] [nvarchar](128) NOT NULL,
[ChangedBy_UserName] [nvarchar](256) NOT NULL,
[IsHeader] [bit] NOT NULL,
[dw_timestamp] [datetime] NOT NULL,
[EventTime] [datetime] NOT NULL,
[StatusReason_Code] [nvarchar](max) NULL,
[StatusReasonDateTime] [datetime] NULL,
[TransportMode_Name] [varchar](5) NULL,
[IMWBWagonMass] [decimal](18, 2) NULL,
[RoadFirstWeight] [decimal](18, 2) NULL,
[RoadSecondWeight] [decimal](18, 2) NULL,
[DraftSurveyTons] [decimal](18, 2) NULL,
[StatusHistory_key] [int] IDENTITY(1,1) NOT NULL,
CONSTRAINT [XPK_StatusHistory] PRIMARY KEY CLUSTERED
(
[StatusHistory_key] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

Evan Barke
(33 rep)
Feb 27, 2020, 08:52 AM
• Last activity: Feb 27, 2020, 01:04 PM
7
votes
1
answers
711
views
Is there any difference with specifying the primary key as an include column in a nonclustered index?
Don't nonclustered indexes inherently store a reference to the primary key on a table so that it can do a key lookup as needed?...if so, is it any less or more performant to specify the primary key as an included column when creating a nonclustered index? Side question, why does the nonclustered ind...
Don't nonclustered indexes inherently store a reference to the primary key on a table so that it can do a key lookup as needed?...if so, is it any less or more performant to specify the primary key as an included column when creating a nonclustered index?
Side question, why does the nonclustered index default to storing the primary key and not the clustered index fields to do a key lookup on the table?...In the cases where the primary key is not the clustered index, isn't it slower for the key lookup to occur whereas if it stored the clustered index it could do the lookup that way?
J.D.
(40893 rep)
Feb 1, 2020, 10:41 PM
• Last activity: Feb 2, 2020, 10:59 AM
0
votes
2
answers
560
views
Are key lookups from non-clustered indexes always slower than a second query that does the lookup?
I've noticed in my system, whenever a non-clustered index is used in a query that has to also do a key lookup to get the additional fields being selected, it's faster for me to instead do two queries. The first with the non-clustered index inserting only the key field into a temp table (so no key lo...
I've noticed in my system, whenever a non-clustered index is used in a query that has to also do a key lookup to get the additional fields being selected, it's faster for me to instead do two queries.
The first with the non-clustered index inserting only the key field into a temp table (so no key lookup is performed) and the second using that temp table to join back to the original table to filter it down on the key and then select the fields I need.
I'm typically querying tables with hundreds of millions to tens of billions of rows when I notice this. I'm not sure if it can be related to the fact that I'm eliminating the key lookup when the table is first loaded into memory and instead I'm inserting the key into a temp table so that the subsequent field lookup query occurs between two tables already in memory?
The difference in time I'll see is usually significant too, e.g. on the order of minutes.
J.D.
(40893 rep)
Jan 30, 2020, 06:05 PM
• Last activity: Jan 30, 2020, 10:13 PM
1
votes
1
answers
133
views
Nasty Lookups in Query plan
I've been trying to get rid of expensive lookups in my query plan but cannot seem to get my head around it. I understand the idea of creating a covering index to eliminate the need for lookups but I don't know how to approach it with a complicated plan like this. Any ideas on how to approach this wo...
I've been trying to get rid of expensive lookups in my query plan but cannot seem to get my head around it.
I understand the idea of creating a covering index to eliminate the need for lookups but I don't know how to approach it with a complicated plan like this.
Any ideas on how to approach this would really be appreciated.
Query plan can be found here:
https://www.brentozar.com/pastetheplan/?id=HyBbQ0eRr
Thanks
Sicilian-Najdorf
(381 rep)
Dec 13, 2019, 09:02 AM
• Last activity: Dec 13, 2019, 10:50 AM
32
votes
3
answers
58530
views
Eliminate Key Lookup (Clustered) operator that slows down performance
How can I eliminate a Key Lookup (Clustered) operator in my execution plan? Table `tblQuotes` already has a clustered index (on `QuoteID`) and 27 nonclustered indexes, so I am trying not to create any more. I put the clustered index column `QuoteID` in my query, hoping it will help - but unfortunate...
How can I eliminate a Key Lookup (Clustered) operator in my execution plan?
Table
This is what the Key Lookup operator says:
Query:
declare
@EffDateFrom datetime ='2017-02-01',
@EffDateTo datetime ='2017-08-28'
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
IF OBJECT_ID('tempdb..#Data') IS NOT NULL
DROP TABLE #Data
CREATE TABLE #Data
(
QuoteID int NOT NULL, --clustered index
[EffectiveDate] [datetime] NULL, --not indexed
[Submitted] [int] NULL,
[Quoted] [int] NULL,
[Bound] [int] NULL,
[Exonerated] [int] NULL,
[ProducerLocationId] [int] NULL,
[ProducerName] [varchar](300) NULL,
[BusinessType] [varchar](50) NULL,
[DisplayStatus] [varchar](50) NULL,
[Agent] [varchar] (50) NULL,
[ProducerContactGuid] uniqueidentifier NULL
)
INSERT INTO #Data
SELECT
tblQuotes.QuoteID,
tblQuotes.EffectiveDate,
CASE WHEN lstQuoteStatus.QuoteStatusID >= 1 THEN 1 ELSE 0 END AS Submitted,
CASE WHEN lstQuoteStatus.QuoteStatusID = 2 or lstQuoteStatus.QuoteStatusID = 3 or lstQuoteStatus.QuoteStatusID = 202 THEN 1 ELSE 0 END AS Quoted,
CASE WHEN lstQuoteStatus.Bound = 1 THEN 1 ELSE 0 END AS Bound,
CASE WHEN lstQuoteStatus.QuoteStatusID = 3 THEN 1 ELSE 0 END AS Exonareted,
tblQuotes.ProducerLocationID,
P.Name + ' / '+ P.City as [ProducerName],
CASE WHEN tblQuotes.PolicyTypeID = 1 THEN 'New Business'
WHEN tblQuotes.PolicyTypeID = 3 THEN 'Rewrite'
END AS BusinessType,
tblQuotes.DisplayStatus,
tblProducerContacts.FName +' '+ tblProducerContacts.LName as Agent,
tblProducerContacts.ProducerContactGUID
FROM tblQuotes
INNER JOIN lstQuoteStatus
on tblQuotes.QuoteStatusID=lstQuoteStatus.QuoteStatusID
INNER JOIN tblProducerLocations P
On P.ProducerLocationID=tblQuotes.ProducerLocationID
INNER JOIN tblProducerContacts
ON dbo.tblQuotes.ProducerContactGuid = tblProducerContacts.ProducerContactGUID
WHERE DATEDIFF(D,@EffDateFrom,tblQuotes.EffectiveDate)>=0 AND DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <=0
AND dbo.tblQuotes.LineGUID = '6E00868B-FFC3-4CA0-876F-CC258F1ED22D'--Surety
AND tblQuotes.OriginalQuoteGUID is null
select * from #Data
Execution Plan:
tblQuotes
already has a clustered index (on QuoteID
) and 27 nonclustered indexes, so I am trying not to create any more.
I put the clustered index column QuoteID
in my query, hoping it will help - but unfortunately still the same.
Execution plan here .
Or view it:



Serdia
(707 rep)
Aug 8, 2017, 11:03 PM
• Last activity: Dec 13, 2018, 02:34 PM
4
votes
2
answers
7107
views
Performance differences between RID Lookup vs Key Lookup?
Are there any performance differences between when a non-clustered index uses the clustered index's key to locate the row vs when that table doesn't have a clustered index and the non-clustered index locates the row via the RID? Does different levels of fragmentation impact this performance comparis...
Are there any performance differences between when a non-clustered index uses the clustered index's key to locate the row vs when that table doesn't have a clustered index and the non-clustered index locates the row via the RID?
Does different levels of fragmentation impact this performance comparison as well? (E.g. in both scenarios the tables are 0% fragmented, vs 50%, vs 100%.)
J.D.
(40893 rep)
Sep 10, 2018, 06:30 PM
• Last activity: Sep 11, 2018, 02:21 PM
2
votes
1
answers
689
views
Can't reduce cost of query plan and get rid of Key Lookup because of cursor
I have tried to create a non-clustered index on the fields that are in the output list which are created_by and Chk1002 .I don't have a column called Chk1002 anywhere . I have read [here][1] that it has to do with the Cursor . Is there any way I could keep the Cursor and get reduce the cost of the Q...
I have tried to create a non-clustered index on the fields that are in the output list which are created_by and Chk1002 .I don't have a column called Chk1002 anywhere .
I have read here that it has to do with the Cursor .
Is there any way I could keep the Cursor and get reduce the cost of the Query plan and get rid of the Key Lookup?
DECLARE @ClaimUniqueNo INT
DECLARE ClaimAudit CURSOR
FOR
SELECT CA.Claim_Audit_ID, CA.Claim_Audit_Action, CA.Create_Date, CA.Created_By, C.Claim_Status
FROM dbo.Claim_Audit_Tbl CA LEFT JOIN Claim_Tbl C
ON CA.Claim_Unique_No = C.Claim_Unique_No
WHERE CA.Claim_Unique_No = @ClaimUniqueNo
ORDER BY Create_Date ASC


Lucy
(299 rep)
May 30, 2018, 03:20 PM
• Last activity: May 31, 2018, 03:10 PM
1
votes
0
answers
440
views
Reverse dictionary (key to sequence of values) search with NoSQL database
Suppose we have a dictionary that maps keys to sequences of values - for example: - potato: A, B, C - tomato: B - lettuce: C - carrot: A, C - cucumber: C I would like to perform queries such as: - query A, B, C returns potato - query C returns lettuce, cucumber (order shouldn't matter) I understand...
Suppose we have a dictionary that maps keys to sequences of values - for example:
- potato: A, B, C
- tomato: B
- lettuce: C
- carrot: A, C
- cucumber: C
I would like to perform queries such as:
- query A, B, C returns potato
- query C returns lettuce, cucumber (order shouldn't matter)
I understand that the reverse dictionary lookup could be done with normalized relational databases (as suggested in this post). However, my intuition is that a non relational database could better fit this problem, like a graph. Additionally, I'm looking for something read-optimized.
Is there a NoSQL database that fits this kind of problem? Where should I direct my search for such an implementation?
bountrisv
(11 rep)
Sep 19, 2017, 12:48 PM
6
votes
2
answers
10686
views
Reducing Key Lookups
I am using SQL server, and I have been looking closely at the concept of key lookups, http://blog.sqlauthority.com/2009/10/07/sql-server-query-optimization-remove-bookmark-lookup-remove-rid-lookup-remove-key-lookup/ So if you have a key lookup you can create an index with the 'include' columns to co...
I am using SQL server, and I have been looking closely at the concept of key lookups,
http://blog.sqlauthority.com/2009/10/07/sql-server-query-optimization-remove-bookmark-lookup-remove-rid-lookup-remove-key-lookup/
So if you have a key lookup you can create an index with the 'include' columns to cover the non index columns you have in the select statement.
For instance,
SELECT ID, FirstName FROM OneIndex WHERE City = 'Las Vegas'
GO
This index will include a key lookup,
CREATE NONCLUSTERED INDEX [IX_OneIndex_City] ON [dbo].[OneIndex]
(
[City] ASC
) ON [PRIMARY]
GO
But this one will remove the key lookup,
CREATE NONCLUSTERED INDEX [IX_OneIndex_Include] ON [dbo].[OneIndex]
(
City
) INCLUDE (FirstName,ID) ON [PRIMARY]
GO
I mean how much of an impact will this have on performance? The key lookup has an operator cost of 0.295969 (99%), but what does that really mean?
How do you know that you need the second index there, and at what point does it become the case that you are trying to add too many indexes and it is not worth it?
It seems to me that some queries can include index scans, key lookups, and still seem to perform very fast.
peter
(2187 rep)
Dec 13, 2011, 10:28 PM
• Last activity: Aug 13, 2017, 07:58 AM
25
votes
3
answers
3673
views
Index on Persisted Computed column needs key lookup to get columns in the computed expression
I have a persisted computed column on a table which is simply made up concatenated columns, e.g. CREATE TABLE dbo.T ( ID INT IDENTITY(1, 1) NOT NULL CONSTRAINT PK_T_ID PRIMARY KEY, A VARCHAR(20) NOT NULL, B VARCHAR(20) NOT NULL, C VARCHAR(20) NOT NULL, D DATE NULL, E VARCHAR(20) NULL, Comp AS A + '-...
I have a persisted computed column on a table which is simply made up concatenated columns, e.g.
CREATE TABLE dbo.T
(
ID INT IDENTITY(1, 1) NOT NULL CONSTRAINT PK_T_ID PRIMARY KEY,
A VARCHAR(20) NOT NULL,
B VARCHAR(20) NOT NULL,
C VARCHAR(20) NOT NULL,
D DATE NULL,
E VARCHAR(20) NULL,
Comp AS A + '-' + B + '-' + C PERSISTED NOT NULL
);
In this
So I forced the use of this index to see if it yielded a better plan:
SELECT t1.ID,
t1.Comp,
t1.D,
D2 = ( SELECT TOP 1 t2.D
FROM dbo.T t2
WHERE t2.Comp = t1.Comp
AND t2.D > t1.D
ORDER BY t2.D
)
FROM dbo.T t1 WITH (INDEX (IX_T_Comp_D))
WHERE t1.D IS NOT NULL
ORDER BY t1.Comp;
Which gave this plan
This shows that a Key lookup is being used, the details of which are:
Now, according to the SQL-Server documentation:
>You can create an index on a computed column that is defined with a deterministic, but imprecise, expression if the column is marked PERSISTED in the CREATE TABLE or ALTER TABLE statement. This means that the Database Engine stores the computed values in the table, and updates them when any other columns on which the computed column depends are updated. The Database Engine uses these persisted values when it creates an index on the column, and when the index is referenced in a query. This option enables you to create an index on a computed column when Database Engine cannot prove with accuracy whether a function that returns computed column expressions, particularly a CLR function that is created in the .NET Framework, is both deterministic and precise.
So if, as the docs say *"the Database Engine stores the computed values in the table"*, and the value is also being stored in my index, why is a Key Lookup required to get A, B and C when they are not referenced in the query at all? I assume they are being used to calculate Comp, but why? Also, why can the query use the index on
Comp
is not unique, and D is the valid from date of each combination of A, B, C
, therefore I use the following query to get the end date for each A, B, C
(basically the next start date for the same value of Comp):
SELECT t1.ID,
t1.Comp,
t1.D,
D2 = ( SELECT TOP 1 t2.D
FROM dbo.T t2
WHERE t2.Comp = t1.Comp
AND t2.D > t1.D
ORDER BY t2.D
)
FROM dbo.T t1
WHERE t1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY t1.Comp;
I then added an index to the computed column to assist in this query (and also others):
CREATE NONCLUSTERED INDEX IX_T_Comp_D ON dbo.T (Comp, D) WHERE D IS NOT NULL;
The query plan however surprised me. I would have thought that since I have a where clause stating that D IS NOT NULL
and I am sorting by Comp
, and not referencing any column outside of the index that the index on the computed column could be used to scan t1 and t2, but I saw a clustered index scan.



t2
, but not on t1
?
*[Queries and DDL on SQL Fiddle](http://sqlfiddle.com/#!3/e47a1/2)*
*N.B. I have tagged SQL Server 2008 because this is the version that my main problem is on, but I also get the same behaviour in 2012.*
GarethD
(693 rep)
Oct 24, 2013, 08:31 AM
• Last activity: Aug 12, 2017, 08:23 PM
1
votes
2
answers
1603
views
Optimizing key lookup in subquery with UNION ALL
I am trying to optimize following query (little bit more complex actually, but this is important part): SELECT Id, StatusDate, [...Lot Of Columns...] FROM ( ( SELECT Id, StatusDate, [...Lot Of Columns...] FROM Results_201505 WHERE A = 0, B = 1, C = 3 ) UNION ALL ( SELECT Id, StatusDate, [...Lot Of C...
I am trying to optimize following query (little bit more complex actually, but this is important part):
SELECT Id, StatusDate, [...Lot Of Columns...]
FROM ( (
SELECT Id, StatusDate, [...Lot Of Columns...]
FROM Results_201505
WHERE A = 0, B = 1, C = 3
) UNION ALL (
SELECT Id, StatusDate, [...Lot Of Columns...]
FROM Results_201504
WHERE A = 0, B = 1, C = 3
) UNION ALL (
SELECT Id, StatusDate, [...Lot Of Columns...]
FROM Results_201503
WHERE A = 0, B = 1, C = 3
)
) as result
ORDER BY StatusDate DESC
OFFSET xxx ROWS
FETCH NEXT 25 ROWS ONLY
On every table I've created index covering 'where' part:
CREATE NONCLUSTERED INDEX ix_asdf ON Results_asdf (StatusDate DESC)
INCLUDE (A, B, C)
I can't use index
A, B, C, StatusDate
, because the WHERE
conditions are changing depending on filters that are user selected. It can be WHERE A=, B=, C=
, but also WHERE B=, D=
or WHERE C=
or without WHERE
at all).
My idea is that SQL Server will perform fast index scan with my index, and delay key lookup until last possible moment (fetching top 25 rows) - this seems to me like most reasonable thing to do.
And indeed, if I comment out [... Lot of Columns...]
everything goes as planned - index is used, no key lookup is necessary and everything is very fast.
But in full form, SQL Server (according to execution plan, as shown by SSMS) tries to do key lookup *for each row*, before OFFSET ... FETCH 25
.
The reason is obvious (I believe) - Sql Server realizes that UNION ALL
concatenates multiple tables, so after UNION
it is too late to do key lookup (because after UNION
SQL Server knows only ID
, but doesn't know which results table it came from). ID
is globally unique, but query optimizer probably doesn't know that.
Of course I'm able to work this around with something like:
SELECT Id, StatusDate, [ some way to select columns from appropiate table depending on result.tbl ]
FROM (
(
SELECT Id, StatusDate, 'Results_201505' as tbl
FROM Results_201505
WHERE A = 0, B = 1, C = 3
) UNION ALL (
SELECT Id, StatusDate, 'Results_201504' as tbl
FROM Results_201504
WHERE A = 0, B = 1, C = 3
) UNION ALL (
SELECT Id, StatusDate, 'Results_201503' as tbl
FROM Results_201503
WHERE A = 0, B = 1, C = 3
)
) as result
ORDER BY StatusDate DESC
OFFSET xxx ROWS
FETCH NEXT 25 ROWS ONLY
But my question is - is there a better way? Some kind of hint to optimizer would be perfect, but if it's not possible - what's most elegant/fastest/best way to handle this query?
MSM
(113 rep)
May 13, 2015, 02:25 PM
• Last activity: Aug 12, 2017, 08:07 PM
4
votes
1
answers
275
views
How can a key lookup with zero executions and zero actual rows take time?
I have a query that is taking about 10 seconds to run and I am trying to optimize it. All the time is spent in the key lookup which is fine however in this case if I mouse over the key lookup in the actual execution plan it says Expected Rows : 1 Estimated Executions : 3341 Actual Rows : 0 Execution...
I have a query that is taking about 10 seconds to run and I am trying to optimize it.
All the time is spent in the key lookup which is fine however in this case if I mouse over the key lookup in the actual execution plan it says
Expected Rows : 1
Estimated Executions : 3341
Actual Rows : 0
Executions : 0
I understand in this case the statistics are expecting the index seek to find rows which is fine but as no rows were returned in the Index Seek why would the key lookup take any time? or even be shown in the query plan at all?
Gavin
(556 rep)
Sep 7, 2011, 04:39 PM
• Last activity: Aug 12, 2017, 08:02 PM
1
votes
1
answers
313
views
Key lookup partition pruning
I have a query inner joining multiple tables all of which are partitioned and have 10 partitions each: SELECT A.COL1, B.COL2, C.COL3 FROM A INNER JOIN B ON A.ID = B.ID INNER JOIN C ON A.ID = C.ID WHERE COL20 < 10000 ---- COL20 IS NOT THE PARTITION KEY COLUMN In the actual query execution plan, for o...
I have a query inner joining multiple tables all of which are partitioned and have 10 partitions each:
SELECT A.COL1, B.COL2, C.COL3
FROM A
INNER JOIN B ON A.ID = B.ID
INNER JOIN C ON A.ID = C.ID
WHERE COL20 < 10000 ---- COL20 IS NOT THE PARTITION KEY COLUMN
In the actual query execution plan, for one of the tables, there is a non-clustered index scan with a key lookup.
When I look at the properties for Key lookup in the actual execution plan, it looks like it's getting partition pruned.
I'm confused as to why that would happen, is this like a negative impact on the system. I understand that key lookups themselves are bad, but why does it show that only 7 partitions have been accessed? The properties say:
Key Lookup (Clustered)
----------------------------------
Actual number of rows: 215805
Actual Partition Count: 7
Actual Partitions Accessed: 3..9
My concern is around how the lookup works. In case, when there is no partitioning, when the lookup fetches the data from the data page for a particular key, would it do a seek or a scan to reach to this page? Would a direct key lookup perform better than a key lookup with partition pruning?
Amam
(399 rep)
Apr 24, 2013, 05:31 AM
• Last activity: Aug 12, 2017, 07:57 PM
Showing page 1 of 17 total questions