INDEXING BASCIS - Unknown
INDEXING BASCIS - Unknown
INDEXING BASCIS
Pages, extents:
The fundamental unit of data storage in sql server is a page (8 KB).
This means SQL server database have 128 pages per MB.
Heap:
The heap is a table without clustered index.
Data pages are not linked in a linked list (i.e; data pages are just marked for the
fact that they belong to particular table).
Clustered index:
The data rows are stored in order based on clustered index key.
Page 2 of 59
Data pages in the leaf level or leaf node are linked in a doubly-linked list.
Clustered index has one row in sys.partitions with index_id = 1 and if a values of 0
means have a heap and if greater than 1 means they do have a non-clustered
indexes.
Root node is nothing but that’s the entry to your table this is got enough
information to take you to actual row in the leaf.
Intermediate nodes can be multiple. Here in diagram it is showing only one level
of Intermediate node. Intermediate node will take you to the leaf node in
clustered index and this is the place where data also stored.
They are doubly linked list so you do know what is the next and previous page.
Non-Clustered index:
They have a B-tree index structure similar to the one in clustered indexes.
The difference is that they will not affect the order of the data rows.
Page 3 of 59
Each index row contains the non-clustered key value, a row locator and any
included, or nonkey, columns.
Building B-Tree
Assumption: Each page contains 10 rows.
The above will be root or intermediate node for below leaf nods which contains
leaf node information.
If we want to search for row no 3 then sql server goes to B-tree and identifies root
node and it is not searching for a value of 3 .To get a value of 3 sql server have to
travels through multiple other intermediate nodes. since the value is 3 it will
Page 5 of 59
come to 1-30 in intermediate and no need to check for >31 and comes to 1-10 in
leaf node.
Understand non-clustered index we were storing only key in leaf node where as in
clustered index stores data rows in leaf node.
Fill Factor
It is a method to pre-allocate some space for future expansion for a particular
row. If we are using varchar data types there is a probability that the length may
change which means row length which is smaller now can become bigger in
future.to accommodate this fill factor will helps you.
Demo
USE tempdb
GO
CREATETABLE INDEXING
(ID INTIDENTITY(1,1),
NAME CHAR(4000),
COMPANY CHAR(4000),
PAY INT)
/* THIS TABLE HAVE 4 COLUMNS OF WHICH 2 CHAR COLUMNS OF 4000 THIS MEANS I
HAVE A ROW OF CLOSE TO 8000 BYTES PLEASE MAKE SURE
THAT EACH ROW GETS A SINGLE PAGE AND A PAGE CANNOT STORE MORE THAN ONE ROW
*/
SELECTOBJECT_NAME(OBJECT_ID) TABLENAME,
ISNULL(name,OBJECT_NAME(OBJECT_ID)) INDEXNAME,
index_id,
type_desc
FROMSYS.indexes
WHEREOBJECT_NAME(OBJECT_ID)='INDEXING'
SETNOCOUNTON-- USED TO SUPRESS THE N ROWS EFFECTED MESSAGE GET FROM SSMS
--STATUS CHECK
SELECTOBJECT_NAME(OBJECT_ID) NAME,
index_type_desc AS INDEX_TYPE,
alloc_unit_type_desc AS DATA_TYPE,
index_id AS INDEX_ID,
index_depth AS DEPTH,
index_level AS IND_LEVEL,
record_count AS RECORDCOUNT,
page_count ASPAGECOUNT,
fragment_count AS FRAGMENTATION
FROMsys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('INDEXING'),NULL,NULL,'D
ETAILED')
GO
IN_ROW_DATA which means the rows data is been able to fit inside a page hence
it’s inside the row.
INSERTINTO Indexing VALUES
('Steve','Central',15000),('Pinal','SQLAuthority', 13000)
GO
-- Status Check
SELECT
OBJECT_NAME(object_id) Name,
index_type_desc AS INDEX_TYPE,
alloc_unit_type_desc AS DATA_TYPE,
index_id AS INDEX_ID,
index_depth AS DEPTH,
index_level AS IND_Level,
record_count AS RecordCount,
page_count ASPageCount,
fragment_count AS Fragmentation
FROMsys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('Indexing'),NULL,NULL,'D
ETAILED');
GO
As see from above two pic’s record count, page count, fragmentation is changed.
INSERTINTO Indexing VALUES
('Dummy','JunkCompany', 1000)
GO 100
Page 7 of 59
-- Clustered Index
CREATECLUSTEREDINDEX CI_IndexingID ON Indexing(ID)
GO
-- Status Check
SELECT
OBJECT_NAME(object_id) Name,
index_type_desc AS INDEX_TYPE,
alloc_unit_type_desc AS DATA_TYPE,
index_id AS INDEX_ID,
index_depth AS DEPTH,
index_level AS IND_Level,
record_count AS RecordCount,
page_count ASPageCount,
fragment_count AS Fragmentation
FROMsys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('Indexing'),NULL,NULL,'D
ETAILED');
GO
It has 2 depth level at depth level of 0 record count and page count is 103.
Page 8 of 59
To manage these 103 records you have another page which has 103 records but
page count is 1
INSERTINTO Indexing VALUES
('MoreJunk','MoreJunkCompany', 100)
GO 700
-- Status Check
SELECT
OBJECT_NAME(object_id) Name,
index_type_desc AS INDEX_TYPE,
alloc_unit_type_desc AS DATA_TYPE,
index_id AS INDEX_ID,
index_depth AS DEPTH,
index_level AS IND_Level,
record_count AS RecordCount,
page_count ASPageCount,
fragment_count AS Fragmentation
FROMsys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('Indexing'),NULL,NULL,'D
ETAILED');
GO
Now we can see 803 rows which have a depth level of 0 and these 803 rows are
managed by 2 pages in the intermediate node(see row 2 page count) and further
2 page is managed by 1 page in Root node (see row 3 page count).
-- Statistics speaks a lot !!!
DBCCSHOW_STATISTICS ('Indexing', CI_IndexingID)
GO
Page 9 of 59
If we don’t have uniqueness with respect to clusted index sql server will go a head
and create another 4 byte to make it unique internally.
-- Non-Clustered Index
CREATENONCLUSTEREDINDEX NCI_Pay on Indexing(Pay)
GO
-- Status Check
SELECT
OBJECT_NAME(object_id) Name,
index_type_desc AS INDEX_TYPE,
alloc_unit_type_desc AS DATA_TYPE,
index_id AS INDEX_ID,
index_depth AS DEPTH,
index_level AS IND_Level,
record_count AS RecordCount,
page_count ASPageCount,
fragment_count AS Fragmentation
FROMsys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('Indexing'),NULL,NULL,'D
ETAILED');
GO
The Non-clustered index stores only the key and not the data so in 5th row page
count = 1 (root page) have 2 records and page count = 2 have 803 records.
Here we can see average key length or two integers of 4 bytes which is 8 bytes.
In 2nd table the 1st column (pay) has four bytes and when we add second column
(pay, id) has eight bytes.
ID which is the clustered index currently as part of the pointer hence this
conforms the fact that the Non-clustered index goes about using the key or the
non-clustered index but the leaf node has got the pointer to the row data which is
a clustered index key or would be row id.
Additional information
A view with a clustered index is Indexed view.
Performance Trivia: Fill factor value of 50 percent can cause database read
performance to degrade by two times.
i.e; you will have to go ahead and read 2 pages for the same amount of data
which could you have read in one single read.
Trivia facts:
Column keys per index – 16 (we cannot have more than 16 columns as part of
composite index we can use included columns which is covering index)
An index is not the solution for every problem. The system was running fine a few
moments ago and now it is slow index is May or may not solve the problem.
Primary key
It automatically creates clustered Index
It automatically creates unique index on column. If the key specified is not unique
internally sql server uses a non-unique cluster index of 4-bytes that it adds as part
of unique identifier and makes it unique.
NOTE: Non Unique clustered index adds 4-byte unique identifier column.
SETNOCOUNTON
INSERTINTO [Sales].[PKSalesOrderDetail]
([SalesOrderID],[CarrierTrackingNumber],[OrderQty]
,[ProductID],[SpecialOfferID],[UnitPrice]
,[UnitPriceDiscount],[LineTotal],[rowguid],[ModifiedDate])
SELECT [SalesOrderID],[CarrierTrackingNumber],[OrderQty]
,[ProductID],[SpecialOfferID],[UnitPrice]
,[UnitPriceDiscount],[LineTotal],[rowguid],[ModifiedDate]
FROM [Sales].[SalesOrderDetail]
GO
-----------------------------------------------------------------------
-- CREATE PK
-- Clustered Index not specified
ALTERTABLE [Sales].[PKSalesOrderDetail]
ADDCONSTRAINT [PK_PKSalesOrderDetail_SalesOrderDetailID]
PRIMARYKEY ([SalesOrderDetailID] ASC
)
GO
-- Check Database >> Tables >> Indexes - Primary Key automatically creates CI
-- Drop PK
ALTERTABLE [Sales].[PKSalesOrderDetail]
DROPCONSTRAINT [PK_PKSalesOrderDetail_SalesOrderDetailID]
GO
-----------------------------------------------------------------------
Page 12 of 59
-- CREATE PK
-- Non-Clustered Index specified
ALTERTABLE [Sales].[PKSalesOrderDetail]
ADDCONSTRAINT [PK_PKSalesOrderDetail_SalesOrderDetailID]
PRIMARYKEY NONCLUSTERED ([SalesOrderDetailID] ASC
)
GO
-- Check Database >> Tables >> Indexes - Primary Key automatically creates
NCI
-- Drop PK
ALTERTABLE [Sales].[PKSalesOrderDetail]
DROPCONSTRAINT [PK_PKSalesOrderDetail_SalesOrderDetailID]
GO
-----------------------------------------------------------------------
-- Create Clustered Index (no primary key)
CREATECLUSTEREDINDEX
[CL_PKSalesOrderDetail_Index] ON [Sales].[PKSalesOrderDetail]
( [SalesOrderID] ASC,
[CarrierTrackingNumber] ASC
)
GO
-- Check Database >> Tables >> Indexes - Primary Key automatically creates
NCI
-- Clean up
DROPTABLE [Sales].[PKSalesOrderDetail]
GO
Over Indexing
Consumes unnecessary disk space
USE [MyAdventureWorks]
GO
SETNOCOUNTON
-- Measure Time
SETSTATISTICSTIMEON
GO
-- Truncate Table
TRUNCATETABLE [Sales].[NewSalesOrderDetail]
GO
-- Create New Indexes on New Table and Measure the insert time.
-- Create Clustered Index
ALTERTABLE [Sales].[NewSalesOrderDetail]
ADDCONSTRAINT
[PK_NewSalesOrderDetail_SalesOrderID_NewSalesOrderDetailID]
PRIMARYKEYCLUSTERED
([SalesOrderID] ASC,
Page 14 of 59
To insert into same table after creating many unwanted indexes time taken in
12.332 sec which is 10 times more than time taken in heap table.
Duplicate Indexing
Reduce the performance of Insert, update, delete query
Wasteful of space
-- Measure Time
SETSTATISTICSTIMEON
SETSTATISTICSIOON
GO
Took 4 seconds
-- Truncate Table
TRUNCATETABLE [Sales].[DupSalesOrderDetail]
GO
Took 46 seconds
Clustered Index
Page 18 of 59
SETNOCOUNTON
-- Measure Time
SETSTATISTICSTIMEON
SETSTATISTICSIOON
GO
-- Create New Indexes on New Table and Measure the Logical Read
-- Create Clustered Index
ALTERTABLE [Sales].[MySalesOrderDetail]
ADDCONSTRAINT [PK_MySalesOrderDetail_SalesOrderDetailID]
PRIMARYKEYCLUSTERED
([SalesOrderDetailID] ASC
)ON [PRIMARY]
GO
Unique Index
SETNOCOUNTON
)ON [PRIMARY]
GO
The unique index which is created explicitly can be edited now we can uncheck
unique to remove uniqueness.
Included Columns
SETNOCOUNTON
This is covering index.sql server add these two columns in leaf node while
keeping the salesorderid as part of the key.
-- Select statement (CTRL+M)
SELECT [SalesOrderID],[ProductID],[col1]
FROM [Sales].[InclSalesOrderDetail] sod
WHERE sod.[SalesOrderID] > 40000
AND sod.[SalesOrderID] < 50000
GO
Filtered Indexes
USE [MyAdventureWorks]
GO
SETNOCOUNTON
,[UnitPriceDiscount],[LineTotal],[rowguid],[ModifiedDate]
FROM [Sales].[SalesOrderDetail]
GO
After using the filtered index the plan is same but it uses the filtered index
where as preiously is uses non-clustered
inex([IX_FilteredSalesOrderDetail_SpecialOfferID]) and the subtree cost also
now it is reduced which is shown in below.
WITH(INDEX([FIX_FilteredSalesOrderDetail_SpecialOfferID]))
WHERE [SpecialOfferID] = 2
GO
------------------------------------------------------------------------
-- Clean up
DROPTABLE [Sales].[FilteredSalesOrderDetail]
GO
Disabled Index
USE [MyAdventureWorks]
GO
SETNOCOUNTON
,[UnitPriceDiscount],[LineTotal],[rowguid],[ModifiedDate])
SELECT [SalesOrderID],[CarrierTrackingNumber],[OrderQty]
,[ProductID],[SpecialOfferID],[UnitPrice]
,[UnitPriceDiscount],[LineTotal],[rowguid],[ModifiedDate]
FROM [Sales].[SalesOrderDetail]
GO
-- Create New Indexes on New Table and Measure the insert time.
-- Create Clustered Index
ALTERTABLE [Sales].[DeleteSalesOrderDetail]
ADDCONSTRAINT
[PK_DeleteSalesOrderDetail_SalesOrderID_DeleteSalesOrderDetailID]
PRIMARYKEYCLUSTERED
([SalesOrderID] ASC,
[SalesOrderDetailID] ASC)ON [PRIMARY]
GO
-- Create Non-Clustered Index
CREATENONCLUSTEREDINDEX [IX_DeleteSalesOrderDetail_CarrierTrackingNumber]
ON [Sales].[DeleteSalesOrderDetail]
([CarrierTrackingNumber] ASC)ON [PRIMARY]
GO
------------------------------------------------------------------------
-- Select Data (CTRL + M)
SELECT [CarrierTrackingNumber]
FROM [Sales].[DeleteSalesOrderDetail]
WHERE [CarrierTrackingNumber] ISNOTNULL
GO
GO
------------------------------------------------------------------------
-- Select Data (CTRL + M)
SELECT [CarrierTrackingNumber]
FROM [Sales].[DeleteSalesOrderDetail]
WHERE [CarrierTrackingNumber] ISNOTNULL
GO
Page 31 of 59
After insert again executing the select query remains no change in plan.
-- Disable clustered index
ALTERINDEX [PK_DeleteSalesOrderDetail_SalesOrderID_DeleteSalesOrderDetailID]
ON [Sales].[DeleteSalesOrderDetail] DISABLE
GO
This concludes that disabling the non-clustered index will not effect the insert
or select but disabling the clustered index will effect the insert and select
query.
Page 32 of 59
Summary
Index Performance
Index Defragmentation
DEMO
USE [MyAdventureWorks]
GO
-----------------------------------------------------------------------------
-----
Page 33 of 59
CREATENONCLUSTEREDINDEX [IX_NewSalesOrderDetail_CarrierTrackingNumber]
ON [Sales].[MainSalesOrderDetail]
([CarrierTrackingNumber] ASC)ON [PRIMARY]
GO
-- Create Non-Clustered Index
CREATENONCLUSTEREDINDEX [IX_NewSalesOrderDetail_CarrierTrackingNumber1]
ON [Sales].[MainSalesOrderDetail]
([CarrierTrackingNumber] ASC)ON [PRIMARY]
GO
-----------------------------------------------------------------------------
-----
-- CTRL + M
-- Run following query
SELECT [SalesOrderID],[CarrierTrackingNumber],[OrderQty]
FROM [Sales].[MainSalesOrderDetail]
WHERE [ProductID] = 717
GO
To know what is miising right click as shown above pointer missing index
detail which gives script of missing index.
Page 35 of 59
, dm_ius.user_lookups AS UserLookups
, dm_ius.user_updates AS UserUpdates
, p.TableRows
,'DROP INDEX '+QUOTENAME(i.name)
+' ON '+QUOTENAME(s.name)+'.'+QUOTENAME(OBJECT_NAME(dm_ius.object_id))as'drop
statement'
FROMsys.dm_db_index_usage_stats dm_ius
INNERJOINsys.indexes i ON i.index_id = dm_ius.index_id AND dm_ius.object_id=
i.object_id
INNERJOINsys.objects o on dm_ius.object_id= o.object_id
INNERJOINsys.schemas s on o.schema_id= s.schema_id
INNERJOIN(SELECTSUM(p.rows) TableRows, p.index_id, p.object_id
FROMsys.partitions p GROUPBY p.index_id, p.object_id)
p
ON p.index_id = dm_ius.index_id AND dm_ius.object_id= p.object_id
WHEREOBJECTPROPERTY(dm_ius.object_id,'IsUserTable')= 1
AND dm_ius.database_id =DB_ID()
AND i.type_desc ='nonclustered'
AND i.is_primary_key = 0
AND i.is_unique_constraint = 0
ORDERBY (dm_ius.user_seeks + dm_ius.user_scans + dm_ius.user_lookups)ASC
GO
-- Running Unused Index will not bring Unused index again in query
-----------------------------------------------------------------------------
-----
-- Clean up
DROPTABLE [Sales].[MainSalesOrderDetail]
GO
Index Defragmentation
Page 37 of 59
DEMO
-- Create New Table Empty Table
SELECT*
INTO [Sales].[DefragSalesOrderDetail]
FROM [Sales].[SalesOrderDetail]
WHERE 1 = 2
GO
-----------------------------------------------------------------------------
-----
-- Create Clustered Index
CREATECLUSTEREDINDEX [IX_DefragSalesOrderDetail_SalesOrderDetailID]
ON [Sales].[DefragSalesOrderDetail]
([SalesOrderDetailID] ASC)ON [PRIMARY]
GO
-- Create Non-Clustered Index
CREATENONCLUSTEREDINDEX [IX_DefragSalesOrderDetail_OrderQty]
ON [Sales].[DefragSalesOrderDetail]
([OrderQty] ASC)ON [PRIMARY]
GO
-- Create Non-Clustered Index
CREATENONCLUSTEREDINDEX [IX_DefragSalesOrderDetail_ProductID]
ON [Sales].[DefragSalesOrderDetail]
([ProductID] ASC)ON [PRIMARY]
GO
-----------------------------------------------------------------------------
-----
-- Identify Index Defragmentation
SELECT ps.database_id,OBJECT_NAME( ps.OBJECT_ID),
ps.index_id, b.name,
ps.avg_fragmentation_in_percent
FROMsys.dm_db_index_physical_stats(DB_ID(),NULL,NULL,NULL,NULL)AS ps
INNERJOINsys.indexesAS b ON ps.OBJECT_ID= b.OBJECT_ID
AND ps.index_id = b.index_id
WHERE ps.database_id =DB_ID()
AND b.OBJECT_ID=OBJECT_ID('Sales.DefragSalesOrderDetail')
ORDERBY ps.OBJECT_ID
GO
Page 38 of 59
-----------------------------------------------------------------------------
-----
-- Rebuild and Reorganize Script
-----------------------------------------------------------------------------
-----
-- Reorganization
ALTERINDEX IX_DefragSalesOrderDetail_OrderQty
ON [Sales].[DefragSalesOrderDetail] REORGANIZE
GO
-- Rebuild
ALTERINDEX IX_DefragSalesOrderDetail_ProductID
ON [Sales].[DefragSalesOrderDetail] REBUILD
GO
-- Rebuild
ALTERINDEXALL
ON [Sales].[DefragSalesOrderDetail] REBUILD
GO
Columnstore Index
Accelerate common data warehouse queries
DEMO
SETNOCOUNTON
-- Performance Test
-- Comparing Regular Index with ColumnStore Index
SETSTATISTICSIOON
GO
-- Cleanup
DROPTABLE [Sales].[ColSalesOrderDetail]
Page 41 of 59
USE tempdb
GO
CREATETABLE my_Big_table
( a1 VARCHAR(2000),
b2 VARCHAR(8000),
d4 TEXT);
We know that sql have 8KB page limit. Now a1 , b2 going to have 10,000
characters this is bigger than the normal page size of 8KB
-- Shows all 3 types of pages available inside SQL Server
SELECTOBJECT_NAME(object_id)AS TABLE_NAME,rows,
type_desc AS PAGE_TYPE
FROMsys.partitions p
INNERJOINsys.allocation_units a
ON p.partition_id = a.container_id
WHEREobject_id=object_id('my_big_table');
Page 42 of 59
Sql have 3 different pages to store the data.Sql server used 3 different pages
though the number of rows is 1 you the one in ROW_DATA and 1 in
LOB_DATA (here it is text datatype) and 1 in ROW_OVERFLOW_DATA (this is
a data which could in get accombidated in a single page for a single row and
hence it currently using the overflow page)
CREATETABLE calc_table
(
id INT,
DOB DATETIME,
Calc ASDATENAME(YYYY, DOB)
)
GO
CREATEINDEX ind1_calc_table
ON calc_table(Calc);
GO
o/p : 0
SELECTCOLUMNPROPERTY(OBJECT_ID('calc_table'),'Calc','IsIndexable');
o/p : 0
SELECTCOLUMNPROPERTY(OBJECT_ID('calc_table'),'id','IsIndexable');
SELECTCOLUMNPROPERTY(OBJECT_ID('calc_table'),'Calc','IsPrecise');
o/p : 1
Page 43 of 59
CREATETABLE set_table
(
ID INT,
Calc AS 2*ID
)
GO
SETQUOTED_IDENTIFIEROFF;
SETANSI_NULLsOFF;
GO
CREATEINDEX ind1
ON set_table(calc);
GO
SETQUOTED_IDENTIFIERON;
SETANSI_NULLsON;
GO
CREATEINDEX ind1
ON set_table(calc);
GO
CREATETABLE Clustered_Order(
id INTIDENTITY(1,1)PRIMARYKEY,
name VARCHAR (15))
INSERT Clustered_Order
VALUES ('Vinod'),('Kumar'),('Pinal'),('Dave')
GO
Select*from Clustered_Order
Page 44 of 59
Some times select statement does not gives the order as shown in above
for id column then we have to use order by clause.
SETNOCOUNTON
DECLARE @i int= 0
WHILE (@i < 5000)
BEGIN
INSERTINTO tbl_compress VALUES (@i,REPLICATE('a', 500))
SET @i = @i + 1
END
GO
-- Status Check
SELECT max_record_size_in_bytes, page_count, avg_page_space_used_in_percent,
index_level, index_id, index_depth
FROMsys.dm_db_index_physical_stats(db_id('MyAdventureWorks'),object_id('dbo.t
bl_compress'),null,null,'DETAILED')
WHEREobject_name(object_id)like'tbl_compress'
The index_leaf node(which is 0 in 4th column ) have all the data have
37.5151% of fill factor which is close to 30 but not 30.
Page 45 of 59
-- Status Check
SELECT max_record_size_in_bytes, page_count, avg_page_space_used_in_percent,
index_level, index_id, index_depth
FROMsys.dm_db_index_physical_stats(db_id('MyAdventureWorks'),
object_id('dbo.tbl_compress'),null,null,'DETAILED')
WHEREobject_name(object_id)like'tbl_compress'
Now it comes to 31.66% after doing compression.It has taken the 34.5% for
intermediate node too.
-- Start PAGE Compression now
ALTERINDEX CI_Compress ON tbl_compress REBUILD
WITH (data_compression= PAGE)
-- Status Check
SELECT max_record_size_in_bytes, page_count,
avg_page_space_used_in_percent,
index_level, index_id, index_depth
FROMsys.dm_db_index_physical_stats(db_id('MyAdventureWorks'),object_id('dbo.t
bl_compress'),null,null,'DETAILED')
WHEREobject_name(object_id)like'tbl_compress'
Instead of 3 level node now we have only 2 level node and compression
properly taken the 30% value.
Therefore, row and page compression honours the fill factor that we defined.
ON [Sales].[SalesOrderDetail]
([SalesOrderDetailID] ASC)
GO
CREATENONCLUSTEREDINDEX [NCI_TrackingNumber]
ON [Sales].[SalesOrderDetail]
([CarrierTrackingNumber] ASC)
GO
CREATENONCLUSTEREDINDEX [NCI_ModDate]
ON [Sales].[SalesOrderDetail]
([ModifiedDate] ASC)
GO
Select*from Sales.SalesOrderDetail
-- CTRL + M
Select CarrierTrackingNumber, [SalesOrderDetailID] from
Sales.SalesOrderDetail
Where CarrierTrackingNumber likeN'00EC-47DF-BB'
GO
Select CarrierTrackingNumber, [SalesOrderDetailID] from
Sales.SalesOrderDetail
WhereCONVERT(VARCHAR(50),CarrierTrackingNumber)likeN'00EC-47DF-BB'
GO
-- CTRL + M
Select ModifiedDate, [SalesOrderDetailID] from Sales.SalesOrderDetail
Where ModifiedDate ='2007-11-11'
GO
Select ModifiedDate, [SalesOrderDetailID] from Sales.SalesOrderDetail
WhereCONVERT(DATE,ModifiedDate)='2007-11-11'
GO
SELECT obj.name,
ind.name,
ISNULL(INDEXPROPERTY(OBJECT_ID(obj.name),ind.name,'IndexFillFactor'),0)
[fill_factor],
create_date, modify_date, ind.type_desc,
fill_factor, has_filter,
ISNULL(INDEXPROPERTY(OBJECT_ID(obj.name),ind.name,'IndexDepth'),0)
[IndexDepth],
ISNULL(INDEXPROPERTY(OBJECT_ID(obj.name),ind.name,'IsAutoStatistics'),0
) [IsAutoStatistics],
ISNULL(INDEXPROPERTY(OBJECT_ID(obj.name),ind.name,'IsStatistics'),0)
[IsStatistics],
ISNULL(INDEXPROPERTY(OBJECT_ID(obj.name),ind.name,'IsUnique'),0)
[IsUnique],
ISNULL(INDEXPROPERTY(OBJECT_ID(obj.name),ind.name,'IsClustered'),0)
[IsClustered]
FROMsys.objects obj
INNERJOINsys.indexes ind
ON obj.object_id= ind.object_id
WHERE obj.type='U'
USE tempdb
GO
CREATETABLE ScanSeek(ID INT,ID1 INT,ID2 VARCHAR(8))
GO
-- Inserting Data
INSERTINTO ScanSeek(ID,ID1,ID2)
SELECT 1, 1,'One'
UNIONALL
SELECT 2, 2,'Two'
UNIONALL
SELECT 3, 3,'Three'
UNIONALL
SELECT 4, 4,'Four'
GO
-- Selecting data - Table Scan - CTRL + M
SELECT*
FROM ScanSeek
WHERE ID1 = 1
GO
USE tempdb
GO
IFEXISTS(SELECT*FROMsys.objects
WHEREobject_id=OBJECT_ID(N'[dbo].[OrderTable]')ANDtypein(N'U'))
DROPTABLE [dbo].[OrderTable]
GO
CREATETABLE OrderTable(ID INT,
ID1 VARCHAR(4),
ID2 VARCHAR(4),
FirstName VARCHAR(100),
LastName VARCHAR(100),
City VARCHAR(100))
GO
-- Inserting Data
INSERTINTO OrderTable(ID, ID1, ID2, FirstName,LastName,City)
SELECTTOP 100000 ROW_NUMBER()OVER (ORDERBY a.name) RowID,
LEFT(NEWID(),4),LEFT(NEWID(),3),
'Bob',
CASEWHENROW_NUMBER()OVER (ORDERBY a.name)%2 = 1
THEN'Smith'
ELSE'Brown'END,
CASEWHENROW_NUMBER()OVER (ORDERBY a.name)%10 =
1 THEN'New York'
WHENROW_NUMBER()OVER (ORDERBY a.name)%10
= 5 THEN'San Marino'
WHENROW_NUMBER()OVER (ORDERBY a.name)%10
= 3 THEN'Los Angeles'
WHENROW_NUMBER()OVER (ORDERBY a.name)%427
= 1 THEN'Salt Lake City'
ELSE'Houston'END
FROMsys.all_objects a
CROSSJOINsys.all_objects b
GO
INSERTINTO OrderTable(ID, ID1, ID2, FirstName,LastName,City)
SELECT 100001,'A2C4','E67','FirstNameTest','SecondNameTest','CityNameTest'
GO
If we see here ID1 have got a more distinct values compared to ID2 which
means ID1 has got a better cardinality when sql server can use it
-- Selecting data - Table Scan - CTRL + M
SELECT ID, ID1, ID2
FROM OrderTable
WHERE ID = 100001
GO
-- Creating Clustered
CREATECLUSTEREDINDEX [IX_OrderTable_ID] ON [dbo].[OrderTable]
(
[ID] ASC
)ON [PRIMARY]
GO
-- Execute Selects
SELECT ID, ID2,ID1
FROM OrderTable
WHERE ID1 ='A2C4'AND ID2 ='E67'
GO
SELECT ID, ID2,ID1
FROM OrderTable
WHERE ID2 ='E67'AND ID1 ='A2C4'
GO
Here you can see both has used the ID2 Index so it is Index seek.
FROM OrderTable
WHERE ID1 ='A2C4'AND ID2 ='E67'
GO
SELECT ID, ID2, ID1
FROM OrderTable
WHERE ID2 ='E67'AND ID1 ='A2C4'
GO
Here I has switched to index seek on ID1 rather than on ID2 remember the
intial query of distinct values in that we have a probability that ID1 have more
distinct values when compared to ID2 .
Hence because of higher cardinality sql server has inteligently
switched from Index scan to Index seek and to better index inside a same non-
clustered indexes that it is got.
Sql server uses a cost based optemiser that is the reason why we get this.
/*
Due to higher cardinatlity of ID1 that index is [IX_OrderTable_ID1] is used
*/
Again it shows clustered index scan and it is not used non-clusterd just before
created. It still shows the missing indexes.
GO
-- Execute Selects CTRL+M
SELECT ID1, LastName, City, FirstName, ID2
FROM OrderTable
WHERE City ='Salt Lake City'
GO
It started to use Index seek which is the non-clustered index and it is using the
order table city ID1 index which is just now created.
Therefore, the order in the where clauses do not matter and order in which
the columns are represented inside the indexes in their keys do really matter.
Page 57 of 59
Check any new index is required and missing index would tell the particular
information.
Check maintenance jobs on rebuild and reorganising the indexes.
Page 58 of 59
Page 59 of 59