Default Index on Primary Key - sql-server

Does SQL Server build an index on primary keys by default?
If yes what kind of index? If no what kind of index is appropriate for selections by primary key?
I use SQL Server 2008 R2
Thank you.

You can easily determine the first part of this for yourself
create table x
(
id int primary key
)
select * from sys.indexes where object_id = object_id('x')
Gives
object_id name index_id type type_desc is_unique data_space_id ignore_dup_key is_primary_key is_unique_constraint fill_factor is_padded is_disabled is_hypothetical allow_row_locks allow_page_locks
1653580929 PK__x__6383C8BA 1 1 CLUSTERED 1 1 0 1 0 0 0 0 0 1 1
Edit: There is one other case I should have mentioned
create table t2 (id int not null, cx int)
create clustered index ixc on dbo.t2 (cx asc)
alter table dbo.t2 add constraint pk_t2 primary key (id)
select * from sys.indexes where object_id = object_id('t2')
Gives
object_id name index_id type type_desc is_unique data_space_id ignore_dup_key is_primary_key is_unique_constraint fill_factor is_padded is_disabled is_hypothetical allow_row_locks allow_page_locks has_filter filter_definition
----------- ------------------------------ ----------- ---- ------------------------------ --------- ------------- -------------- -------------- -------------------- ----------- --------- ----------- --------------- --------------- ---------------- ---------- ------------------------------
34099162 ixc 1 1 CLUSTERED 0 1 0 0 0 0 0 0 0 1 1 0 NULL
34099162 pk_t2 2 2 NONCLUSTERED 1 1 0 1 0 0 0 0 0 1 1 0 NULL
With regard to the second part there is no golden rule it depends on your individual query workload, and what your PK is.
For satisfying individual lookups by primary key a non clustered index will be fine. If you are doing queries against ranges these would be well served by a matching clustered index but a covering non clustered index could also suffice.
You also need to consider the index width of the clustered index in particular as it impacts all your non clustered indexes and effect of inserts on page splits.
I recommend the book SQL Server 2008 Query Performance Tuning Distilled to read more about the issues.

Yes. By default a unique clustered index is created on all primary keys, but you can create a unique nonclustered index instead of you like.
As to what the appropriate choice, I'd say that for 80-90% of the tables you create, you generally want the clustered index to be the primary key, but that's not always the case.
You'd typically make the clustered index something else if you do heavy range scans on that "something else". For example, if you have a synthetic primary key*, but have a date column that you typically query in terms of a range, you'd often want that date column to be the most significant column in your clustered index.
*That's usually done by using an INT IDENTITY column as the PK on the table.

Yes, it builds a clustered index on the primary key by default.

