Tải bản đầy đủ (.pdf) (71 trang)

Microsoft SQL Server 2000 Programming by Example phần 4 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (877.56 KB, 71 trang )

Microsoft SQL Server 2000 Programming by Example

198
between the root node and the leaf level. These intermediate pages form the nonleaf level of the
index.
Not every index has a nonleaf level.
SQL Server tables can be organized in two ways:
• As a heap, where data is stored without any specific order.
• As a clustered index, where data is stored in order according to the order ofspecific key fields.
In a clustered index, the actual data pages are the leaf level of the index, because the data is already in order.
Clustered Indexes
As explained before with the telephone directory example, SQL Server can create clustered indexes, ordering
the actual data using the selected key columns as ordering criteria.
Figure 6.13 shows the schema of a clustered index. You can see in the schema that the leaf level of the
index is the actual data. Because the data is physically in order, there is no need for an extra layer with
ordered key values plus pointers to the physical rows.
Figure 6.13. A clustered index has a leaf level and a root node, plus more nonleaf levels if required.

Because a clustered index physically orders the actual data, it is not possible to create more than one
clustered index per table.
When you create a clustered index, perhaps the keys of the index are not unique in the table. If you created a
clustered nonunique index, SQL Server creates an extra hidden-integer field to uniquely identifyevery physical
row.
Caution
Chapter 6. Optimizing Access to Data: Indexes
199
As you will see in Chapter 7, when you create a PRIMARY KEY, SQL Server creates a
CLUSTERED INDEX by default, unless you specify NONCLUSTERED in the PRIMARY KEY definition.
Thus, creating a PRIMARY KEY with the default settings prevents you from creating a new
clustered index.


Creating and Dropping Clustered Indexes
As Listing 6.11 shows, to create a clustered index you must specify CLUSTERED in the CREATE INDEX
statement.
Listing 6.11 You Must Specify the CLUSTERED Keyword to Create a Clustered Index


USE Northwind
GO

IF OBJECT_ID('OrderDetails') IS NOT NULL
DROP TABLE OrderDetails
GO

Create a new table with the same structure and data
as the Order Details table

SELECT *
INTO OrderDetails
FROM [Order Details]

Create a Clustered index on the new table

CREATE CLUSTERED INDEX C_ORDER_DETAILS_ORDER_PRODUCT
ON OrderDetails (OrderID, ProductID)
To drop a CLUSTERED INDEX, you have to execute a DROP INDEX statement, as in Listing 6.12. Note that
you must qualify the index name with the table name, because index names are not unique in the database.
Listing 6.12 You Must Execute the DROP INDEX Statement to Remove an Index from a Table


USE Northwind

GO

Microsoft SQL Server 2000 Programming by Example

200
DROP INDEX OrderDetails.C_ORDER_DETAILS_ORDER_PRODUCT
Note
If you did not execute the code from Listing 6.11 and you try to execute the code from Listing
6.12, you will get an error message because the OrderDetails table does not exist.

When you drop a clustered index, the leaf level is not removed; otherwise, the data itself would be removed.
Only the root node and the nonleaf levels will be deallocated.
Caution
You cannot specify the CLUSTERED keyword in the DROP INDEX statement.

Specify the UNIQUE keyword to declare a CLUSTERED INDEX as unique, as you can see in Listing 6.13.
Listing 6.13 You Must Specify the UNIQUE Keyword to Create a Unique Clustered Index


USE Northwind
GO

CREATE UNIQUE CLUSTERED INDEX UC_ORDER_DETAILS_ORDER_PRODUCT
ON OrderDetails (OrderID, ProductID)
Note
If you did not execute the code from Listing 6.11 and you try to execute the code from Listing
6.13, you will get an error message, because the OrderDetails table does not exist.

In SQL Server 2000, every column of the key in an index can be sorted using ascending or descending order.
Listing 6.14 shows an example of this new feature.

Listing 6.14 You Can Specify Descending or Ascending Order for Every Column in the Index Key
Chapter 6. Optimizing Access to Data: Indexes
201


USE Northwind
GO

