Printer Friendly Version      Send     
Click to Rate and Give Feedback
Related Articles

This month Scott looks at improving development skills, writing regular expressions, a web scheduling control and a SQL tips blog.

Scott Mitchell

MSDN Magazine November 2008

...

Read more!

With the Entity Framework, developers are given more flexibility by being able to design around a conceptual model rather than a relational data model. To get started, you need to know how to design an Entity Data Model. John Papa walks you through that process.

John Papa

MSDN Magazine February 2008

...

Read more!

Building a data access layer using LINQ to SQL and the ADO.NET Entity Framework allows you to decouple your application from the persistence technology you're using.

Anthony Sneed

MSDN Magazine December 2008

...

Read more!

ASP.NET controls, a comprehensive FTP library, rich imaging support, and a book about SQL Server 2005.

Scott Mitchell

MSDN Magazine September 2007

...

Read more!

Language Integrated Query makes lots of things easier. Here we put LINQ, or more specifically the LINQ to SQL provider, to use testing SQL stored procedures.

Dr. James McCaffrey

MSDN Magazine April 2008

...

Read more!

Also by this Author

Smart client apps use local resources, provide a rich client experience, and support intelligent install mechanisms. Web services offer powerful interoperability and integration features. Find out how to combine them to develop integrated apps that incorporate data from disconnected sources.

Carl Nolan

MSDN Magazine February 2006

...

Read more!

Popular Articles

A Sidebar gadget is a powerful little too that's surprisingly easy to create. Get in on the fun with Donavon West.

Donavon West

MSDN Magazine August 2007

...

Read more!

One-time passwords offer solutions to dictionary attacks, phishing, interception, and lots of other security breaches. Here's how it all works.

Dan Griffin

MSDN Magazine May 2008

...

Read more!

James Avery does it again with his popular list of developer tools. This time he covers the best Visual Studio add-ins available today that you can download for free.

James Avery

MSDN Magazine December 2005

...

Read more!

Jason Clark

MSDN Magazine July 2003

...

Read more!

Here are some design patterns that allow you to achieve higher cohesion and looser coupling for more flexible, reusable applications.

Jeremy Miller

MSDN Magazine October 2008

...

Read more!

Our Blog

Windows Workflow Foundation (WF) imposes some restrictions on the developer authoring programs that target it. But in return WF offers a powerful, flexible, and extensible set of runtime services such as support for long-running code.

In the December 2008 issue of MSDN Magazine, Josh Lane provides some best practices to consider ...

Read more!

Because Windows Workflow Foundation (WF) is based on a runtime that manages the execution of workflows and activities, testing must, in almost all cases, involve the use of the runtime – and this can introduce some interesting challenges.

In the November 2008 issue of MSDN Magazine, Matt Milner presents some techniques for unit testing ...

Read more!

Earlier this year MSDN Magazine embarked on a collaborative project with Behind the Code, an interview program airing on MSDN Channel 9. In this program, Robert Hess interviews prominent developers at Microsoft, and those developers also write a column for { End Bracket } in MSDN Magazine. In the newest interview, Richard Ward talks about working on the core infrastructure components of future versions of Windows, as well as ...

Read more!

Silverlight and SharePoint provide a simple, yet powerful, infrastructure for building intranet and extranet applications with sophisticated user interface designs and interactions.

In the November 2008 issue of MSDN Magazine, Steve Fox and Paul Stubbs demonstrate how to build a SharePoint Web Part as a wrapper for a Silverlight application.

...

Read more!

So many factors can affect the performance of a Web page—the distance between server and client, the size of the elements on the page, how the browser loads these elements, available bandwidth. Finding those bottlenecks and identifying the culprits is no easy task.

In the November 2008 issue of MSDN Magazine, Jim Pierson introduces ...

Read more!

This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
MSDN Magazine

SQL Server 2000: New Features Provide Unmatched Ease of Use and Scalability to Admins and Users

Carl Nolan
This article assumes you�re familiar with Transact-SQL and XML
Level of Difficulty    1   2   3 
Code for this article: Nolan0800.exe(53KB)
SQL Server 2000 includes many new features that extend its capabilities as a high performance relational database system with a rich development environment. This article presents an overview of new product features including AWE support, new data types, new user-defined and built-in functions as well as enhancements in trigger functionality, indexing, collation, and property definition. The article then concentrates on XML support, covering XML data retrieval and manipulation, queries, and HTTP access. It concludes with a discussion of performance improvements in SQL Server 2000.
M
icrosoft SQL Server™ 2000 provides major enhancements to the current SQL Server product. Numerous changes have been made across the product, including the Relational Engine, the Storage Engine, administration and tools, replication, the Analysis Service (OLAP Services and Data Mining), English Query, full-text search, integration with Windows® 2000 (with Active Directory™, Address Windowing Extensions, and Windows 2000 Data Center), and Meta Data Services (including the Repository Engine).
      The main areas of interest for developers will be the improvements in the Relational Engine. These features extend the functionality of the database platform to make SQL Server 2000 an outstanding, feature-rich environment for application development, and I'll be focusing on the enhancements to the Transact-SQL programmability of SQL Server 2000 in this article. This discussion is based on the Beta 2 release of the product (the final test release). To get better acquainted with the new features of SQL Server, you can obtain a copy of the current beta from the Microsoft® Web site at http://www.microsoft.com/sql/productinfo/sql2000.htm. If you are an MSDN® Universal subscriber, then this release will be provided as part of your subscription. The final release is due out this summer. In the interim, the feature set may change.
      In addition to Transact-SQL enhancements, many internal changes have been made to improve the performance, scalability, and reliability of SQL Server. This includes support for 32 processors and 64GB of memory (through a feature called AWE in Windows 2000 Advanced Server and DataCenter Server), parallel DBCC operations and index creation, improved backup and restore architecture, four-node failover clustering support, integrated failover standby server management (log shipping), and native support for high-speed System Area Network (SAN) technology.
      Replication is an area where improvements have been made in performance, reliability and feature set. If you are using replication, seriously consider adopting SQL Server 2000. Replication now supports Queued Updating Subscribers. Similar to transactional replication, the subscriber gets updated via a transactional message queue. This allows a subscriber to spend a large amount of time disconnected from the network while ensuring that replication occurs in a transactionally consistent manner. Another big improvement that will ease the use of replication is the support for schema replication, namely ALTER and DROP columns statements. This support is for transactional and merge replication and will not require a publication to be redefined.
      Another key area of improvement is the full-text search capabilities in SQL Server. Full-text search now includes change tracking and image filtering. Change tracking maintains a log of all changes to the full-text indexed data. You can update the full-text index with these changes by flushing the log manually, on a schedule, or as changes occur. Image filtering allows you to index and query documents stored in image columns. To support this you have to define the document type to allow the full-text search engine to load the appropriate document filter to extract textual information for indexing.
      In addition to these enhancements, SQL Server now supports multiple instances of the database engine running concurrently on the same computer, each having its own set of system and user databases. Applications can connect to each database engine instance on a computer in much the same way they connect to database engines running on different computers. Each system will have a default instance, identified solely by the name of the computer. All other named instances are identified by an instance name specified during installation. Named instances are identified by the computer name and the instance name in the format computer_name\instance_name.
      In this article I will discuss new data type support, user-defined functions, enhancements to triggers, cascading constraint capabilities, indexed views and view enhancements, updateable union views called distributed partitioned views, support for collations, extensions to property definitions, new built-in functions, and perhaps most noteworthy of all, XML support. Most examples here use the Northwind database provided with SQL Server.

