A: 

I would try to add an index on the foreign keys in your UserSiteRight_T table - they're not yet indexed, and an index on those fields should speed up the lookups:

CREATE NONCLUSTERED INDEX IX01_UserSiteRight
  ON UserSiteRight_T(UserID_i)

CREATE NONCLUSTERED INDEX IX02_UserSiteRight
  ON UserSiteRight_T(SiteID_i)  

and on your SitePath_T table as well:

CREATE NONCLUSTERED INDEX IX01_SitePath
  ON dbo.SitePath_T(SiteID_i)

Try to put these in place, then run your queries again, and compare the run times and the execution plans - do you see any improvement??

It's a common misconception, but SQL Server does not automatically put an index on a foreign key column (like SiteID_i on SitePath_T), even though the general consensus is that a foreign key is useful and potentially speeds up both enforcement of referential integrity, as well as JOINs over those foreign keys.

marc_s
+2  A: 

Based on:

SELECT sp.SiteID_i 
  FROM SitePath_t sp 
 WHERE EXISTS ( SELECT * 
              FROM [dbo].[SitePath_T] usp 
                 , [dbo].[UserSiteRight_T] uusr 
             WHERE uusr.SiteID_i = usp.SiteID_i 
               AND uusr.UserID_i = 2484 
               AND usp.Path_v LIKE sp.Path_v+'%' ) 

(which is just fine based on the fact that you're doing a Semi Join).

It's focussing (rightly) on the uusr table first, to find the records for that user. It's already doing a CIX Seek on that, which is good. From there, it's finding the corresponding records in usp according to the SiteID_i fields.

So next consider the fact that it wants to find the Sites by SiteID_i, and what kind of join you want this to be.

How about a Merge Join? That would be nice, but requires the data to be sorted on both sides. That's fine if the indexes are in the right order...

...and after that, you want to be finding stuff based on the Path. So how about:

CREATE INDEX ix_UUSR on [dbo].[UserSiteRight_T] (UserID_i, SiteID_i);
CREATE INDEX ix_usp on [dbo].[SitePath_T] (SiteID_i) INCLUDE (Path_v);

And then another index on SitePath_T that finds the SiteIDs you want:

CREATE INDEX ix_sp on [dbo].[SitePath_T] (Path_v) INCLUDE (SiteID_i);

There may be a Nested Loop used on this final one, but that's hopefully not too bad. The thing that's going to impact your system will be the first two indexes, which should let you see a Merge Join between the two tables in your EXISTS clause.

Rob Farley
So... think about the fact that you want the rows that come out of uusp to be in SiteID_i order, and that the rows coming out of usp will be in that same order, helping the join immensely. Then you have your paths, and there may still be some pain there, but potentially not too much.
Rob Farley
I was also going to say, "throw an covering index on SitePath_T (Path_v)", but he's already got one w/ the non-clustered unique constraint. It still does an index scan! So the question remains, how do we reduce the data volume down as early as possible in the execution plan?
Peter
Hi Rob,I am curious to know then impact if instead of two indexes on SitePath_t we have just one ......CREATE INDEX ix_sp on [dbo].[SitePath_T] (Path_v,SiteID_i);Considering Path_v might have more filtering, so we it will first filter and then have a merge join. Please correct me if i am wrong. In my opinion if we can save traditional look up by using composite index, then we should do that.
Nitin Midha
One is sorted by path_v, and the other by siteid_i. If the optimizer starts by filtering the user and then getting the sorted list of sites, then the index which starts on siteid will be more useful. They are both potentially useful, but could be useful in different situations (ie, different sets of statistics)
Rob Farley
A: 

The self join on SitePath_T to find parents is killing you. Perhaps you should add a column for ParentSiteID_i and use a normal recursive CTE?

Then it becomes:

WITH Recurse_CTE AS (
  SELECT 
    us.SiteID_i
  , us.ParentSiteID_i
  , 0 AS RecurseDepth_i
  FROM dbo.SitePath_T us
  JOIN dbo.UserSiteRight_T uusr ON us.SiteID_i = uusr.SiteID_i
  WHERE uusr.UserID_i = 2484
  UNION ALL
  SELECT 
    us.SiteID_i
  , us.ParentSiteID_i
  , rcs.RecurseDepth_i+1 AS RecurseDepth_i
  FROM dbo.SitePath_T us
  JOIN Recurse_CTE rcs ON us.SiteID_i = rcs.ParentSiteID_i
  )
SELECT * FROM Recurse_CTE

Throw an index on SitePath_T (ParentSiteID_i) and performance should be snappy.

Peter
A: 

I would also like to give credit to Rob Farley for understanding the schema/algorithm and providing the indexing schema.

But the problem is not the indexing schema.

It's the Query Optimizer !

More about it in this followup post : http://stackoverflow.com/questions/2040314

Adrian S.