CREATE INDEX C_Products_Category_Price
ON Products (CategoryID ASC, UnitPrice DESC)
Accessing Data Through Clustered Indexes
If a table is stored as a clustered index and the Query Optimizer decides to use the clustered index to return
the result, SQL Server can access data in that table in different ways:
• As a Clustered Index Scan if the query doesn't restrict the data to be returned.
• As a Clustered Index Seekwhen the query is restricted to a certain number of rows.
Accessing Data Through a Clustered Index Scan
When a Clustered Index Scan is required, it is not guaranteed that the data will be returned in order, because
SQL Server could use the information stored in the IAM pages to access the data more efficiently than
navigating the index. If you need results in order, you must specify an ORDER BY clause in the query. This is
the case of the example of Figure 6.14.
Figure 6.14. SQL Server uses a Clustered Index Scan to execute unrestricted queries.

Microsoft SQL Server 2000 Programming by Example

202
Using a Clustered Index Seek to Execute Point Queries
SQL Server can use aClustered Index Seek to retrieve individual rows. Figure 6.15 shows an example.
Figure 6.15. SQL Server uses a Clustered Index Seek to execute restricted queries.

In this example, SQL Server navigates the index from the root node to the leaf level, applying a binary search

until reaching the required data.
Using a Clustered Index Seek to Execute Queries with a Range Search
Perhaps a more interesting use of a clustered index is to execute queries restricted to a range of values.
Figure 6.16 shows an example of a range search.
Figure 6.16. SQL Server uses a Clustered Index Seek to execute queries that contain a range search.
Chapter 6. Optimizing Access to Data: Indexes
203

In this example, SQL Server navigates the index from the root node to the leaf level, searching for the lower
limit of the range, and then continues reading from the leaf level until it reaches the upper limit of the range.
Nonclustered Indexes
The leaf level of a nonclustered index contains one entry for every row in the table. Each entry in the index
contains the key columns and a pointer to identify the row in the table where this index entry points.
As explained earlier in this chapter, if the table doesn't have a clustered index, the pointer included on every
entry of the nonclustered index is a binary value. This binary value is a combination of the file number, page
number, and row slot number where the original row is stored. In this way, every entry in the index is
connected to the physical location of the row. If a row changes its physical location, the index pointer must be
modified. This process can produce some overhead on SQL Server.
If the table has a clustered index, the index entries don't point to the physical location of the rows. In this case,
the pointer is the clustered key of the corresponding row. You will see later in this chapter how to access data
using a nonclustered index when the data is stored as a clustered index.
Creating and Dropping Nonclustered Indexes
You can specifythe keyword NONCLUSTERED to create a nonclustered index using the CREATE INDEX
statement. This is the default option. Listing 6.15 shows how to create a NONCLUSTERED index.
Listing 6.15 You Can Specify the NONCLUSTERED Keyword to Create a Nonclustered Index


Microsoft SQL Server 2000 Programming by Example

204

USE Northwind
GO

CREATE NONCLUSTERED INDEX C_ORDER_DETAILS_PRODUCT
ON [Order Details] (ProductID)
To drop a NONCLUSTERED INDEX,you have to execute a DROP INDEX statement, as in Listing 6.16.
Listing 6.16 You Must Execute the DROP INDEX Statement to Remove an Index from a Table


USE Northwind
GO

DROP INDEX [Order Details].C_ORDER_DETAILS_PRODUCT
When you drop a nonclustered index, the complete index is removed, including the leaf level, the root node,
and the nonleaf levels.
Caution
You cannot specify the NONCLUSTERED keyword in the DROP INDEX statement.

Specify the UNIQUE keyword todeclare a NONCLUSTERED INDEX as unique, as you can see in Listing 6.17.
Listing 6.17 You Must Specifythe UNIQUE Keyword to Create a Unique Nonclustered Index


USE Northwind
GO

IF OBJECT_ID('NewOrders') IS NOT NULL
DROP TABLE NewOrders
GO

SELECT *

INTO NewOrders
FROM Orders
GO

