Suitable indexes for sorting in ranking functions

2019-05-09 12:34发布

问题:

I have a table which keeps parent-child-relations between items. Those can be changed over time, and it is necessary to keep a complete history so that I can query how the relations were at any time.

The table is something like this (I removed some columns and the primary key etc. to reduce noise):

CREATE TABLE [tblRelation](
    [dtCreated] [datetime] NOT NULL,
    [uidNode] [uniqueidentifier] NOT NULL,
    [uidParentNode] [uniqueidentifier] NOT NULL
)

My query to get the relations at a specific time is like this (assume @dt is a datetime with the desired date):

SELECT *
  FROM (
    SELECT ROW_NUMBER() OVER (PARTITION BY r.uidNode ORDER BY r.dtCreated DESC) ix, r.*
      FROM [tblRelation] r
      WHERE (r.dtCreated < @dt)
    ) r
  WHERE r.ix = 1

This query works well. However, the performance is not yet as good as I would like. When looking at the execution plan, it basically boils down to a clustered index scan (36% of cost) and a sort (63% of cost).

What indexes should I use to make this query faster? Or is there a better way altogether to perform this query on this table?

回答1:

The ideal index for this query would be with key columns uidNode, dtCreated and included columns all remaining columns in the table to make the index covering as you are returning r.*. If the query will generally only be returning a relatively small number of rows (as seems likely due to the WHERE r.ix = 1 filter) it might not be worthwhile making the index covering though as the cost of the key lookups might not outweigh the negative effects of the large index on CUD statements.



回答2:

The window/rank functions on SQL Server 2005 are not that optimal sometimes (based on answers here). Apparently better in SQL Server 2008

Another alternative is something like this. I'd have a non-clustered index on (uidNode, dtCreated) INCLUDE any other columns required by SELECT. Subject to what Martin Smith said about lookups.

WITH MaxPerUid AS
(
    SELECT
       MAX(r.dtCreated) AS MAXdtCreated, r.uidNode
    FROM
       MaxPerUid
    WHERE
       r.dtCreated < @dt
    GROUP BY
       r.uidNode
)
SELECT
    ...
FROM
   MaxPerUid M
   JOIN
   MaxPerUid R ON M.uidNode = R.uidNode AND M.MAXdtCreated = R.dtCreated