New Data Types

      Like its predecessor, SQL Server 7.0, SQL Server 2000 delivers several new data types. SQL Server 7.0 introduced a whole range of Unicode data types and the GUID unique identifier. SQL Server 2000 introduces the 64-bit integer (bigint), variant (sql_variant), and table data types.
      The bigint data type is the easiest to explain. It is just an integer that supports data from -263 (-9,223,372,036,854,775,808) through 263-1 (9,223,372,036,854,775,807). Storage size for the bigint is 8 bytes, hence the name 64-bit integer. You will want to use the new integer in support of identity columns where the tables will exceed the previous maximum integer size of 231-1 (2,147,483,647). The new bigint data type is supported where integer values are supported, but there is a caveat: functions will return bigint only if the parameter expression is a bigint data type. SQL Server will not automatically promote other integer data types (such as tinyint, smallint, and int) to bigint.
      In support of the new bigint data type, two new built-in functions have been introduced; BIG_COUNT and ROWCOUNT_ BIG. These functions operate in a manner similar to the COUNT function and @@ROWCOUNT variable except they return an integer value of type bigint.
      The new sql_variant data type stores values of various supported data types except text, ntext, image, timestamp, and sql_ variant. This data type may be used in columns, parameters, variables, and return values of user-defined functions, which are a new feature of SQL Server that I will describe shortly. The sql_variant data type operates much like the variant data type in Visual Basic®. It allows a single column, parameter, or variable to store data values of different data types. For example, a single sql_variant column can hold int, decimal, char, binary, and nchar values. Each sql_variant instance records a data value and associated metadata information, which includes the base data type, maximum size, scale, precision, and collation.
      The key to working with a sql_variant data type is to remember to always cast the variant to the required base data element. This is easily achieved using the CAST operator:


DECLARE @my_var sql_variant
SET @my_var = 3.142

DECLARE @my_chr VARCHAR(12), @my_dec DECIMAL(5,4)
SET @my_chr = CAST(@my_var AS VARCHAR(12))
SET @my_dec = CAST(@my_var AS DECIMAL(5,4))
      To support the sql_variant data type, a new function called SQL_VARIANT_PROPERTY has been introduced. This function returns the base data type and other information about a sql_variant instance:


SQL_VARIANT_PROPERTY(expression, property)
The property parameter contains the name of the sql_variant property for which information is to be provided. It can take any one of the following values: BaseType, Precision, Scale, TotalBytes, Collation, and MaxLength. To demonstrate this, take a look at the code and output shown in Figure 1.
      The sql_variant data type becomes extremely useful in applications where this metadata information is managed by the app. An example of this would be an ASP application that collects information from a Web user in a form, especially when the form processing has been abstracted. Instead of saving the form value in the database as a character variable along with all the associated metadata information, all you need to do is save the value as a sql_variant. Just remember to cast the variable, before persisting it to the database:


Dim varName, varAge
varName = CStr(Request.Form("txtName"))
varAge = CInt(Request.Form("txtAge"))
      As the name implies, the new table data type is a local variable that can temporarily store a rowset. It is primarily used for temporary storage of a set of rows, which can also be returned as the result of a table-valued function. The table data type can be used in place of temporary tables stored in the tempdb database. Because the new table data type is managed in memory and is not persisted to disk, it provides performance benefits.
      Once you define a local table, you can use it in the same fashion as a tempdb table. It does not, however, support INSERT INTO� EXEC and SELECT�INTO statements. To define a local table you have to use the usual DECLARE statement with a subsequent table definition:


DECLARE @local_variable TABLE <table_defintion>
To demonstrate this, let's define and populate a local variable that contains all the rowset information about Northwind database territory data:


DECLARE @territory TABLE (territory_id INT, territory_desc NVARCHAR(50))
INSERT INTO @territory
SELECT TerritoryId, TerritoryDescription FROM Territories
While this example is not very exciting, it does show how easy it is to use the table data type. The table data type can be useful when parsing XML documents in cooperation with the new OpenXML function. This function, which I will discuss in detail later, allows you to receive an XML document and extract the information in rowset format. This rowset format can be a local table variable, which can be used for processing of the parsed data.
       Figure 2 shows an XML document. The stored procedure shown in Figure 3 will receive and parse this XML document, inserting the parsed elements into the appropriate Northwind region and territory tables. One point to note about this stored procedure is that the insert operations do not take into account the possibility that updates of existing data may be required. I will outline one solution to this problem when I discuss triggers later in this article.
      In addition to replacing the need to use tempdb tables, the table data type becomes extremely useful when used in conjunction with user-defined functions, as you will see in the next section.

User-defined Functions

      User-defined functions are similar to stored procedures and can be executed as such, or they can be executed in the same fashion as a system function. Like stored procedures, they accept zero or more input parameters, but can return a scalar data type such as int, decimal, varchar, or sql_variant in addition to the new table data type.
      The defining structure of a user-defined function is similar to that of a stored procedure. The main differences are the requirement to specify the RETURNS value and to terminate the function with the RETURN statement.
      A user-defined function that returns a table data type can declare an internal table variable and return that variable as its return value. These are known as rowset functions, and can be used where table or view expressions are allowed in Transact-SQL queries. This means a table returned by a user-defined function can be referenced in the from clause of a Transact-SQL statement, while stored procedures that return resultsets cannot.
      User-defined functions that return a table can be a powerful alternative to views. While views are limited to a single select statement, user-defined functions can contain additional statements that allow more powerful logic than is possible in views.
      For a simple example of returning a table with a user-defined function, consider the Northwind view for returning product sales for 1997. Figure 4 shows how you can easily extend this to return sales for a year specified as a function parameter.
      To use this function, you can select directly from the function as if it were a view. The only difference is that you can now specify the required year:


SELECT * FROM ProductSalesByYear(1996)
Extending this simple example, let's say you need to return product sales for the year 1997 along with the category information, but only for sales exceeding $15,000. You can easily achieve this using the user-defined function:


SELECT Product.ProductID, Product.ProductName, Product.ProductSales,
 Categories.*
FROM ProductSalesByYear(1997) AS Product
INNER JOIN Categories ON Product.CategoryID = Categories.CategoryID
WHERE Product.ProductSales > 15000
      If a user-defined function returns a scalar data type, all you have to do is define the return type and specify the value in the return statement. Figure 5 shows an example that returns an integer representing the number of orders received in a system for a specified date. You can then use this user-defined function in a simple select statement or set declaration:


DECLARE @count INT
SET @count = dbo.SalesForSpecifiedDate('12/12/97')
      As you saw in Figure 4, the return type is a local table variable. Defining the table as such allows you to use the local table variable for inserts and other operations prior to the return. There is, however, a special type of table that returns a user-defined function, called an in-line function. These have a return type of table but without the accompanying table definition. They return the resultset of a single select statement from which the table definition is derived.
      Consider the Northwind database stored procedure that returns the 10 most expensive products. The example written as an inline user-defined function looks like this:


CREATE FUNCTION dbo.TenMostExpensiveProductsTable ()
RETURNS TABLE
AS
RETURN    SELECT TOP 10
        ProductName AS TenMostExpensiveProducts,
        UnitPrice, SupplierID, UnitsInStock, UnitsOnOrder
        FROM Products
        ORDER BY Products.UnitPrice DESC
The advantage this has over the use of a stored procedure is the ability to use the function in query statements. If you wanted to query the 10 most expensive products along with the supplier information, you could now write a query such as:


SELECT Products.*, Suppliers.*
FROM TenMostExpensiveProductsTable() AS Products
INNER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID

Trigger Enhancements

      SQL Server has always supported triggers, a special class of stored procedures defined to execute automatically when an update, insert, or delete statement is issued against a table. This ability has been extended with the inclusion of an INSTEAD OF trigger, which also has the ability to be specified on views. An AFTER trigger is now the default trigger, synonymous with previous trigger definitions.
      An AFTER trigger, which you can define only on tables, is executed after the statement that triggered it completes. If the statement fails with an error such as a constraint violation or syntax error, the trigger is not executed. In addition to the ability to specify multiple AFTER triggers for each triggering action, you can also specify which triggers execute first and last using a new system stored procedure: sp_settriggerorder. All other AFTER triggers fire in an undefined order that you cannot control.
      An INSTEAD OF trigger executes in place of the triggering action. INSTEAD OF triggers can be specified on both tables and views, but you can only define one INSTEAD OF trigger for each triggering action. As well as providing the update capabilities to previously nonupdateable views, INSTEAD OF triggers can be used to perform before trigger logic. For example, consider the stored procedure defined in Figure 3. One of the limitations of this procedure is its inability to handle updates of existing entries. One solution to this problem would be to define an INSTEAD OF trigger on the tables that perform updates on the existing entries.
       Figure 6 shows a trigger definition for the region and territories tables. The previously defined stored procedure that takes an XML input stream and inserts records into the Northwind region and territory tables no longer needs to cater to the difference between updates and inserts.
      As I stated previously, INSTEAD OF triggers can also be placed on views, providing update capabilities to previously nonupdateable views. Consider a simple view on the employee table returning a concatenated version of the employee's first and last name:


CREATE VIEW dbo.EmployeeName AS
    SELECT EmployeeId, FirstName + ' ' + LastName AS EmployeeName
    FROM Employees
      This view could contain an INSTEAD OF trigger, allowing it to be used for updating employee names when entered as a single attribute. Figure 7 shows such a trigger definition that would allow for updates such as:


UPDATE EmployeeName
SET EmployeeName = 'Anne Dodsworth-Smith'
WHERE EmployeeId = 9
Without the trigger in place, an update on the employee name view would generate an error as an attempt to update using a derived attribute.

Cascading Referential Integrity Constraints

      Currently, SQL Server supports foreign key constraints to identify the relationships between tables and ensure referential integrity. This concept has been extended to support the ANSI definition of cascading delete and update operations. The current implementation is equivalent to the new NO ACTION constraint. This action prevents data on a referenced table from being deleted or updated if corresponding records exist on the referring table. It also prevents data on the referring table from being inserted or updated if no corresponding entry exists on the referenced table.
      This concept has now been extended to allow update and delete operations to be cascaded from the referenced table to the referred table. Consider the foreign key definitions in the Northwind database on the order detail table to the order and product tables:


ALTER TABLE dbo.[Order Details] ADD 
CONSTRAINT FK_Order_Details_Orders FOREIGN KEY 
    (OrderID) REFERENCES dbo.Orders(OrderID),
CONSTRAINT FK_Order_Details_Products FOREIGN KEY 
    (ProductID) REFERENCES dbo.Products (ProductID)
This definition prevents operations on both the order and product tables that will result in orphaned records on the order details table. Syntactically this definition is synonymous with specifying that delete and update operations should result in NO ACTION if the constraint is violated:


ALTER TABLE dbo.[Order Details] ADD 
CONSTRAINT FK_Order_Details_Orders FOREIGN KEY 
    (OrderID) REFERENCES dbo.Orders(OrderID)
    ON UPDATE NO ACTION
    ON DELETE NO ACTION,
CONSTRAINT FK_Order_Details_Products FOREIGN KEY 
    (ProductID) REFERENCES dbo.Products (ProductID)
    ON UPDATE NO ACTION
    ON DELETE NO ACTION
      This preventative operation may not have the effect you desire. If you change a product code it may be more desirable to cascade this new code to all the related order detail records. Similarly, if you deleted an order it may be more desirable to have the corresponding order details records purged. This can easily be achieved with the following definition:


ALTER TABLE dbo.[Order Details] ADD 
CONSTRAINT FK_Order_Details_Orders FOREIGN KEY 
    (OrderID) REFERENCES dbo.Orders(OrderID)
    ON UPDATE NO ACTION
    ON DELETE CASCADE,
CONSTRAINT FK_Order_Details_Products FOREIGN KEY 
    (ProductID) REFERENCES dbo.Products (ProductID)
    ON UPDATE CASCADE
    ON DELETE NO ACTION
      Currently such operations can be implemented only through triggers, and many databases have such implementations. However, the preferred mechanism is the new CASCADE operations; they are easier to implement and more efficient.

Indexed Views and other Index Enhancements

      A view can be thought of as either a virtual table or a stored query. The data accessible through a view is not stored; rather, the SELECT statement forming the view is persisted. A view forms a virtual table that can be referenced in the same manner as a table. Indexed views are an extension to this definition that greatly improves the performance of views of the type normally found in data warehouses or other decision support systems.
      Decision support queries can reference large numbers of rows in base tables, and aggregate large amounts of information into relatively concise aggregates such as sums or averages. The use of an indexed view will force the view's resultset to be stored in the database. This means that statements that reference the view will have substantially better response times. In using indexed views you should remember that modifications to the base data are automatically reflected in the view, which may cause additional maintenance overhead.
      Consider the Northwind database view for order subtotals. This view consists of a single GROUP BY statement summarizing the amounts for each order. To allow an indexed view to be created, the view must first be recreated with the schema binding option, and all table objects referenced using the two-part syntax for object owner. Finally, certain ANSI and connection options must be enabled prior to creating the view. Figure 8 shows an example of such a view creation statement.
      The SCHEMABINDING option prevents the base referenced tables from being changed without adjusting the view. You can also specify this option for user-defined functions. In the situation where schema binding is defined, all the appropriate views and user-defined functions would have to be dropped prior to making table schema changes.
      For views using the GROUP BY aggregation, the COUNT_ BIG(*) statement is mandatory. In addition, all grouping columns must appear in the view's select list, these being the only columns allowed in the creation of the unique clustered index. To define an index on this view, all you would have to do is execute the following create index statement:


CREATE UNIQUE CLUSTERED INDEX IDX_CV_ORDERSUBTOTAL_OI_ST
    ON OrderSubTotals (OrderID)
      Nonclustered indexes are also supported, but only on a view with a previously defined unique clustered index. Thus, the row locator of a nonclustered index on an indexed view is always the clustered index key.
      As an example of the performance benefits of indexed view, on my computer the execution of the following select statement averages 5ms of CPU time:


SELECT Orders.*, det.SubTotal, det.DetailCount
FROM Orders INNER JOIN OrderSubTotals det
ON Orders.OrderID = det.OrderID
After the creation of the indexed view this was reduced to zero. As the data for the indexed view is saved in the database, there is no requirement to perform any join or aggregation functions, both of which consume CPU resources.
      To gain the benefits of using indexed views, you do not necessarily have to use that view. The query optimizer will determine when a query will benefit from the use of the indexed view. Let's say you wrote the following query:


SELECT Orders.CustomerID, Orders.OrderID,
    SUM(CONVERT(MONEY,(UnitPrice*Quantity*(1-Discount)/100))*100)
    AS SubTotal
FROM Orders INNER JOIN [Order Details] OD
ON Orders.OrderID = OD.OrderID
GROUP BY Orders.CustomerID, Orders.OrderID
ORDER BY Orders.CustomerID, Orders.OrderID
If you were to view the query plan for this query, any access to the base order detail table would not be present. Rather, you would see a clustered index scan on the newly created indexed view.
      In addition to indexed views, there have been several other enhancements to views. You can now create indexes on computed and bit data type columns, specify whether indexes are built in either ascending or descending order, and specify whether the database engine should use parallel scanning and sorting during index creation.
      Indexes defined on computed columns are allowed, as long as the expression defined for the column only references columns from the table containing the computed column and is deterministic (calculated values do not change subsequent invocations). It is worth noting here that if one of the referenced columns is of the float or real data type, the computed value will be nondeterministic. I will say more about determinism later.
      Using indexes on computed columns becomes extremely useful when you are creating covering indexes. Consider the following sample query, where the extended price is a computed column defined as the product of order quantity and unit price:


SELECT Orders.OrderID, Orders.CustomerID, det.ProductID,
 det.ExtendedPrice
FROM Orders INNER JOIN [Order Details] det
ON Orders.OrderID = det.OrderID
WHERE det.ExtendedPrice > 2500
The creation of the following index will greatly improve the performance of this query. The query plan will no longer scan the orders detail table; rather, it will use the newly created index. Prior to creating the index on a computed column, you need to set the same ANSI and connection options used for creating indexed views:


CREATE UNIQUE NONCLUSTERED INDEX dbo.IDX_NCT_ORDERSDEATILS_OI_PI_EP
    ON [Order Details] (OrderID, ProductID, ExtendedPrice)
      Another useful extension to views is their ability to be ordered in ascending, descending, or mixed order. By default, all indexes are defined in ascending sort order. You should consider the following select statement:


SELECT  CustomerID, OrderID, OrderDate FROM Orders
WHERE OrderDate BETWEEN '01/01/1997' AND '01/31/1997'
ORDER BY CustomerID ASC, OrderId ASC, OrderDate DESC
The statement's query plan would consist of a clustered index scan followed by a sort operation. If you created a covering index with the appropriate sort order, the query plan would be reduced to a simple nonclustered index scan, a much faster operation:


CREATE NONCLUSTERED INDEX IDX_NCT_ORDERS_CI_OD
    ON Orders (CustomerID ASC, OrderDate DESC)
      If you did not create a covering index, the query plan may still reflect the original clustered index scan and sort operation. The other alternative would be a nonclustered index scan followed by the appropriate bookmark lookups. To force the use of the nonclustered index, an optimizer hint could be specified.