CREATE UNIQUE NONCLUSTERED INDEX UNC_ORDERS_ORDERID
Chapter 6. Optimizing Access to Data: Indexes
205
ON NewOrders (OrderID)
Accessing Data Through Nonclustered Indexes
The way SQL Serverretrieves data through nonclustered indexes depends on the existence of a clustered
index on the table. As explained earlier in this chapter, the reason for this difference in behavior is to optimize
index maintenance, trying to avoid the continuous physical pointer modifications when data rows must be
moved because of reordering the clustered index.
Data Stored As a Heap
If table data is stored as a heap, the nonclustered index is a binary structure built on top of the actual data.
Figure 6.17 shows an example of index navigation.
Figure 6.17. SQL Server uses a Nonclustered Index Seek to search for rows within a search condition.

SQL Server searches for entries in the nonclustered index, starting from the root node and going down to the
leaf level of the index.
Every entry in the leaf level has a pointer to a physical row. SQL Server uses this pointer to access the page
where the row is located and reads it.
Data Stored As a Clustered Index
Microsoft SQL Server 2000 Programming by Example

206
If table data is stored as a clustered index, it is stored in the order specified by the clustered index definition.
The index is a binary structure built on top of the actual data, which in this case is the leaf level of the
clustered index.
Nonclustered indexes don't have a pointer to the physical position of the rows. They have as a pointer the

value of the clustered index key.
Figure 6.18 shows an example of nonclustered index navigation on top of a clustered index.
Figure 6.18. SQL Server uses a Nonclustered Index Seek to search for rows within a search condition,
and it must navigate the clustered index as well.

If SQL Server decides to use a Nonclustered Index Seek to execute a query, it must follow a process similar
to the one described in the preceding section, with an important difference: It also must navigate the clustered
index to arrive at the physical rows.
You have to consider the extra work when you use a nonclustered index on top of a clustered index. However,
if you consider the number of pages to read, you will consider this solution quite efficient.
The customers table could have 20,000 pages, so to execute a table scan, SQL Server will have to read
20,000 pages.
Indexes don't have many levels, often less than four. The right index image should be a pyramid with a base
of thousands of pages and only three or four pages high. Even if you have two pyramids to navigate, the
number of pages to read is still much smaller than reading through a full table scan.
Covered Queries and Index Intersection
Mentioned earlier in this chapter were different ways to access data, depending on the available indexes. Let's
consider the example of Listing 6.18. If you had an index on (City, CompanyName, ContactName),
this index has every field required to execute the query. In this case, it is not required to access the data
pages, resulting in a more efficient access method. Figures 6.19 and 6.20 show the query plan with and
without this index.
Chapter 6. Optimizing Access to Data: Indexes
207
Figure 6.19. SQL Server uses the index on (City, CompanyName, ContactName) to cover the query.

Figure 6.20. SQL Server needs access to the data pages to execute the query if an index on (City,
CompanyName, ContactName) doesn't exist.

Listing 6.18 This Query Can Be Coveredby an Index on the Columns (City, CompanyName,
ContactName)



USE Northwind
GO

SELECT CompanyName, ContactName
FROM Customers
WHERE City = 'Madrid'
In these situations, you can say that the selected index covers the query.
Microsoft SQL Server 2000 Programming by Example

208
If you consider the example in Listing 6.19, you can see in the query execution shown in Figure 6.21 that
the index in (City, CompanyName, ContactName) still covers the query, even if you added a new field
called CustomerID.
Figure 6.21. SQL Server uses the index on (City, CompanyName, ContactName) to cover the query
even when you add the field CustomerID.

Listing 6.19 This Query Can Be Covered by an Index on the Columns (City, CompanyName,
ContactName)


USE Northwind
GO

SELECT CustomerID, CompanyName, ContactName
FROM Customers
WHERE City = 'Madrid'
The Customers table has a clustered index on the field CustomerID, and this field is used as a pointer for
every nonclustered index, such as the one you created on (City, CompanyName, ContactName). In

other words, every key entry in the index actually contains four fields: City, CompanyName, ContactName,
and CustomerID. That's why this index covers this query, too.
Trying to cover every query is almost impossible and requires too much space and maintenance cost. SQL
Server 2000 can combine indexes, if required, to execute a query, and this technique is called index
intersection. Listing 6.20 shows a query that selects three fields from the [Order Details] table and
applies two conditions to two of the selected fields. In this case, you could be tempted to create a composite
index on these three fields to cover the query, but if you had already an index on UnitPrice and another
index on OrderID, SQL Server can combine these two indexes to solve the query. Figure 6.22 shows the
query plan of this query.
Figure 6.22. SQL Server combines two indexes to solve the query.
Chapter 6. Optimizing Access to Data: Indexes
209