To be direct, SQL does create an index on the PRIMARY KEY (PK) keyword. That index is a Unique, Clustered Index.
sqlvogel brings up an important point in his commoent above. You can only have one "CLUSTERED" index. If you already have one prior to declaring a PK then your key will be NONCLUSTERED. This is a little more detail than the default answer to this question. It should also be noted that PK's can not have NULL values.
Note, however that that index can vary depending on prior constraints or index on the table. Additionally, you can declare the details of this index upon creation depending on how you write out the code:
< table_constraint > ::= [ CONSTRAINT constraint_name ]
{ [ { PRIMARY KEY | UNIQUE }
[ CLUSTERED | NONCLUSTERED ]
{ ( column [ ASC | DESC ] [ ,...n ] ) }
[ WITH FILLFACTOR = fillfactor ]
[ ON { filegroup | DEFAULT } ]
]
Example:
CREATE TABLE MyTable
(
Id INT NOT NULL,
ForeignKeyId INT REFERENCES OtherTable(Id) NOT NULL,
Name VARCHAR(50) NOT NULL,
Comments VARCHAR(500) NULL,
PRIMARY KEY NONCLUSTERED (Id, ForeignKeyId)
)

No it doesn't in the case where there is already an index defined on the table

I just come across the same confusion ,Just created a following script to test :
create database mytest
go
use mytest
go
create table x
(
id int primary key
)
go
create table y
(
id int
)
go
select * from sys.indexes where object_id = object_id(N'x') or object_id=object_Id(N'y')
go
First Table x having primary key get's the clustered index ,while table y did't get any
indexes ,as there is no primary key .
Confirmed following point about clustered Indexes :
When we create table with Primary Key Clustered Index will be created
by Default
When we create table without Primary key clustered index will not be
created .
There will be single clustered Index ,as it depends on the Index key
Value . Shorting of the data rows in the table can only be in the one
order based on Index key value .

Related

TSQL Reference Table with redundant keys

I'm currently working on a stored procedure on SQL Server 2016. In my Database I have a table structure and need to add another table, which references to the same table as an existing one.
Thus, I have 2 times a 1:1 relation to the same table.
The occuring problem is, I reference the same keys from 2 different origin tables twice in the same target table.
Target table:
FK_Tables | Text
----------------
1 | Table One Text Id: 1
1 | Table Two Text Id: 1 // The error: Same FK_Tables 2 times
Table One:
ID | OtherField
---------
1 | 42
Table Two:
ID | CoolField
---------
1 | 22
Table One and Table Two are currently referencing to the table Reference Table.
Do you know how I can solve this problem, of the same ID twice?
Thanks!!
You need to add a column for each table you're referencing, otherwise you wouldn't know where the ID is coming from if they were all inserted into the same field. Something like this:
/*
CREATE TEST TABLES
*/
DROP TABLE IF EXISTS tbOne;
CREATE TABLE tbOne ( ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
, TXT VARCHAR(10)
);
DROP TABLE IF EXISTS tbTwo;
CREATE TABLE tbTwo ( ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
, TXT VARCHAR(10)
);
DROP TABLE IF EXISTS Target;
CREATE TABLE Target ( ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
, FKTB1 INT
, FKTB2 INT
, TXT VARCHAR(100)
);
-- 1st FK tbOne
ALTER TABLE Target ADD CONSTRAINT FK_One FOREIGN KEY (FKTB1) REFERENCES tbOne (ID);
--2nd FK tbTwo
ALTER TABLE Target ADD CONSTRAINT FK_Two FOREIGN KEY (FKTB2) REFERENCES tbTwo (ID);
-- Populate test tables
INSERT INTO tbOne (TXT)
SELECT TOP 100 LEFT(text, 10)
FROM SYS.messages
INSERT INTO tbTwo (TXT)
SELECT TOP 100 LEFT(text, 10)
FROM SYS.messages
INSERT INTO [Target] (FKTB1, FKTB2, TXT)
SELECT 1, 1, 'Test - constraint'
-- Check result set
SELECT *
FROM tbTwo
SELECT *
FROM tbOne
SELECT *
FROM [Target] T
INNER JOIN tbOne TB1
ON T.FKTB1 = TB1.ID
INNER JOIN tbTwo TB2
ON T.FKTB2 = TB2.ID

Need to tune the select query without adding index

I have a query related to Indexes and Execution plan. Below are few tables that I am using
Table 1 : TestReviewResult
Column_name |Type |Length
TestReviewResultId |int |4
TestNumber |int |4
ReviewerUserId |int |4
Current Index
index_name index_description index_keys
TestReviewResult_PK clustered, unique, primary key located on PRIMARY TestReviewResultId
Table 2: TestReviewFinding
Column_name Type Length
TestReviewFindingId int 4
TestReviewResultId int 4
ScreenCode varchar 100
ReviewComments varchar 8000
CurrentIndex
index_name index_description index_keys
TestReviewFinding_PK clustered, unique, primary key located on PRIMARY TestReviewFindingId
Table3: TestReviewResultComment
Column_name Type Length
TestReviewResultCommentId Int 4
TestReviewResultId Int 4
TestReviewComment varchar 8000
CurrentIndex
index_name index_description index_keys
TestReviewResultComment_CI clustered located on PRIMARY TestReviewResultId
TestReviewResultComment_PK nonclustered, unique, primary key located on PRIMARY TestReviewResultCommentId
Table 4: TestReviewFindingElement
Column_name Type Length
TestReviewFindingElementId Int 4
TestReviewFindingId Int 4
ElementCode varchar 25
CurrentIndex
index_name index_description index_keys
TestReviewFindingElementType_PK clustered, unique, primary key located on PRIMARY TestReviewFindingElementId
Below is the query that is being used
Select columnnames…. from
From TestReviewResult(NOLOCK) cr
left outer join TestReviewResultComment(NOLOCK) crc
on cr.TestReviewResultId = crc.TestReviewResultId
left outer join TestReviewFinding(NOLOCK) cf
on cf.TestReviewResultId = cr.TestReviewResultId
left outer join TestReviewFindingElement(NOLOCK) crf
on cf.TestReviewFindingId = crf.TestReviewFindingId
where cr.TestReviewResultId = #TestReviewNumber -- Test Review number is
being passed in the stored procedure
When I ran the Execution plan it suggested to add 2 indexes mentioned below
CREATE NONCLUSTERED INDEX IX_TestReviewResultId
ON [TestReviewFinding] (TestReviewResultId)
CREATE NONCLUSTERED INDEX IX_TestReviewFindingId
ON [TestReviewFindingElement] (TestReviewFindingId)
Is there any alternative way I can tune the above query without adding indexes as there are lot of write operations that do get performed
Below is the ddl that you can execute in SQL Server and check the Query execution Plan
Create table TestReviewResult(
TestReviewResultId int NOT NULL PRIMARY KEY ,
TestNumber int ,
ReviewerUserId int )
insert into TestReviewResult values(1,1,1)
insert into TestReviewResult values(2,2,2)
insert into TestReviewResult values(3,3,3)
Create table TestReviewFinding(
TestReviewFindingId int not null primary key,
TestReviewResultId int ,
ScreenCode varchar(100))
insert into TestReviewFinding values(1,1,'A')
insert into TestReviewFinding values(2,2,'B')
insert into TestReviewFinding values(3,3,'C')
Create table TestReviewResultComment(
TestReviewResultCommentId int not null primary key nonclustered,
TestReviewResultId int ,
TestReviewComment varchar( 8000)
)
CREATE CLUSTERED INDEX [CI_TestReviewResultId]
ON TestReviewResultComment (TestReviewResultId);
insert into TestReviewResultComment values(1,1,'A')
insert into TestReviewResultComment values(2,2,'B')
insert into TestReviewResultComment values(3,3,'C')
Create table TestReviewFindingElement(
TestReviewFindingElementId int not null primary key,
TestReviewFindingId int ,
ElementCode varchar(25))
insert into TestReviewFindingElement values(1,1,'A')
insert into TestReviewFindingElement values(2,3,'B')
insert into TestReviewFindingElement values(3,3,'C')
When I m running the below query I am getting Index scans on 2 tables
TestReviewFindingElement and TestReviewFinding
Select *
From TestReviewResult(NOLOCK) cr
left outer join TestReviewResultComment(NOLOCK) crc
on cr.TestReviewResultId = crc.TestReviewResultId
left outer join TestReviewFinding(NOLOCK) cf
on cf.TestReviewResultId = cr.TestReviewResultId
left outer join TestReviewFindingElement(NOLOCK) crf
on cf.TestReviewFindingId = crf.TestReviewFindingId
where cr.TestReviewResultId = #TestReviewNumber -- Test Review number is
being passed in the stored procedure.You can use --> 1
Is there any way to modify the select query to avoid scan without adding index

ORA-00911: invalid character on oracle, but works with H2

I have the following SQL command, it works with H2 database, but when i try to run it on Oracle XE, it gets the " ORA-00911: invalid character " error.
create table EMPLOYEE (
EMPLOYEE_KEY NUMBER(10) not null,
SALARY NUMBER(10,2),
LAST_NAME VARCHAR2(132),
FIRST_NAME VARCHAR2(132),
SUPERVISOR_KEY NUMBER(10),
constraint EMPLOYEE_PK primary key (EMPLOYEE_KEY)
);
create unique index EMPLOYEE_PK on EMPLOYEE(EMPLOYEE_KEY);
With this example, when you created the primary key constraint, you also created an index with the same name as the index you are trying to create, EMPLOYEE_PK.
Tom Kytes states on his ask tom site:
A primary key or unique constraint is not guaranteed to create a new
index, nor is the index they create guaranteed to be a unique index.
Therefore, if you desire a unique index to be created for query
performance issues, you should explicitly create one.
Oddly, enough when I run your DDL, I receive the ORA-00955 error and not the ORA-00911 (invalid character error).
Often times the ORA-00911 error occurs when one is copying from one editor to another and you copy some non-printable characters.
Below, I ran the first DDL statement provided and then I checked the indices created. If you look closely, you will see that EMPLOYEE_PK index was created as a consequence of the primary key constraint which you created.
SCOTT#dev> create table EMPLOYEE (
2 EMPLOYEE_KEY NUMBER(10) not null,
3 SALARY NUMBER(10,2),
4 LAST_NAME VARCHAR2(132),
5 FIRST_NAME VARCHAR2(132),
6 SUPERVISOR_KEY NUMBER(10),
7 constraint EMPLOYEE_PK primary key (EMPLOYEE_KEY)
8 );
Table created.
SCOTT#dev> SELECT ind.index_name,
2 ind.index_type,
3 ind.table_owner,
4 ind.table_name
5 FROM all_indexes ind
6 JOIN all_ind_columns icol
7 ON ind.owner = icol.index_owner
8 AND ind.table_name = icol.table_name
9 AND ind.index_name = icol.index_name
10 WHERE 1 = 1
11 AND ind.table_name = 'EMPLOYEE'
12 /
INDEX_NAME INDEX_TYPE TABLE_OWNER TABLE_NAME
============================== =========================== ============================== ==============================
EMPLOYEE_PK NORMAL SCOTT EMPLOYEE
If you desire to create a unique index on EMPLOYEE_KEY separately, a number of approaches could be taken. Here is one:
--create the table
SCOTT#dev> create table EMPLOYEE (
2 EMPLOYEE_KEY NUMBER(10) not null,
3 SALARY NUMBER(10,2),
4 LAST_NAME VARCHAR2(132),
5 FIRST_NAME VARCHAR2(132),
6 SUPERVISOR_KEY NUMBER(10)
7 );
Table created.
--create the unique index
SCOTT#dev> CREATE UNIQUE INDEX EMPLOYEE_PK ON EMPLOYEE (EMPLOYEE_KEY);
Index created.
--add the primary key
SCOTT#dev> alter table EMPLOYEE add
2 constraint EMPLOYEE_PK primary key (EMPLOYEE_KEY)
3 /
Table altered.
SCOTT#dev> SELECT ind.index_name,
2 ind.index_type,
3 ind.table_owner,
4 ind.table_name
5 FROM all_indexes ind
6 JOIN all_ind_columns icol
7 ON ind.owner = icol.index_owner
8 AND ind.table_name = icol.table_name
9 AND ind.index_name = icol.index_name
10 WHERE 1 = 1
11 AND ind.table_name = 'EMPLOYEE'
12 /
INDEX_NAME INDEX_TYPE TABLE_OWNER TABLE_NAME
============================== =========================== ============================== ==============================
EMPLOYEE_PK NORMAL SCOTT EMPLOYEE
SCOTT#dev>
SCOTT#dev> SELECT cons.constraint_name
2 FROM all_constraints cons
3 JOIN all_cons_columns conc
4 ON conc.table_name = 'EMPLOYEE'
5 AND cons.owner = conc.owner
6 AND cons.table_name = conc.table_name
7 WHERE 1 = 1
8 AND cons.constraint_name = conc.constraint_name
9 /
CONSTRAINT_NAME
=============================
EMPLOYEE_PK
I won' insist on the redundancy between a primary key and an unique index, but, to answer only to your question as it is titled:
when i try to run it on Oracle XE, it gets the " ORA-00911: invalid character " error.
It chops on ; as, as far as I know, you can only issue one SQL command at a time through the "SQL Workshop > SQL Command" page of Oracle application Express (you can send a PL/SQL bloc too)
However you can select your commands one by one and hit run. When there is a selection, only the sectioned part is executed. For example, in the following screen capture, only the first DDL statement will be executed by clicking on the "run" button:

get max from table where sum required

Suppose I have a table with following data:
gameId difficultyLevel numberOfQuestions
--------------------------------------------
1 1 2
1 2 2
1 3 1
In this example the game is configured for 5 questions, but I'm looking for a SQL statement that will work for n number of questions.
What I need is a SQL statement that given a question, displayOrder will return the current difficulty level of question. For example - given a displayOrder of 3, with the table data above, will return 2.
Can anyone advise how the query should look like?
I'd recommend a game table with a 1:m relationship with a question table.
You shouldn't repeat columns in a table - it violates first normal form.
Something like this:
create table if not exists game
(
game_id bigint not null auto_increment,
name varchar(64),
description varchar(64),
primary key (game_id)
);
create table if not exists question
(
question_id bigint not null auto_increment,
text varchar(64),
difficulty int default 1,
game_id bigint,
primary key (question_id) ,
foreign key game_id references game(game_id)
);
select
game.game_id, name, description, question_id, text, difficulty
game left join question
on game.game_id = question.game_id
order by question_id;
things might be easier for you if you change your design as duffymo suggests, but if you must do it that way, here's a query that should do the trick.
SELECT MIN(difficultyLevel) as difficltyLevel
FROM
(
SELECT difficltyLevel, (SELECT sum(numberOfQuestions) FROM yourtable sub WHERE sub.difficultyLevel <= yt.difficultyLevel ) AS questionTotal
FROM yourTable yt
) AS innerSQL
WHERE innerSQL.questionTotal >= #displayOrder