Distributed Partitioned Views

      I've already discussed the ability to create an index on a view, and the ability to define INSTEAD OF triggers. One interesting application of INSTEAD OF triggers would be the ability to provide update capabilities to UNION views. The trigger would only have to disperse the data to the appropriate tables in the union view.
      UNION views are commonplace in environments where horizontal data partitioning has taken place. SQL Server now supports updateable UNION views, even over remote linked servers known as distributed partitioned views. This feature enables federations of servers running SQL Server 2000 (a federated cluster) to cooperate to provide a single view of the partitioned data, providing almost unlimited scale-out performance. More information on federated clusters can be found on MSDN Online (see http://msdn.microsoft.com/msdn-online/start/features/highperform.asp).
      The process of defining distributed partitioned views incorporates the following steps:
  1. Create an image of the database on each of the participating servers.
  2. Define the member tables that you are horizontally partitioning, including the primary and check constraints.
  3. Define each member server as a linked server.
  4. Define the distributed partitioned views in all of the member databases.
      The views make the horizontal partition transparent to the application. The only difference in the table definitions among all these member servers will be the check constraint definition. These check constraints must be defined to ensure the union of the resulting domain is mutually exclusive and covers the entire range of possible data values, enabling the query engine to determine the location of the partitioned data.
      When you're defining the check constraints, there is one important item you should consider: the check constraint must be on a column in the primary key. For this reason, you can't use an identity column in the key. The best way to handle this is to centralize inserts via a stored procedure that would generate a unique key.
      To demonstrate, I will take the Northwind database and partition the customer, order, and order detail information into two separate partitioned databases: NorthwindOrd1 and NorthwindOrd2. The data partition I have defined is on the customer number, forcing the requirement to have the customer number as part of the primary key for both the order and order detail tables.
       Figure 9 outlines the table definitions for the first database partition. The second partition would be defined identically except for the check constraint, which would have be defined to cover the remainder of possible customer values:


CustomerID >= 'N'
      Once you have defined the member databases and tables, all you need to do is construct the UNION views incorporating the data from each member server using linked servers. The definition of the linked server would take place on each of the member servers, each definition being identical to the following sample:


EXEC sp_addlinkedserver 'NodeName'
EXEC sp_setnetname 'NodeName', 'ServerName'
EXEC sp_serveroption 'NodeName', 'lazy schema validation', 'true'
The lazy schema validation option optimizes performance by ensuring the query processor will not request meta data for any of the linked tables until data is actually needed from the remote member table.
      Finally, you have to define the UNION view for each of the partitioned tables, customers, orders, and order details in each of the member databases. Such a view definition for the customer table in the original Northwind database would be:


CREATE VIEW dbo.CustomersUnion
AS
    SELECT * FROM Node1.NorthwindOrd1.dbo.Customers
    UNION ALL
    SELECT * FROM Node2.NorthwindOrd2.dbo.Customers
      This view is now fully updateable, unlike the previous versions of SQL Server. Note that statements such as the following INSERT are fully valid:


INSERT INTO CustomersUnion
SELECT     CustomerID, CompanyName, ContactName, ContactTitle,
    Address, City, Region, PostalCode, Country, Phone, Fax
FROM Northwind.dbo.Customers
Regardless of the location of the UNION view, the data will be shipped to the appropriate location.
      In addition to providing updateable distributed partitioned views, the SQL Server query processor has been optimized for their performance. The most important aspect of their performance is minimizing the amount of data that has to be transferred between member servers. To accommodate this, intelligent, dynamic query plans are constructed to access data from the member tables, thanks to check constraints.
      The query processor compares the key ranges specified in a statement's WHERE clause to a map showing how the rows are distributed in the member tables. The query processor then builds a query execution plan that uses distributed queries to retrieve only those remote rows needed to complete the statement. The execution plan is also built in such a way that any access to remote member tables, for either data or metadata, is delayed until the information is required. For example, consider the following query:


SELECT * FROM CustomersUnion
WHERE CustomerID BETWEEN 'M' and 'O'
The execution plan for this query extracts the rows with customer key values starting with M from the first database and values between N and O from the second database.
      The query processor can also build dynamic logic into query execution plans for SQL statements where the key values are not known when the plan is built. Consider the inline user-defined function returning customer information for a parameterized key value:


CREATE FUNCTION dbo.GetCustomer (@CustomerID NCHAR(5))
RETURNS TABLE
AS
RETURN  SELECT * FROM CustomersUnion
        WHERE CustomerID = @CustomerID
      The query processor cannot predict what key value will be supplied by the parameter each time the procedure is executed. Since the key value cannot be predicted, the query processor also cannot predict which member table to access. To handle this case, the query processor builds an execution plan that has conditional logic to control which member table is accessed based on the input parameter value. The execution plan logic could be represented as:


IF @CustomerID  <  'N'
   Retrieve row from table NorthwindOrd1.dbo.Customers
ELSEIF @CustomerID >= 'N'
   Retrieve row from table NorthwindOrd2.dbo.Customers
      As I stated earlier, horizontally partitioning data in such a way provides you with almost unlimited scale-out scalability. To provide additional performance to a federated cluster, all you have to do is add additional member servers. The advantage of this approach is the ability to deploy an initial configuration of one or two servers. As the demand grows, additional member servers could be added to the federated cluster, and a repartitioning of the data across the member servers would be the only requirement to incorporate the new member servers.

Collation Support

      When installing an instance of SQL Server 7.0, you must specify a default code page and sort order. From that point onward all databases are locked into that particular code page and sort order. This concept has now been enhanced with the inclusion of collations. A collation is defined as a collection of three properties: the sort order for Unicode data types, the sort order for non-Unicode character data types, and the code page that is used to store non-Unicode character data types.
      SQL Server 2000 includes support for most collations that were supported in earlier versions of SQL Server, and introduces a new set of collations based on Windows collations. In addition, as an important enhancement, you can now specify collations at the database or column level, in addition to the default collation specified during installation.
      Support for collations at the database and column level greatly enhances the support for international applications. Consider an example where a database is created with the default collation of the serverâ€"the Latin character set, character-insensitive, and accent-sensitive (SQL_Latin1_General_CP1_CI_AS). If you required support for product names that would be unique regardless of the accent characters, you could define a table with the product key having the appropriate collation:


CREATE TABLE IntlProduct (
    ProdName    NVARCHAR(128) COLLATE SQL_Latin1_General_CP1_CI_AI,
    ProdDesc    NVARCHAR(256),
    ProdType    VARCHAR(8),
    LangCode    VARCHAR(16),
    CONSTRAINT pk_intl_product PRIMARY KEY (ProdName)
)
Without the specified collation at the product name level, the following two inserts would complete successfully. The collation forces the second insert to fail with a duplicate key violation:


INSERT INTO IntlProduct
VALUES ('Merlot Grape', 'Merlot Wine Grape', 'Fruit', 'French')
INSERT INTO IntlProduct
VALUES ('M�rlot Grape', 'M�rlot Wine Grape', 'Fruit', 'French')
      An alternative solution to the column-level collation is a database-level collation; you could define a separate database solely for product information. The difference in this scenario is that all columns in the database would be accent-insensitive:


CREATE DATABASE InternationalProducts 
 COLLATE SQL_Latin1_General_CP1_CI_AI
      In addition to support for collations at design time, SQL Server now supports specifying collations for individual statements. This allows you to create queries on tables supporting multilingual data specific to the language of the data. Consider this simple query for selecting the products from the previously defined table:


SELECT * FROM IntlProduct
ORDER BY ProdType, ProdName
By default the order of the data will be based on the collation sequence of the specified column. If no column collation is specified, the database collation is used. This means that the product type will be accent-sensitive and the product name will be accent-insensitive. If you wanted to override the default collation in the select statement, you can specify a new collation in the order statement or column list.
      If you needed to produce a regional query that returned all products for the French region, it would be wise to set the collation for the product name in the ORDER BY clause to a French collation:


SELECT * FROM IntlProduct
    WHERE LangCode = 'French'
    ORDER BY ProdType, ProdName COLLATE French_CI_AI
This statement-level collation would have limitations when defining views, as views cannot have ORDER BY clauses. Here is where a column-level collation specification would be required. As an example, the following lines create a view that uses column-level collations for returning product information for the French region in the local French collation and, hence, sort order.


CREATE VIEW IntlProductFrench
AS
SELECT ProdName COLLATE French_CI_AI AS ProdName, ProdDesc, ProdType
        FROM MyProducts
        WHERE LangCode = 'French'
WITH CHECK OPTION
      When selecting data using this view, you no longer have to be concerned with collations to ensure the data is returned using the appropriate sort order:


SELECT * FROM IntlProductFrench
    ORDER BY ProdType, ProdName

Extended Properties

      If you have ever used Microsoft Access you are familiar with the concept of attaching properties to database objects. SQL Server has adopted this concept and extended it to allow multiple user-defined extended properties to be attached to various objects within the database. These extended properties can be used to store application or site-specific information about database objects, helping enforce consistency in the way data is treated by all systems.
      An extended property is a name/value pair, the value being a sql_variant that can contain up to 7500 bytes of data. They are implemented using three new system stored procedures, one each for adding, updating, and dropping extended properties: sp_ setextendedproperty, sp_dropextendedproperty, and sp_updateextendedproperty. A new user-defined function, fn_listextendedproperty, retrieves the extended properties.
      As an example, consider the Northwind employees databases. You could set field masks and display names for certain key fields within the table as shown:


-- Set the caption for the table
EXEC sp_addextendedproperty 'Caption', 'Employee Details',
    'user', 'dbo', 'table', 'Employees'
-- Set the input mask for the postal code
EXEC sp_addextendedproperty 'Mask', '99999[-9999]',
    'user', 'dbo', 'table', 'Employees', 'column', 'PostalCode'
-- Set the input mask for the home phone
EXEC sp_addextendedproperty 'Mask', '(NNN)NNN-NNNN',
    'user', 'dbo', 'table', 'Employees', 'column', 'HomePhone'
Retrieving these extended property values is achieved using a SELECT of the rowset returning function fn_listextendedproperty. An application would only have to perform the following query to get all mask fields for all columns in the required table:


SELECT * FROM ::fn_listextendedproperty
(NULL, 'user', 'dbo', 'table', 'Employees', 'column', default)
Figure 10 shows the output from the previous query.
      Using extended properties is syntactically simple, but a brief explanation is warranted. For the purpose of extended properties, the objects within a database are classified into three levels. When defining extended properties on an object at one level, it must be fully qualified with the name of the object at the higher level. There are three defined levels, 0 being the highest level and 2 being the lowest. Figure 11 lists the level 0 objects, with their valid level 1 and level 2 objects.
      With this object level structure in place, the format of the stored procedures for extended properties should become clearer, their format being essentially the same:


sp_extendedproperty ['property_name', 'value']
[, 'level0_object_type', 'level0_object_name'
[, 'level1_object_type', 'level1_object_name'
[, 'level2_object_type', 'level2_object_name' ] ] ]
The structure of the listing function is also similar; the difference being the inclusion of the null or default keyword. If the value for the property name is null or default, the function returns all the properties for that object. When the object type is specified and the value of the object name is null or default, the function returns all extended properties for all objects of the specified type.

Built-in Functions and Determinism

      SQL Server 2000 introduces several new built-in functions. COUNT_BIG, ROWCOUNT_BIG, and SQL_VARIANT_PROPERTY are directly related to the introduction of the new data types and have already been discussed. The OpenXML function will be explained in greater detail in the following XML section. First, I'll describe the new Universal Time Coordinate date function, functions for identity columns, a session information function, and enhancements to the capabilities of the distributed query functions.
      SQL Server has always supported the GetDate function for returning the current date and time in the internal datetime format. This has now been extended with the GetUTCDate function, which returns the date and time in UTC format (Universal Time Coordinate or Greenwich Mean Time). This value is derived from the current local time and the time zone setting of the operating system. The GetUTCDate function operates in exactly the same manner as the GetDate function, and both are interchangeable:


DECLARE @utcdate DATETIME
SET @utcdate = GetUTCDate()
      This function can be quite helpful in multi-site applications. The current GetDate function is often used in recording the time of insert and update of row entries. In applications today, many of which use replication and span multiple time zones, this value loses its meaning unless the time zone in which the value was recorded is known. If you standardize on UTC time, the new GetUTCDate function eliminates this problem.
      Two new functions have been introduced to assist in using identity columns: SCOPE_IDENTITY and IDENT_CURRENT. These are additions to the current @@IDENTITY global variable. SCOPE_IDENTITY returns the last value inserted into an identity column within the same scope. The scope concept is important and is defined as a module of work contained within a stored procedure, trigger, function, or batch. The global variable @@IDENTITY acts like the SCOPE_IDENTITY function, but it is not limited to a specific scope.
      To demonstrate the concept of scope, look at the table definitions listed in Figure 12. One table holds a list of names and the other a list of corresponding last names, which is managed through a trigger operation.
      If you run the following INSERT statement, the outcome of the identity selections would be 1 for SCOPE_IDENTITY and 100 for @@IDENTITY. The SCOPE_IDENTITY scope is confined to the INSERT INTO the Full Names table and will not encompass the scope of the trigger operation. The @@IDENTITY has no concept of scope and will return the last Identity insert, in this case the trigger operation on the Last Names table:


INSERT INTO FullNames VALUES ('Carl Nolan')
SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY],
 @@IDENTITY AS [@@IDENTITY]
      Running the insert operations that follow, the return values for scope values would both equal 2. Only one insert operation takes place, the trigger process does not create an insert, and hence the identity functions will show the same values:


INSERT INTO FullNames VALUES ('Jared Nolan')
SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY],
 @@IDENTITY AS [@@IDENTITY]
The reason the trigger does not perform an insert operation for the second insert is that the trigger only inserts new last names, and both insert operations are for the last name of Nolan.
      The final identity function IDENT_CURRENT returns the last identity value generated for a specified table in any session and any scope. The function takes the table names for which you want the value of the last identity insert as its only parameter. As a simple example, the following operation would produce the last identity value for the Full Names and Last Names tables, these values being 2 and 100 respectively:


SELECT IDENT_CURRENT('FullNames') AS FullnamesIdent,
IDENT_CURRENT('LastNames') AS LastnamesIdent
      An interesting new concept introduced in SQL Server 2000 is that of session or connection information. Using a CONTEXT_ INFO variable you can associate up to 128 bytes of binary information with the current session or connection. This information can be referenced in multiple batches, stored procedures, triggers, or user-defined functions.
      You can retrieve this session information from a column in a master.dbo.sysprocesses table called context_info, whose process ID (spid) is that of the connection (@@SPID).


SELECT context_info
FROM master.dbo.sysprocesses
WHERE spid = @@SPID
      Although the session information can be referenced in user-defined functions, the set statement cannot occur in one. The advantage of this session information is the scope of the information. Local variables are limited in scope to the batch, trigger, stored procedure, or function in which they are defined. The session information can be used to hold user state or to persist information across batches. An example of this would be a stored procedure persisting information for access within a fired trigger.
      The key to using the session information is to understand how a binary data type can be used to persist other data types such as integer, unique identifier, character, and so on. Conversion to binary form will force right padding with hexadecimal zeroes. When converting back to the original data type, you will need to truncate these padding characters. You can perform this operation easily using the CAST and SUBSTRING functions.
      To persist a unique identifier variable you should do an explicit cast of the first 16 bytes of the session information as shown in the following statements:


-- Persists a unique identifier
DECLARE @var uniqueidentifier
SET @var = NEWID()
SET CONTEXT_INFO @var
GO

-- Select the context information
DECLARE @sess VARBINARY(128), @var uniqueidentifier
SET @sess = (SELECT context_info FROM master.dbo.sysprocesses
 WHERE spid = @@spid)
SET @var  = CAST(SUBSTRING(@sess,1,16) AS uniqueidentifier)
GO
This code works because a unique identifier is a 16-byte data type. To work with other data types you just need to know the length of the internal representation and modify the substring length parameter. For example, to persist an integer, the substring would take the first four binary bytes.
      If you are working with character data, you have to perform explicit conversion of the data into binary form; no implicit conversion will take place. When you're working with Unicode data you have to remember that two binary characters represent each character. The following statements show an example of persisting Unicode data:


-- Persists a unicode character string
DECLARE @var VARBINARY(128)
SET @var = CAST(N'Hello World' AS VARBINARY(128))
SET CONTEXT_INFO @var
GO

-- Select the context information
DECLARE @sess VARBINARY(128), @var NVARCHAR(64)
SET @sess = (SELECT context_info FROM master.dbo.sysprocesses
 WHERE spid = @@spid)
SET @var  = CAST(@sess AS NVARCHAR(64))
GO
      SQL Server 7.0 introduced the concept of linked servers, along with two new functions for handling distributed queries: OPENQUERY and OPENROWSET. OPENQUERY executes the specified pass-through query on the given linked server. OPENROWSET provides the same functionality, but it also includes all connection information necessary to access remote data. These functions are an alternative to accessing tables in a linked server, but only as a one-time, ad hoc method.
      SQL Server 2000 introduces a new function called OPENDATASOURCE. This provides ad hoc connection information as part of a four-part object name without using a linked server name. Like the OPENROWSET function, OPENDATASOURCE should only be used for infrequently accessed remote data. In addition, neither of these functions provides the functionality of linked server definitions, such as security management and the ability to query catalog information. The following two queries are equivalent, one using OPENDATASOURCE and the other using a linked server:


SELECT FirstName + ' ' + LastName AS FullName
FROM LocalNode.Northwind.dbo.Employees
WHERE LastName LIKE 'D%'

SELECT FirstName + ' ' + LastName AS FullName
FROM OPENDATASOURCE(
    'SQLOLEDB','Data Source=myserver;User ID=devuser;
     Password=devpassword'
    ).Northwind.dbo.Employees
WHERE LastName LIKE 'D%'
      A final word on determinism: until the advent of SQL Server 2000, the concept of function determinism had no significance. It is now important to understand the determinism of a function. All functions are either deterministic or nondeterministic. Deterministic functions always return the same result any time they are called with a specific set of input values.
      For example, the DATEADD function is deterministic because it always returns the same value for the given parameters. GETDATE is nondeterministic because the return values changes for subsequent invocations.
      It is important that you understand determinism for two reasons. First, an index cannot be created on a computed column if the column expression references nondeterministic functions. Second, a clustered index cannot be created on a view if the view references any nondeterministic functions.
      An important note here is that any function that operates on the real or float data type will be nondeterministic. This is a by-product of the fact these are approximate, not exact, data types. In addition, for a user-defined function to be deterministic, it cannot reference database objects outside the scope of the function. This includes database tables, but not local tables.

XML Support

      Probably one of the hottest topics in the whole IT industry is XML. It must come as no surprise that the SQL Server relational database engine natively supports XML. But what does XML support in SQL Server mean? Several new features have been introduced to provide XML support: the ability to access SQL Server over HTTP through a URL, support for XML-Data schemas and the ability to specify XPath queries against these schemas, and the ability to retrieve and write XML data.
      One important aspect of the XML support included with SQL Server is that it extends beyond the database engine. Enhancements for XML support have been made to the SQL Server OLE DB provider to allow XML documents to be set as command text and to return XML documents as a text stream. Both XPath queries and extensions to the SELECT statement are supported.
      As the basis for all of the following samples, I am going to use the following query:


SELECT oh.CustomerID, oh.OrderId, od.ProductID, od.UnitPrice,
 od.Quantity
FROM Orders oh INNER JOIN [Order Details] od
ON oh.orderid = od.orderid
WHERE oh.CustomerID = 'GROSR'
ORDER BY oh.CustomerID, oh.OrderId, od.ProductID
      When talking about XML documents received from SQL Server through either means, it is important that you understand that the resulting XML document is a document fragment. These document fragments can then be consumed by other documents to construct a well-formed XML document.
      The main focus of the XML discussions in this article will be SQL Server's ability to retrieve and write XML data. Retrieving XML data is supported via the FOR XML clause of the SELECT statement. Writing XML data is supported using the OpenXML rowset function.

XML Data Retrieval

      The results of SELECT statements can now be returned as XML document fragments through the use of a FOR XML clause. Different degrees of flexibility are available to define the serialization or shaping of the resulting relational data to an XML document fragment through three modes: RAW, AUTO, and EXPLICIT. The complete syntax specifying the XML mode in the FOR clause is:


FOR XML {mode} [,XMLDATA][, ELEMENTS][, BINARY Base64]
      RAW mode transforms each row in the SQL query into an XML element with the generic row identifier. Each column value is mapped to an attribute where the attribute name is the same as the column name. AUTO mode returns query results in a simple, nested XML tree. Each table in the from clause, listed in the SELECT statement, is represented as an XML element of the same name. The select columns are then mapped as attributes of the elements. The hierarchy is determined based on the order of the tables identified by the columns of the SELECT statement.
      To see what all this means take a look at the output in Figure 13. There are two XML document fragments based on the query previously mentioned. The first is for RAW mode and the second for AUTO mode.
      Before I discuss the final XML mode (EXPLICIT), the other optional parameters in the FOR XML clause need explanation. The XMLDATA option specifies that XML-Data schema information should also be returned. This schema is appended to the document as inline schema.
      Within AUTO mode you can also specify the ELEMENTS option. If this option is specified, columns are no longer returned as XML attributes, but rather as subelements. The final option, BINARY Base64, causes binary data such as the image and varbinary data types to be returned in base64 encoding.
      Using the previously outlined query and the following XML clause, the output of the schema and elements option can be seen in Figure 14. The elements option forces the resulting XML document to be element-centric rather than the attribute-centric.


FOR XML AUTO, XMLDATA, ELEMENTS
      The final XML mode is EXPLICIT. This is the most flexible XML mode and definitely the most verbose. The EXPLICIT mode specifies the shape of the XML tree. When you specify EXPLICIT mode, the responsibility for ensuring that the generated XML is well-formed falls into the hands of the creator.
      An EXPLICIT mode query produces a universal table containing all the information about the resulting XML tree. The table is vertically partitioned into groups, each of which becomes an XML element in the result. In addition, two metadata columns are held: Tag and Parent. The Tag column stores the tag number of the current element and the parent column stores the tag number of the parent. These metadata tags describe the parent/child hierarchy in the XML tree.
      In using EXPLICT mode the query must specify all information to produce the universal tree. This means that the query must, in addition to specifying the required data, specify all the metadata. The FOR XML EXPLICIT clause requires the first two columns be these required metadata tags. Finally, the query must specify the column names in this universal table.
      This may seem like a lot more work, but it does allow you to construct an XML document exactly as you want. Other important benefits of EXPLICIT mode are that columns can be individually mapped to either attributes or subelements, and you can generate sibling hierarchies.
       Figure 15 outlines how the previous query could be rewritten using EXPLICIT mode. This example maps the columns for the order information to attributes and the order details to subelements. This sort of structure is not available using the other modes, where you are confined to either attribute or element-centric mappings. The resulting output is shown in Figure 16.
      To get a better understanding of the concept of a universal table, you can run the previous query without the FOR XML EXPLICIT clause. The output will be the universal table. To get more information on the EXPLICIT mode take a look at: http://www.asptoday.com/articles/20000322.htm.
      One of the main tasks in using EXPLICT mode is ensuring that the Tag and Parent properties are set correctly. In this case a UNION ALL statement is used to correctly return the required Order and then Order Detail information. The final ORDER BY is important to correctly relate the order details with the appropriate order information.
      The specified column names are XML generic identifiers and attribute names. Their structure consists of four parts. The first three are the resulting generic identifier for the element, the elements tag number, and the XML attribute name. The fourth optional part is a list of directives that allow you to perform certain XML-specific transformations such as generating a subelement instead of an attribute, generating an XML CDATA section, and so on.

Writing XML Data

      In the New Data Types section of this article I introduced a new rowset function, OpenXML, that was used to expose the data from the XML document as a relational rowset. As OpenXML is a rowset function, it can be used as a table reference, allowing you to use the data in XML documents for inserts or updates into database tables.
      To use the OpenXML function you must first create an internal representation of the XML document using the sp_xml_preparedocument system stored procedure. As good practice, once the internal document has been used, it should be removed from memory using the sp_xml_removedocument system stored procedure. This internal document is a tree representation of the various nodes of the XML document, returned in the form of an integer handle.
      The basic structure of the OpenXML rowset function is:


OPENXML(idoc int [in], rowpattern nvarchar[in], [flags byte[in]])
[WITH (SchemaDeclaration | TableName)]
The document handle (idoc) and XPath pattern (rowpattern) are the only required parameters. The XPath row pattern is used to define the nodes of the XML documents to process. The flag parameter indicates the mapping that should be used between the XML data and the relational rowset. By default, XML attributes are usedâ€"a flag value of zero is the default. Other values are 1 for attribute-centric mapping and 2 for element-centric mapping.
      The basic format of schema declarations is similar to table declarations. The main inclusion would be an optional XPath column pattern as the previous example illustrated. If you use the column patterns in the schema declaration, the specified mappings override the flags parameter.
      To see some examples of OpenXML, review Figure 2 along with the corresponding stored procedure in Figure 3. The stored procedure has a local variable @idoc to hold the internal handle of the XML document:


DECLARE @idoc INT
exec sp_xml_preparedocument @idoc OUTPUT, @xmldoc
      In its simplest form, the OpenXML statement used to obtain just the region information would be as follows. (This is based on an attribute-centric mapping, as the region elements do not have any subelements.)