Listing 6.20 This Query Can Be Solved by an Index on OrderID and Another Index on UnitPrice


USE Northwind
GO

SELECT OrderID, UnitPrice
FROM [Order details]
WHERE UnitPrice > 15
AND OrderID > 11000
Index Maintenance
SQL Server automatically maintains indexes on tables. Every INSERT, UPDATE, or DELETE operation forces
SQL Server to update the index information to keep it up to date. This maintenance produces some overhead
on these operations.
Any time you insert a new row and you had a clustered index, SQL Server must search for the correct page to
insert this new row. If the page is full, SQL Server decides the best way to insert this row, depending on the
following conditions:

• If the row has to be inserted at the end of the table and the last page doesn't have any free space,
SQL Server must allocate a new page for this new row.
• If the row has to be inserted into an existing page and there is enough free space in the page to
allocate this new row, the row will be inserted in any free space in the page, and the row-offset will be
reordered to reflect the new row order.
• If the row has to be inserted into an existing page and the page is full, SQL Server must split this page
into two. This is done by allocating a new page and transferring 50% of the existing rows to the new
page. After this process, SQL Server will evaluate on which one of these two pages the new row must
be inserted. The row-offset list must be reordered according to new row order.
Figure 6.23 shows the split-page process. This Page Split process produces some overhead for SQL Server
as well.
Microsoft SQL Server 2000 Programming by Example

210
Figure 6.23. To insert a new row into a full page, SQL Server must split the page.

The same process must be done to accommodate a new key into a leaf-level page of a nonclustered index.
Rebuilding Indexes
If you would like to change the index definition, you can use the CREATE INDEX statement with the
DROP_EXISTING option. Listing 6.21 shows an example where you want to convert a nonclustered index on
(OrderID, ProductID) on the [Order Details] table into a clustered index on the same fields.
Chapter 6. Optimizing Access to Data: Indexes
211
Listing 6.21 You Can Modify Existing Indexes with the CREATE INDEX Statement and the
DROP_EXISTING Option


USE Northwind
GO


CREATE UNIQUE CLUSTERED INDEX UC_ORDER_DETAILS_ORDER_PRODUCT
ON OrderDetails (OrderID, ProductID)
WITH DROP EXISTING
Note
If you did not execute the code from Listing 6.11 and you try to execute the code from Listing
6.21, you will get an error message because the OrderDetails table does not exist.

In this case, other nonclustered indexes must be rebuilt because they must point to the clustered keys, and
not to the physical row locations.
If you had a table with aclustered index and several nonclustered indexes and you wanted to modify the
clustered index definition, you could drop the index and create it again. In this case, the nonclustered indexes
must be rebuilt after the clustered index is dropped, and they must be rebuilt again after the clustered index is
re-created. However, using the DROP_EXISTING option to rebuild the clustered index will save time, because
the nonclustered indexes will be rebuilt automatically just once, instead of twice, and only if you select
different key columns for the clustered index.
Tip
Create the clustered index before the nonclustered indexes. In this way, you can avoid rebuilding
the nonclustered indexes because of the creation of the clustered index.

When an index is rebuilt, the data is rearranged, so external fragmentation is eliminated and internal
fragmentation will be adjusted, as you'll see later in this chapter.
Another alternative to CREATE INDEX WITH DROP EXISTING is to use the DBCC DBREINDEX.This
statement can rebuild all the indexes of a given table with a single command. This is the preferred way to
rebuild indexes if they are part of a constraint definition. Listing 6.22 shows the statement to rebuild all the
indexes of the [Order Details] table. In this case, indexes are rebuilt with the same definition they were
created.
Microsoft SQL Server 2000 Programming by Example

212
Listing 6.22 Use DBCC DBREINDEXto Rebuild All the Indexes of a Table



USE Northwind
GO