Optimize database schema / indexes for faster query result when using LIKE and EXISTS clauses

While implementing a tree structure over a SQL 2005 server database, the query response is taking too long ( below queries are talking more than 5 sec ) when using LIKE clause combined with the EXISTS clause.
The slow queries involve two tables - [SitePath_T] and [UserSiteRight_T] :
CREATE TABLE [dbo].[UserSiteRight_T](
[UserID_i] [int] NOT NULL
, [SiteID_i] [int] NOT NULL
, CONSTRAINT [PKC_UserSiteRight_UserIDSiteID] PRIMARY KEY CLUSTERED ( [UserID_i] ASC, [SiteID_i] ASC )
, CONSTRAINT [FK_UserSiteRight_UserID] FOREIGN KEY( [UserID_i] ) REFERENCES [dbo].[User_T] ( [ID_i] )
, CONSTRAINT [FK_UserSiteRight_SiteID] FOREIGN KEY( [SiteID_i] ) REFERENCES [dbo].[Site_T] ( [ID_i] )
)
Number of rows ( rights ) for UserID_i = 2484 in [UserSiteRight_T] table is quite small : 545
( UserID_i = 2484 was randomly chosen )
Also, the database is relatively small - only 23000 rows in the [SitePath_T] table :
CREATE TABLE [dbo].[SitePath_T] (
[SiteID_i] INT NOT NULL,
[Path_v] VARCHAR(255) NOT NULL,
CONSTRAINT [PK_SitePath_PathSiteID] PRIMARY KEY CLUSTERED ( [Path_v] ASC, [SiteID_i] ASC ),
CONSTRAINT [AK_SitePath_Path] UNIQUE NONCLUSTERED ( [Path_v] ASC ),
CONSTRAINT [FK_SitePath_SiteID] FOREIGN KEY( [SiteID_i] ) REFERENCES [Site_T] ( [ID_i] )
)
I am trying to get only the SiteIDs which have subsites accessible by a certain UserID ( given by the [UserSiteRight_T] table ) as :
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+'%' )
Below you can find a part of the result where only column sp.SiteID_i is needed/returned - also I added the related corresponding Path_v, UserSiteRight_T.SiteID_i WHERE UserID = 2484 and the corresponding SitePath_T SiteID_i and Path_v matching the LIKE condition :
sp.SiteID_i sp.Path_v [UserSiteRight_T].SiteID_i usp.SiteID_i usp.Path_v
1 '1.' NULL 10054 '1.10054.'
10054 '1.10054.' 10054 10054 '1.10054.'
10275 '1.10275.' 10275 10275 '1.10275.'
1533 '1.1533.' NULL 2697 '1.1533.2689.2693.2697.'
2689 '1.1533.2689.' NULL 2697 '1.1533.2689.2693.2697.'
2693 '1.1533.2689.2693.' NULL 2697 '1.1533.2689.2693.2697.'
2697 '1.1533.2689.2693.2697.' 2697 2697 '1.1533.2689.2693.2697.'
1580 '1.1580.' NULL 1581 '1.1580.1581.'
1581 '1.1580.1581.' 1581 1581 '1.1580.1581.'
1585 '1.1580.1581.1585.' 1585 1585 '1.1580.1581.1585.'
222 '1.222.' 222 222 '1.222.'
223 '1.222.223.' 223 223 '1.222.223.'
224 '1.222.223.224.' 224 224 '1.222.223.224.'
3103 '1.3103.' NULL 3537 '1.3103.3529.3533.3537.'
3529 '1.3103.3529.' NULL 3537 '1.3103.3529.3533.3537.'
3533 '1.3103.3529.3533.' NULL 3537 '1.3103.3529.3533.3537.'
3537 '1.3103.3529.3533.3537.' 3537 3537 '1.3103.3529.3533.3537.'
Execution plan for the above query :
|--Nested Loops(Left Semi Join, WHERE:([MyTestDB].[dbo].[SitePath_T].[Path_v] as [usp].[Path_v] like [Expr1007]))
|--Compute Scalar(DEFINE:([Expr1007]=[MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%', [Expr1008]=LikeRangeStart([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1009]=LikeRangeEnd([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1010]=LikeRangeInfo([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%')))
| |--Index Scan(OBJECT:([MyTestDB].[dbo].[SitePath_T].[AK_SitePath_Path] AS [sp]))
|--Table Spool
|--Hash Match(Inner Join, HASH:([uusr].[SiteID_i])=([usp].[SiteID_i]))
|--Clustered Index Seek(OBJECT:([MyTestDB].[dbo].[UserSiteRight_T].[PKC_UserSiteRight_UserIDSiteID] AS [uusr]), SEEK:([uusr].[UserID_i]=(2484)) ORDERED FORWARD)
|--Index Scan(OBJECT:([MyTestDB].[dbo].[SitePath_T].[AK_SitePath_Path] AS [usp]))
And the rewritten query :
SELECT DISTINCT
sp.SiteID_i
FROM [dbo].[SitePath_t] sp
, [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+'%' )
ORDER BY SiteID_i ASC
Execution plan :
|--Hash Match(Aggregate, HASH:([sp].[SiteID_i]))
|--Nested Loops(Inner Join, WHERE:([MyTestDB].[dbo].[SitePath_T].[Path_v] as [usp].[Path_v] like [Expr1006]))
|--Hash Match(Inner Join, HASH:([uusr].[SiteID_i])=([usp].[SiteID_i]))
| |--Clustered Index Seek(OBJECT:([MyTestDB].[dbo].[UserSiteRight_T].[PKC_UserSiteRight_UserIDSiteID] AS [uusr]), SEEK:([uusr].[UserID_i]=(2484)) ORDERED FORWARD)
| |--Index Scan(OBJECT:([MyTestDB].[dbo].[SitePath_T].[AK_SitePath_Path] AS [usp]))
|--Table Spool
|--Compute Scalar(DEFINE:([Expr1006]=[MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%', [Expr1007]=LikeRangeStart([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1008]=LikeRangeEnd([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1009]=LikeRangeInfo([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%')))
|--Index Scan(OBJECT:([MyTestDB].[dbo].[SitePath_T].[AK_SitePath_Path] AS [sp]))
All the indexes are in place - Database Engine Tuning Advisor is not suggesting new schema modification - but both queries are returning the correct result in more that 5 seconds - and, as it's a response of an Ajax reques - feels ( and is ) very slow when updating the navigation tree
Any suggestions to optimize / modify database schema / indexes / queries in order to get a faster response ?
Thank you
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.
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.
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.

Resources