DBCC DBREINDEX ('[Order details]')
Index Fragmentation
Every time a page is split, the index suffers some fragmentation. If fragmentation were important, it would be
necessary to read more pages than normal to retrieve the same information. For read-only tables,
fragmentation must be as minimal as possible. However, for read/write tables it is better to have some
fragmentation to accommodate new rows without splitting too many pages.
You can adjust the fragmentation of an index using the FILLFACTOR option. FILLFACTOR expects a value
between 1 and 100, which specifies the percentage of the page that should be full at the time the index is
created. The actual percentage will be less than or equal to the specified fill factor.
Applying a FILLFACTOR will pack or expand the leaf-level pages to accommodate this new filling factor. For
nonleaf-level pages, there will be one free entry per page. Listings 6.23 and 6.24 show two examples of
rebuilding an index with a new FILLFACTOR. Listing 6.23 uses CREATE INDEX,and Listing 6.24 uses
DBCC DBREINDEX.
Listing 6.23 You Can Specify a New FILLFACTOR for an Existing Index with the CREATE INDEX
Statement and the FILLFACTOR Option


USE Northwind
GO

CREATE NONCLUSTERED INDEX OrderID
ON [Order Details] (OrderID)
WITH DROP_EXISTING, FILLFACTOR = 80
Listing 6.24 Use DBCC DBREINDEX to Rebuild All the Indexes of a Table with a Different FILLFACTOR



Chapter 6. Optimizing Access to Data: Indexes
213
USE Northwind
GO

DBCC DBREINDEX ('[Order details]', '', 70)
Considering that fragmentation on nonleaf-level pages will be produced only when allocating and deallocating
new pages at leaf level, having a free entry per page should be considered normal. If you expected many new
rows and many new pages in the leaf level, you could be interceding to specify the PAD_INDEX option, which
will apply the FILLFACTOR value to nonleaf-level pages as well. Listing 6.25 shows how to apply this option
to one of the indexes of the [Order Details] table.
Listing 6.25 You Can Specify a New FILLFACTOR for an Existing Index with the CREATE INDEX
Statement and the FILLFACTOR Option, and Apply This FILLFACTOR to the Nonleaf-Level Pages with
the PAD_INDEX Option


USE Northwind
GO

CREATE NONCLUSTERED INDEX OrderID
ON [Order Details] (OrderID)
WITH DROP_EXISTING, FILLFACTOR = 80, PAD_INDEX
If you want to avoid fragmentation on data pages, you can build a clustered index and specify a FILLFACTOR
option with a value of 100. If there is already a clustered index on the table, you can rebuild the index and
specify a value of 100 for FILLFACTOR. Listing 6.26 shows how to pack the data pages on the Products
table by rebuilding the index PK_Products with a FILLFACTOR of 100.
Listing 6.26 Use DBCC DBREINDEX to Pack the Data Pages by Rebuilding the Clustered Index with a
FILLFACTOR of 100



USE Northwind
GO

DBCC DBREINDEX ('Products', PK_Products, 100)
If the clustered index is not required to provide a normal use of the table, and you want to pack data pages,
you can create a clustered index with FILLFACTOR 100 and drop the clustered index when it's created.
Index Statistics
SQL Query Optimizerselects the best strategy based on indexes available for every table to be used in the
query and for specific information about every index. For every index, Query Optimizer gets general
information from the sysindexes table about
Microsoft SQL Server 2000 Programming by Example

214
• The number of data pages in the index (the field dpages)
• The approximate number of rows (the field rowcnt)
• Density of the index (information included in the statblob field)
• Average length of the key (information included in the statblob field)
However, this information is not enough to predict whether the index is useful in a particular query.
Consider the Customers table. To filter customers who live in a specific city, having an index on the City
column can be useful. However, if 95% of your customers live in Toronto, the index on City will be useless
when searching for customers living in Toronto. In this case, a table scan will produce better results.
SQL Server maintains distribution statistics about every index. Statistic information is stored in the statblob
field of the sysindexes table.
SQL Server samples the data to select information organized in ranges of data. For every range, SQL Server
stores
• The number of rows where the key is in the specific range, excluding the maximum value of the range
• The maximum value of the range
• The number of rows wherethe value of the key is equal to the maximum value of the range
• The number of distinct key values in the range, excluding the maximum value of the range

SQL Server calculates the average density of every range as well, dividing number of rows by number of
distinct key values on every range.
Listing 6.27 shows how to use the DBCC SHOW_STATISTICSstatement to get statistics information about
the (Products.SupplierID) index.
Listing 6.27 Use DBCC SHOW_STATISTICS to Get Information About Index Statistics


USE Northwind
GO

DBCC SHOW_STATISTICS (Products, SupplierID)


Statistics for INDEX 'SupplierID'.
Updated Rows Rows Sampled Steps Density Average key length

Oct 23 2000 5:16PM 77 77 20 3.3189032E-2 8.0
(1 row(s) affected)

All density Average Length Columns

3.4482758E-2 4.0 SupplierID
1.2987013E-2 8.0 SupplierID, ProductID
Chapter 6. Optimizing Access to Data: Indexes
215

(2 row(s) affected)

RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS


1 0.0 3.0 0 0.0
2 0.0 4.0 0 0.0
4 3.0 3.0 1 3.0
5 0.0 2.0 0 0.0
6 0.0 3.0 0 0.0
7 0.0 5.0 0 0.0
8 0.0 4.0 0 0.0
9 0.0 2.0 0 0.0
10 0.0 1.0 0 0.0
11 0.0 3.0 0 0.0
12 0.0 5.0 0 0.0
13 0.0 1.0 0 0.0
17 9.0 3.0 3 3.0
19 2.0 2.0 1 2.0
20 0.0 3.0 0 0.0
22 2.0 2.0 1 2.0
24 3.0 3.0 1 3.0
26 2.0 2.0 1 2.0
27 0.0 1.0 0 0.0
29 2.0 2.0 1 2.0

(20 row(s) affected)

DBCC execution completed. If DBCC printed error messages,
contact your system administrator.
SQL Server by defaultautomatically creates statistics when you create an index and automatically maintains
these statistics as you add rows to the base table. It is possible, but not advisable, to avoid automatic
statistics maintenance by setting an option at database level, as shown in Listing 6.28.
Listing 6.28 By Using sp_dboption, It Is Possible to Avoid Automatic Statistics Creation and
Maintenance for the Entire Database



USE Northwind
GO

Change the database setting to avoid
statistics creation and maintenance

EXEC sp_dboption 'Northwind', 'auto create statistics', 'false'

EXEC sp_dboption 'Northwind', 'auto update statistics', 'false'

To test the present settings

PRINT 'After changing to manual statistics maintenance'

EXEC sp_dboption 'Northwind', 'auto create statistics'
Microsoft SQL Server 2000 Programming by Example

216

EXEC sp_dboption 'Northwind', 'auto update statistics'


After changing to manual statistics maintenance
OptionName CurrentSetting

auto create statistics off

OptionName CurrentSetting


auto update statistics off
It is possible to create statistics on individual columns, or groups of columns, without creating an index. These
statistics can be helpful for Query Optimizer to select efficient query execution strategies. Listing 6.29 shows
different ways to create statistics.
Listing 6.29 It Is Possible to Create Statistics on Nonindexed Columns


USE Northwind
GO

To create statistics in an individual column

CREATE STATISTICS stProductsStock
ON Products(UnitsInStock)

To create statistics in a group of columns

CREATE STATISTICS stProductsStockOrder
ON Products(UnitsInStock, UnitsOnOrder)

To create single column statistics for all eligible columns
on all user tables in the current database

EXEC sp_createstats
To retrieve information aboutavailable statistics in a table, you can use the sp_helpstats system stored
procedure. Listing 6.30 shows an example of sp_helpstats execution.
Listing 6.30 Use the sp_helpstats System Stored Procedure to Get Information About Statistics
Chapter 6. Optimizing Access to Data: Indexes
217



USE Northwind
GO

EXEC sp_helpstats
Products
statistics_name statistics_keys



_WA_Sys_UnitPrice_07020F21 UnitPrice
QuantityPerUnit QuantityPerUnit
ReorderLevel ReorderLevel
stProductsStock UnitsInStock
stProductsStockOrder UnitsInStock, UnitsOnOrder
UnitsOnOrder UnitsOnOrder
Getting Information About Indexes
You can use the sp_help system stored procedure to retrieve general information about a table, including
the list of available indexes. Using the sp_helpindex system stored procedure you can get only the list of
indexes available for a specific table, as in the example included in Listing 6.31.
Listing 6.31 Use the sp_helpindex System Stored Procedure to Retrieve Information About Indexes
in a Table


USE Northwind
GO

EXEC sp_helpindex customers


index_name index_description index_keys

Microsoft SQL Server 2000 Programming by Example

218


City nonclustered located on PRIMARY City
CompanyName nonclustered located on PRIMARY CompanyName
Contact nonclustered located on PRIMARY ContactName
PK_Customers clustered, unique, primary key located on PRIMARY CustomerID
PostalCode nonclustered located on PRIMARY PostalCode
Region nonclustered located on PRIMARY Region
To get specific information about individual index properties, you can use the INDEXPROPERTY system
function as demonstrated in Listing 6.32.
Listing 6.32 Use the INDEXPROPERTY Function to Retrieve Information About Any Index


USE Northwind
GO

To retrieve number of index levels

SELECT INDEXPROPERTY(OBJECT_ID('Products'), 'PK_Products', 'IndexDepth')
AS 'Index Levels'
To determine if the index is clustered

SELECT CASE
INDEXPROPERTY(OBJECT_ID('Products'), 'PK_Products', 'IsClustered')
WHEN 0 THEN 'No'

ELSE 'Yes'END as 'Is Clustered'

To determine if it is not a real index
So it cannot be used for data access
because it contains only statistics
SELECT CASE
INDEXPROPERTY(OBJECT_ID('Products'), 'PK_Products', 'IsStatistics')
WHEN 0 THEN 'No'
ELSE 'Yes'END as 'Is Statistics only'

To know if the index is unique

SELECT CASE
INDEXPROPERTY(OBJECT_ID('Products'), 'PK_Products', 'IsUnique')
WHEN 0 THEN 'No'
ELSE 'Yes'END as 'Is Unique'
Chapter 6. Optimizing Access to Data: Indexes
219


Index Levels

1

Is Clustered

Yes

Is Statistics only


No

Is Unique

Yes
Indexes on Computed Columns
In SQL Server 2000, you can create computed columns in a table. These columns don't use storage space,
and SQL Server maintains them automatically whenever the underlying data changes.
You can create a computed column SalePrice in the [Order Details] table to get the total sale value of
every row, considering the unit price and quantity. To speed up the process of searching, or sorting, for this
SalePrice column, you can create an index on this computed column, and Query Optimizer might use it, if
necessary. Listing 6.33 shows the complete process.
Listing 6.33 It Is Possible to Create Indexes on Computed Columns


USE Northwind
GO

Create the computed column SalePrice
ALTER TABLE [order details]
ADD SalePrice AS (UnitPrice * Quantity)

Create an index on SalePrice

CREATE INDEX ndxSale ON [order details] (SalePrice)
To create an index on a computed column, you have to check the following requirements:
Microsoft SQL Server 2000 Programming by Example

220
• The expression defining the computed column must be deterministic.An expression is deterministic if

it always produces the same results for the same arguments. Every function referenced in the
expression must be deterministic, too.
An example of a nondeterministic expression is (Month(GetDate()) because it uses a
nondeterministic function, GetDate, which changes every time you call it.
• The expression must be precise. To be precise, the expression cannot use the float or real data types,
or any combination of them, even if the final result uses a precise data type.
If you define the SalePrice computed column as (UnitPrice * Quantity * (1 -
Discount)), the expression is not precise because it uses an imprecise field: Discount. In this
case, you cannot create an index on this computed column.
• Because connection settings can affect results, the connection that creates the index in the computed
column, and every connection that modifies data affecting this index, must have settingsaccording to
the following list:

• SET ANSI_NULLS ON
• SET ANSI_PADDING ON
• SET ANSI_WARNINGS ON
• SET ARITHABORT ON
• SET CONCAT_NULS_YIELDS_NULL ON
• SET QUOTED_IDENTIFIER ON
• SET NUMERIC_ROUNDABORT OFF
Indexed Views
SQL Server 2000 can create indexes on views. This functionality is implemented by extensions in the CREATE
VIEW and CREATE INDEX statements. If a view is not indexed, it doesn't use any storage space. Whenever
you use the view in a Transact-SQL statement, SQL Server merges the view definition with the statement to
produce a single execution plan and it directly accesses the underlying tables on which the view is defined.
After a view is indexed, its index needsstorage space, as any standard index.
The process of creating an indexed view is as follows:
1. Create the view with SCHEMABINDING, which prevents modifications on the definition of referenced
objects.
2. Create a clustered index on the view to physically save the view results in a clustered index structure

where the leaf level will be the complete resultset of the view. The index key should be as short as
possible to provide good performance.
3. Create nonclustered indexes, if required.
After the creation of the clustered index, the view is stored like a clustered index for a table, but this
information is maintained automatically whenever data changes in the underlying tables.
If you reference the view, Query Optimizer will use the indexed view directly, and it will be unnecessary to
access the underlying tables.
SQL Server 2000 Enterprise Edition can use indexed views to optimize the execution of queries that don't
reference the indexed views explicitly, improving execution performance.
Listing 6.34 shows how to create an index on a view. Figure 6.24 shows how SQL Server uses the view
definition to access the base tables directly. Figure 6.25 shows that by having an indexed view, SQL Server
can avoid accessing the base tables, reducing the amount of IO required to execute the query.
Figure 6.24. When using nonindexed views, SQL Server accesses data directly from tables.
Chapter 6. Optimizing Access to Data: Indexes
221

Figure 6.25. When using indexed views, SQL Server doesn't require access to the tables.

Listing 6.34 In SQL Server 2000, You Can Create Indexes on Views


USE Northwind
GO

Create the view

CREATE VIEW Customers_UK
WITH SCHEMABINDING
AS
SELECT CustomerID, CompanyName, ContactName, Phone

FROM dbo.Customers
WHERE Country = 'UK'

Microsoft SQL Server 2000 Programming by Example

222
Test how a normal query uses the view

SELECT CustomerID, CompanyName, ContactName, Phone
FROM Customers
WHERE Country = 'UK'
AND CompanyName like 'C%'

Create a clustered index on the view

CREATE UNIQUE CLUSTERED INDEX CustUK ON Customers_UK (CustomerID)

Create a nonclustered index on the CompanyName field on the view

CREATE NONCLUSTERED INDEX CustUKCompany ON Customers_UK (CompanyName)

Test how a normal query uses the view after indexing the view

SELECT CustomerID, CompanyName, ContactName, Phone
FROM Customers
WHERE Country = 'UK'
AND CompanyName like 'C%'
Not every view can be indexed because some requirements must be met. Some requirements affect the
definition of the view, and others the creation of the index. To create a view that can be indexed, it is
necessary to meet the following requirements:

• The ANSI_NULLS option must be set to ON to create the base tables and the view.
• The QUOTED_IDENTIFIER must be set to ON to create the view.
• The view must reference only base tables, from the same database and owner.
• The view must be created with the SCHEMABINDING option set to prevent changes on the underlying
objects. If the view uses a user-defined function, this must be created as well with SCHEMABINDING.
• To avoid ambiguity, objects must be referenced with two part names, owner, and object name. Three-
or four-part names are not allowed because the view cannot reference objects from other databases
or servers.
• All the expressions used in the view definition must be deterministic, and the expressions used in key
columns must be precise, as explained earlier, for indexes on computed columns.
• The view must specifically name all columns, because SELECT * is not allowed.
• You cannot use a column more than once in the SELECT clause, unless every time the column was
used as a part of a complex expression.
• It is not allowed to use subqueries, and that includes derived tables in the FROM clause.
• You cannot use Rowset functions, such as OPENROWSET, OPENQUERY, CONTAINSTABLE, or
FREETEXTTABLE.
• It cannot contain the UNION operator.
• It cannot contain outer or self-joins.
• It cannot contain the ORDER BY clause, and that includes the use of the TOP clause.
• You cannot use the DISTINCT keyword.
• The only aggregate functions allowed are COUNT_BIG and SUM. To use the SUM function, you must
select COUNT_BIG as well, and you must specify a GROUP BY clause.
• SUM cannot be used on a nullable column or expression.
• It cannot use full-text functions.
• COMPUTE or COMPUTE BY clauses are not allowed.
• The view cannot contain BLOB columns, as text, ntext, and image.
To create an index in a view, some more requirements must be met:
• Only the owner of the view can create indexes on the view.
• The connection settings must be the same as the settings for indexes on computed columns.

×