You are on page 1of 44

Interactive SQL Server & XML Online Tutorial

About
Written by Scott Klein, published at TopXML

What is this PDF?


This PDF is supplied to our customers for printing purposes. This PDF does not contain live
links, nor does it have many of the other advantages of the live online tutorial.
If you purchase this online tutorial you will get the source code, all accompanying material, the
interactive exam and this PDF. We encourage you to go to http://www.topxml.com/tutorials to
make your purchase, if you havent yet! If you already have made your purchase, then thank
you!

Introduction
With the release of SQL Server 2000, a lot more support and functionality for XML was included in the
product. Some of this functionality includes accessing SQL Server using HTTP and new OLAP
services (now called Analysis Services). But the majority of the new functionality focuses on
supporting XML. This new functionality includes the ability to read and write XML data, support for
XDR schemas, and XPath
This tutorial will show you the different methods of retrieving XML from SQL Server, how to control
how the data is returned, and then discuss how to navigate through the returned results. You will also
learn ways of viewing SQL Server tables from an XML perspective. From there youll learn how to
load data into SQL Server using XML natively, including real-time over HTTP.
Most of the topics covered today are straight out of the box functionality. However, a couple of things
are not. Functionality such as Updategrams and XML Bulk Load require an extra installation. This
file, XML for SQL Server Web Release 1, is included with this tutorial (or it can also be downloaded
from the Microsoft website). Later on in the tutorial when we discuss Updategrams and Bulk Load we
will also walk through the installation of this file.

For XML
New with SQL Server 2000 is the ability to return the results of a query in XML format. This is
accomplished by adding the FOR XML clause at the end of the SELECT statement. It is not difficult to
use, and the syntax is as follows:
FOR XML mode [, XMLDATA] [, ELEMENTS] [, BINARY BASE 64]

The arguments in brackets are optional, but definitions of all the arguments are:

mode this is the only required argument. It specifies how the XML will be returned in the result
set. There are 3 values that can be used:
o AUTO returns query results as nested XML elements.
o EXPLICIT you specify the format of the results.
o RAW each row is returned as an XML element
XMLDATA when specified, the schema is returned along with the results.
ELEMENTS this argument can only be used in conjunction when mode is AUTO. The query
results are returned as XML sub-elements.
BINARY BASE64 if the resultset being returned will contain binary data then this attribute
specifies what form the results will look like. The binary data is returned as BASE 64.

Before we get to an example, open up the Query Analyzer for SQL Server 2000 and select Options
from the Tools menu. When the Options dialog opens, select the Results tab. You will want to make
two changes here. First, change the Default results target to Results to Text. Second, it would be a
good idea to increase the Maximum characters per column to something larger than the default. You
dont want to make it too large because then you will have to scroll too far. A good place to start is
2048.
The reason you may want to make these changes is because it makes the query results easier to read.
Click OK to close the Options dialog.
Since the RAW mode is the most basic and straight forward to understand, that one will be discussed
first.

RAW
Look at the following statement executed in the Query Analyzer:
SELECT * FROM Class FOR XML RAW

The results you should get are not as nicely formatted as you would expect. But with a bit of manual
formatting it should look this:
XML_F52E2B61-18A1-11d1-B105-00805F49916B
--------------------------------------------------------------<row ClassID="1" Class="125"/>
<row ClassID="2" Class="250"/>
(4 row(s) affected)

The first thing you will notice is the column name. This is a GUID that is just a placeholder for the
name of the column in the rowset. All results returned using FOR XML will have a GUID prefixed by
XML_. Not really important in understanding FOR XML so the rest of the examples shown in this
tutorial will not include it.
The results, however, are returned as a single XML document which contain, either as elements or
attributes, the names of the columns. In the example above the column names are returned as attributes.
When using FOR XML RAW, all the columns in each row of the result set are returned as attributes,
with each row of the row of the result set beginning with the element row as seen in the example above.
The only time a column is not returned as an attribute is when a column has a NULL value.

For example, look at the following statement and results:


SELECT RiderName, ClothingSponsor FROM Rider WHERE RiderName LIKE D% FOR
XML RAW.

After applying our formatting it should look something like this:


<row RiderName="Damon Bradshaw" Clothing Sponsor=AXO/>
<row RiderName="David Vuilliman"/>

Youll notice that the second row only has a RiderName and does not have a ClothingSponsor,
therefore the ClothingSponsor is not included as an attribute. The first row however has both a
RiderName and a ClothingSponsor and both are included as attributes.

XMLDATA
All examples throughout this tutorial will use the database that accompanied this tutorial. In some
examples we will use the Northwind database in which case it will state to use that database.
Type the following into the Query Analyzer and run it.
SELECT * FROM Rider FOR XML RAW, XMLDATA

After formatting your results should look like this:


<Schema name="Schema1" xmlns="urn:schemas-microsoft-com:xml-data"
xmlns:dt="urn:schemas-microsoft-com:datatypes">
<ElementType name="row" content="empty" model="closed">
<AttributeType name="RiderID" dt:type="i4"/>
<AttributeType name="TeamID" dt:type="i4"/>
<AttributeType name="RiderName" dt:type="string"/>
<AttributeType name="ClothingSponsor" dt:type="string"/>
<attribute type="RiderID"/>
<attribute type="TeamID"/>
<attribute type="RiderName"/>
<attribute type="ClothingSponsor"/>
</ElementType>
</Schema>
<row xmlns="x-schema:#Schema1" RiderID="1" TeamID="1" RiderName="Damon
Bradshaw" ClothingSponsor="AXO"/>
<row xmlns="x-schema:#Schema1" RiderID="2" TeamID="1" RiderName="Jeremy
McGrath"/>
<row xmlns="x-schema:#Schema1" RiderID="3" TeamID="1" RiderName="David
Vuillemin"/>
<row xmlns="x-schema:#Schema1" RiderID="4" TeamID="2" RiderName="Ricky
Carmichael/>
<row xmlns="x-schema:#Schema1" RiderID="5" TeamID="2" RiderName="Sebastien
Tortelli/>
<row xmlns="x-schema:#Schema1" RiderID="6" TeamID="3" RiderName="Ezra Lusk
/>
<row xmlns="x-schema:#Schema1" RiderID="7" TeamID="3"/>
<row xmlns="x-schema:#Schema1" RiderID="8" TeamID="4" RiderName="Travis
Pastrana"/>
<row xmlns="x-schema:#Schema1" RiderID="9" TeamID="4" RiderName="Kevin
Windham"/>

The first thing you will see is that the Data schema has been included in our result set and placed at the
top of the actual row results. Using XMLData comes in handy because it provides a definition of the
data in case you ever want to validate it.
The name of the schema is Schema3. If we were to run the query again it would become Schema4.

BINARY BASE64
In this example well use the Categories table from the Northwind database. Type the following in the
Query Analyzer and execute it:
SELECT * FROM Categories FOR XML RAW

Didnt work did it? It shouldnt have. You should receive the following error:
FOR XML EXPLICIT and RAW modes currently do not support addressing binary
data as URLs in column Picture. Remove the column, or use the BINARY BASE64
mode, or create the URL directly using the
dbobject/TABLE[@PK1="V1"]/@COLUMN syntax.

This error is generated because the RAW or AUTO modes dont quite know how to handle data in
Binary format. We can do a couple of things to get to the binary data.
When a table has binary data, such as a picture or binary field, we can retrieve that data by
specifying BINARY BASE64. Modify your query to look like the following and execute it:
SELECT * FROM Categories FOR XML RAW, BINARY BASE64

Now the results should look like this:


<row CategoryID="1" CategoryName="Beverages" Description="Soft drinks,
coffees, teas, beers, and ales" Picture="FRwv.HrQX+"/>
<row CategoryID="2" CategoryName="Condiments" Description="Sweet and savory
sauces, relishes, spreads, and seasonings" Picture="FRwv.DgrQX+"/>
<row CategoryID="3" CategoryName="Confections" Description="Desserts,
candies, and sweet breads" Picture="FRwv....ADhrQX+"/>
<row CategoryID="4" CategoryName="Dairy Products" Description="Cheeses"
Picture="FRwv.gSW1+"/>

A lot of the base64 data has been removed for readability but your results should show a whole lot of it.
In this example we can at least get to the data but it is not very readable. What would be even better
would be to generate a URL that points to the binary data. If you remember, the error message we
received earlier gave a clue how to do that. By using the following query the
SELECT TOP 1 EmployeeID,
'dbobject/Employees[@EmployeeID='+CAST(EmployeeID as
nvarchar(4000))+']/@Photo' Photo FROM Employees FOR XML RAW

The results should look like this:


<row EmployeeID="3" Photo="dbobject/Employees[@EmployeeID=3]/@Photo"/>

To make this useful requires that a virtual directory be set up in IIS to access the SQL Server
table. We will talk about how to do that later on. Once this is set up we can come back and run
this query and view the results.

Worksheet 1
This first one is going to be fairly simple. First, look at what the Rider table looks like normally. Execute
the following query:
SELECT * FROM Rider

What do the results look like?


Now we want to use what we learned above and return the records as a single XML document with the
column data returned as Elements and Attributes.

Next, return the XML document with the Schema returned with the data.
Answers
SELECT * FROM Rider FOR XML RAW
XML_F52E2B61-18A1-11d1-B105-00805F49916B
--------------------------------------------------------------<row RiderID="1" TeamID="1" ClassID="2" RiderName="Damon Bradshaw"
ClothingSponsor="AXO"/>
<row RiderID="2" TeamID="1" ClassID="2" RiderName="Jeremy McGrath"
ClothingSponsor="Thor"/>
<row RiderID="3" TeamID="1" ClassID="2" RiderName="David Vuillemin"/>
:
:
SELECT * FROM Rider FOR XML RAW, XMLDATA
XML_F52E2B61-18A1-11d1-B105-00805F49916B
--------------------------------------------------------------<Schema name="Schema1" xmlns="urn:schemas-microsoft-com:xml-data"
xmlns:dt="urn:schemas-microsoft-com:datatypes">
<ElementType name="row" content="empty" model="closed">
<AttributeType name="RiderID" dt:type="i4"/>
<AttributeType name="TeamID" dt:type="i4"/>
<AttributeType name="ClassID" dt:type="i4"/>
<AttributeType name="RiderName" dt:type="string"/>
<AttributeType name="ClothingSponsor" dt:type="string"/>
<attribute type="RiderID"/>
<attribute type="TeamID"/>
<attribute type="ClassID"/>
<attribute type="RiderName"/>

<attribute type="ClothingSponsor"/>
</ElementType>
</Schema>
<row xmlns="x-schema:#Schema1" RiderID="1" TeamID="1" ClassID="2"
RiderName="Damon Bradshaw" ClothingSponsor="AXO"/>
<row xmlns="x-schema:#Schema1" RiderID="2" TeamID="1" ClassID="2"
RiderName="Jeremy McGrath" ClothingSponsor="Thor"/>

AUTO
The big difference between Raw and Auto is that AUTO mode returns query results as nested XML
elements. For example, execute the following query in Query Analyzer:
SELECT team.Sponsor, rider.RiderName
FROM Team, Rider
WHERE team.TeamID = rider.TeamID
FOR XML RAW

Your results should look like this:


<row
<row
<row
<row

<row Sponsor="Yamaha" RiderName="Damon Bradshaw"/>


Sponsor="Yamaha" RiderName="David Vuillemin"/>
Sponsor="Honda" RiderName="Ricky Carmichael"/>
Sponsor="Honda" RiderName="Sebastian Tortelli"/>
Sponsor="Kawasaki" RiderName="Ezra Lusk"/>

These results are not returned in a nested format. However by using the Auto mode the results should
look like we want them to. So, change the FOR XML clause to look like this:
FOR XML AUTO

Now the results will look like the following:


<Team sponsor="Yamaha">
<Rider ridername="Damon Bradshaw"/>
<Rider ridername="David Vuillemin"/>
<Rider ridername="Jeremy McGrath"/>
</employees>
<Team sponsor="Honda">
<Rider ridername="Ricky Carmichael"/>
<Rider ridername="Sebastian Tortelli"/>
<Rider ridername="Bob Hope"/>
</employees>

The differences being that the results are returned in a hierarchal order with the column values as
attributes and the table names as elements. This is much nicer and easier to read.
If you remember the syntax from earlier, we also have the option of adding the Elements parameter to
this query. By adding the Elements to the end of the query string like this:
FOR XML AUTO, ELEMENTS

Our results now look like this:


<Team>
<Sponsor>Yamaha</Sponsor>
<Rider>
<RiderName>Damon Bradshaw</RiderName>
</Rider>
<Rider>
<RiderName>Jeremy McGrath</RiderName>
</Rider>
<Rider>
<RiderName>David Vuillemin</RiderName>
</Rider>
</Team>
<Team>
<Sponsor>Honda</Sponsor>
<Rider>
<RiderName>Ricky Carmichael</RiderName>
</Rider>
<Rider>
<RiderName>Sebastien Tortelli</RiderName>
</Rider>
</Team>

This causes the values of the columns to be displayed has elements instead of attributes.
XMLDATA and BINARY BASE64

All the information that was covered for XMLDATA and BINARY BASE64 using the RAW mode
will work for AUTO mode. Using the above example, our XMLDATA query would look like this:
SELECT team.Sponsor, rider.RiderName
FROM Team, Rider
WHERE team.TeamID = rider.TeamID
FOR XML AUTO, ELEMENTS, XMLDATA

This will return the same results as the example above but also pre-append the schema to the results.
For BINARY BASE64 the AUTO mode actually does all the work for you. Execute the following
query in the Query Analyzer (again using the Northwind database):
SELECT CategoryID, CateogryName, Picture
FROM Categories
FOR XML AUTO, ELEMENTS

Notice that binary results are already in the URL format. There are a couple of things that should be
noted here. First, the column that contains the binary image should not be referenced with an alias in
the query string.
Second, make sure that the table that contains the binary column has a primary key column. This also
applies to views as well because views technically dont keep the primary key from the table or tables.
That means that even though a view may exist that selects a primary key, if a SELECT query is run
against that view using the FOR XML AUTO clause, it will generate an error.

Restrictions
When working with AUTO mode, there are a few restrictions that need to be mentioned here. The first
is that GROUP BY and other aggregate function dont work when used with AUTO mode. This is
because aggregate functions return a single value.
The second deals with computed columns. When using computed columns, each computed column
needs to have an associated name with it. For example:
SELECT RiderName + + ClothingSponsor
FROM Rider
FOR XML AUTO

This should generate an error asking you to name your unnamed column. Therefore the query needs to
be changed as follows:
SELECT RiderName + + ClothingSponsor As RiderInfo
FROM Rider
FOR XML AUTO

The results will look like this:


XML_F52E2B61-18A1-11d1-B105-00805F49916B
--------------------------------------------------------------<Rider RiderInfo="Damon Bradshaw AXO"/>
<Rider RiderInfo="Jeremy McGrath Thor"/>
:
:
:

In the above scenario where we are concatenating two fields make sure that there is data in both fields
because if either one of the fields is empty (NULL) then results for either column wont be returned.
For example, type the following query into the Query Analyzer:
SELECT RiderName + + ClothingSponsor As RiderInfo
FROM Rider
WHERE RiderID = 6
FOR XML AUTO

Even though there is a RiderName (in the database) there is no corresponding ClothingSponsor so the
results returned are as follows:
XML_F52E2B61-18A1-11d1-B105-00805F49916B
-------------------------------------------------------------<Rider/>

The last issue deals with encoding. In the query analyzer, type and execute the following query:
SELECT * FROM Encoding WHERE EncodingID = 1

The results look pretty standard. Now add FOR XML AUTO to the end of the query and execute it.
The results should look like this:

<encoding EncodingID="1" EncodingName="8 &gt; 7" EncodingDescription="me


myself &amp; I"
EncodingImage="dbobject/encoding[@EncodingID=1]/@EncodingImage"/>

Whats with all the funky characters in our results? This is the way XML and URLs handle special
characters. If the attributes or elements contain &, , <, >, or then they will be converted to use XML
encoding (*amp; , &apos; , &lt; , &gt; , &quot;) respectively.

Worksheet 2
For this worksheet we are going to use 2 tables and have our records returned in a nested XML
document.
Using the Team and Rider tables, write a statement that returns all the riders nested neatly with their
corresponding teams as attributes.
Once youve done that, modify your query to return the riders as elements instead of attributes.
What do these queries and results look like?
If youre feeling really gutsy, add the Class table and do it again.

Answers
SELECT team.Sponsor, rider.RiderName
FROM Team, Rider
WHERE team.TeamID = rider.TeamID
FOR XML AUTO

Ok, this was easy because we used it in the tutorial.


SELECT team.Sponsor, rider.RiderName
FROM Team, Rider
WHERE team.TeamID = rider.TeamID
FOR XML AUTO, ELEMENTS

Yeah, we covered this too, so it was easy. But how about the last one? Can you do that?

EXPLICIT
With AUTO and RAW modes, while fairly easy to use, the user had no real control over the form of the
XML results returned by the query. With the EXPLICIT mode the user has the ability to specify how
the results will look and offers much more flexibility over the AUTO and RAW modes. There is some
responsibility that the user needs to assume when using EXPLICIT mode by ensuring that the XML is
well formed when generated.

FOR XML EXPLICT mode works by transforming the results from a recordset into an XML
document. This XML document has a format that is determined but the way the SELECT query is
written. This is where all the work comes in. The user must determine what their results need to look
like and then write the SELECT query to return those results.

The query needs to written so that a recordset produces something called a Universal Table in which the
XML document is generated. This table is not an actual SQL table but an In-Memory table whose sole
purpose is to hold the XML document. The query using the EXPLICIT mode requires two columns
of meta data. The first column in the query needs to be a named tag (Tag) number, of an integer type
and has the responsibility of storing the tag number of the current element. The second column needs
to be a named (Parent) tag number. This column lists the tag number of the parent element and is also
an integer type.
Huh? Exactly. A bit confusing isnt it? Here is an example to clarify things. The following example
lists all the orders (no order detail) for every sales person. The two table names could be Employee and
Orders, for example. In Universal Table format the layout would look like this:

Tag

Parent

Sales Person

1
2
2
2
1
2
2

NULL
1
1
1
NULL
1
1

Joe Bob

Order Date
6/15/97
7/21/98
8/20/98

Sally Joe
5/10/94
5/10/94

The Tag column is required to be the first column in a FOR XML EXPLICIT query. It is metadata
only and does not correspond to a physical database column. It is for level definition only, meaning
that it defines what level in the hierarchy tree this record is located.
The Parent column is required to be the second column in a FOR XML Explicit query. It also
does not correspond to a physical database column. This column represents the tag number of an
elements parent. Every element has a Parent, even if the value of a Parent is NULL. This is possible
because the top level of a hierarchy has no Parent. In the above example there are two records that have
no Parent because they are at the top of the hierarchy level.
The rest of the columns DO correspond to a physical database column. It contains the column value
from the database value.
The general format for representing this information looks like this:
ElementName!TagNumber!AttributeName!Directive

For every FOR XML EXPLICIT query, this alias is required for each column in the SELECT
statement.
In our example above, the Tag 1 is the primary table in this example so its tag number is 1. The parent
column is NULL because it is the primary table. The next three rows have a tag number of 2 and a
parent column value of 2 because it is the secondary table and is linked to the primary table. It is then
repeated for the next sales person and so on.
Lets modify our first example and add a third table. The layout would change a bit:

Tag

Parent

Sales Person

1
2
3
3
2
3
3

NULL
1
2
2
1
2
2

Joe Bob

Order Date

Order Detail

6/15/97
Hammers
Nails
7/21/98
Duck Tape
Glue

This example is not that different from the first. A third table was added (OrderDetail) and the Parent
of those rows is Tag 2 (rows 3, 4 and 6, 7 respectively)
It is time to deal with actual data and real examples. In Query Analyzer type and execute the following
query:
SELECT

123
AS Tag,
0
As Parent,
RiderName
As [RiderTable!123!RiderName]
FROM
Rider
FOR XML EXPLICIT

The results from this query will look as follows:


<RiderTable
<RiderTable
<RiderTable
<RiderTable
<RiderTable
<RiderTable
<RiderTable
<RiderTable
<RiderTable
:
:

RiderName=Damon Bradshaw>
RiderName=Jeff Emig>
RiderName=Jeremy McGrath>
RiderName= Ricky Carmichael>
RiderName=Travis Pastrana>
RiderName=Kevin Windham>
RiderName=Ezra Lusk>
RiderName=John Dowd>
RiderName= David Vuillemin>

Although this example is only one table and fairly simple, it should start to give you an idea of how it
works and basic requirements. Lets throw another table into the mix. Change the query to look like
this:
SELECT 1

as tag,
0 as parent,
sponsor as [TeamTable!1!Sponsor],
NULL as [RiderTable!2!RiderName]
FROM Team
Union All
SELECT 2,
1,
Sponsor,
RiderName

FROM Team, Rider


WHERE Team.TeamID = Rider.TeamID
ORDER BY [TeamTable!1!Sponsor],
[RiderTable!2!RiderName]
FOR XML EXPLICIT

Internally, this will generate a Universal Table like the one used in the first example above. It would
look like this:

Tag

Parent

TeamID

RiderName

1
2
2
2
1
2

NULL
1
1
1
2
2

1
1
1
1
2
2

Damon Bradshaw
Jeremy McGrath
David Vuillemin
Ricky Carmichael
Sebastien Tortelli

We could go even deeper and add a third table (the Class) table and break it down even further but the
concept should be clear as to what is being accomplished.
Going back to the alias format you will notice that there is an optional part that we have not been using
in any of our queries, and that is the Directive. The Directive attribute determines how string data is
handled in XML.
There are 5 options for the Directive attribute and they are:

cdata this causes the column data to be placed in a CDATA section of an XML
document. This data must be a ntext, nvarchar, text, or varchar datatype. If this directive is
used then the attribute name cannot be used (i.e., [RiderTable!1!Sponsor!cdata] is not
valid. [RiderTable!1!!cdata] is valid).
element causes the column data to be specified as an element instead of an attribute.
xml identical to the element directive except that the data is not encoded whereas the data
is encoded when the element directive is used.
hide any column that has the hide directive is not included in the XML document.
xmltext handles overflow. Used in conjunction with OPENXNL, when there are more
elements or attributes than table columns, these extra elements and attributes are sent into a
column (created by OPENXML) to deal with the overflow of data. The xmltext directive
fetches this extra data from this column.

Lets take a minute and do a couple of examples using these directives. The first one well do is the
hide directive. Using the earlier example lets make the following changes:
SELECT 1
as tag,
as parent,
Team.TeamID as [TeamTable!1!TeamID!hide],
sponsor as [TeamTable!1!Sponsor],
NULL as [RiderTable!2!RiderName]
FROM Team

Union All
SELECT 2,
1,
Rider.TeamID,
Sponsor,
RiderName
FROM Team, Rider
WHERE Team.TeamID = Rider.TeamID
ORDER BY [TeamTable!1!TeamID!hide],
[RiderTable!2!RiderName]
FOR XML EXPLICIT

Even though the TeamID column is included in the SELECT statement it wont show up in the results
because of the hide directive added.
The next example well look at uses the element directive. Using our very first example, lets modify it
to look like the following:
SELECT

123
AS Tag,
0
As Parent,
RiderName
As [RiderTable!123!RiderName!element]
FROM
Rider
FOR XML EXPLICIT

This causes our column data to be listed inside an element rather than an attribute, shown as follows:
<RiderTable>
<RiderName>Damon Bradshaw</RiderName>
</RiderTable>
<RiderTable>
<RiderName>Jeremy McGrath</RiderName>
</RiderTable>

The import thing to remember with all of this is that you can have complete control over how your
results are returned to you. The power and flexibility provided with the FOR XML can be
overwhelming but dont let it be intimidating. With this information you can be doing FOR XML in no
time at all.

FOR XML and ADO


Lets take a look how to do this with Visual Basic and ADO. The Visual Basic example included with
this tutorial includes an example on how to execute a FOR XML query via ADO.
Unlike regular SQL queries a FOR XML cannot be directly executed via ADO. For example, this will
not work:
rs.open SELECT * FROM Users FOR XML

The best way to do this is to use the Steam object that contains the XML. The SQL statement also
needs to be in a well formatted XML document. The code snippet below demonstrates how this done.

strText = <ROOT xmlns:sql=urn:schema-microsoft-com:xml-sql>


strText = strText & <sql:query>
strText = strText & SELECT * FROM Users FOR XML AUTO
strText = strText & </sql:query>
strText = strText & </ROOT>
:
StreamIn.WriteText
:
Set cmd.CommandStream = streamIn
:
cmd.Properties(Output Stream) = Streamout
:
cmd.Execute , , adExecuteStream
:
StreamOut.ReadText(adReadAll)

First we assign the XML document to a variable. The next step is to open the Stream and write the
XML to the input stream. The CommandStream method on the Command object is then used and set
equal to the stream object.
Obviously there is more code to this but the complete code is shown in the included Visual Basic
project.
Get the Visual Basic project source code
The intent here was to demonstrate how to use the Steam object to execute a FOR XML statement.
There will be situations where putting the SQL Statement in the Visual Basic code doesnt make sense.
A solution to that would be to create a stored procedure which contains the SQL statement and to call
the stored procedure from the Visual Basic code.
My stored procedure would look like this:
CREATE PROCEDURE GetRidersRaw AS
SELECT <ROOT xmlns:sql=urn:schemas-microsoft-com:xml-sql>
SELECT * FROM Riders FOR XML RAW </ROOT>

Now a few changes need to be made to the VB code since Im not hard coding the SQL statement
directly in my VB code. First, I need to set a couple of properties on the Command object like this:
cmd.CommandText = GetRidersAuto
cmd.CommandType = adCmdStoredProc

Next, Ill need to change my Execute command. It should look like this:
cmd.Execute , , adExecuteStream + adCmdStoredProc

Everything else should basically stay the same. The full functional code for this is included in the VB
project.

Worksheet 3

In this worksheet the goal is to create a Universal table and resulting XML document that lists all the
riders in their corresponding class (125cc or 250cc).
Once youve got that done, modify it to include the Clothingsponsor from the rider table, but we dont
want to display it in our results.
Lastly, display the RiderName as an element in the resulting XML document instead of as attributes.

Answers
SELECT 1

as tag,
0 as parent,
Class
as [ClassTable!1!Class],
NULL as [RiderTable!2!RiderName]
FROM Class
Union All
SELECT 2,
1,
Class,
RiderName
FROM Class, Rider
WHERE Class.ClassID = Rider.ClassID
ORDER BY [ClassTable!1!Class],
[RiderTable!2!RiderName]
FOR XML EXPLICIT

SELECT 1
as tag,
as parent,
Class.ClassID as [ClassTable!1!ClassID!hide],
Class
as [ClassTable!1!Class],
NULL as [RiderTable!2!RiderName]
FROM Class
Union All
SELECT 2,
1,
Class.ClassID
Class,
RiderName
FROM Class, Rider
WHERE Class.ClassID = Rider.ClassID
ORDER BY [ClassTable!1!Class],
[RiderTable!2!RiderName]
FOR XML EXPLICIT

OPENXML
Up to this point we have been dealing with how to get data from SQL Server and display it in an XML
format. What about if we want to get data from an XML document into SQL Server? This is possible
using OPENXML.

The syntax for OPENXML is like this:


OPENXML(iDoc, RowPattern, [Flags],
[WITH (SchemaDeclaration | TableName)]

Lets talk about those parameters.

iDoc . We get this by calling a stored procedure called sp_xml_preparedocument. Well talk
more about this stored procedure in a moment.
The RowPattern parameter specified which nodes we want OPENXML to process using XPath.
The Flags parameter specifies the format of our results. The following values can be used:
o 0 Default value. Attribute centric mapping.
o 1 Use Attribute centric mapping.
o 2 Use element centric mapping.
o 8 Only unconsumed data should be copied to the overflow property @mp;xmltext.

So what is attribute and element centric mapping? Attribute centric grabs the data from specific
elements whereas element centric grabs data from specific sub elements. This will all make sense when
we do a couple of examples. It is possible to enter a value of 3 for the Flag parameter which indicates
that both attribute and element are to be used together.
The WITH clause can be left out completely. However, if it is used there are two options that can be
used with it. The first is by associating the results with an existing database table. This comes in handy
when the XML document is formatted to fit the structure of table. The other is to specify specific
columns and their data types.
Its time to show how all of this works. Here is the XML document we are going to use in the
following examples.
<?xml version=1.0 encoding=UTF-8?>
<ROOT>
<Team Sponsor = Yamaha>
<Class CC = 125>
<Rider>Ivan Tedesco</Rider>
</Class>
<Class CC=250>
<Rider>Jeremy McGrath</Rider>
<Rider>David Vuillemin</Rider>
<Rider>Damon Bradshaw</Rider>
</Class>
</Team>
<Team Sponsor = Honda>
<Class CC=125>
<Rider>Travis Preston</Rider>
</Class>
< Class CC=250>
<Rider>Ricky Carmichael</Rider>
<Rider>Sebastien Tortelli</Rider>
</Class>
</Team>
<Team Sponsor = Kawasaki>
<Class CC=125>

<Rider>James Stewart</Rider>
</Class>
< Class CC=250>
<Rider>Ezra Lusk</Rider>
</Class>
</Team>
<Team Sponsor = Suzuki>
<Class CC=125>
<Rider>Mike Brandes</Rider>
</Class>
<Class CC=250>
<Rider>Travis Pastrana</Rider>
<Rider>Kevin Windham</Rider>
</Class>
</Team>
</ROOT>

Now apply OPENXML to this XML document and here is what it looks like using Attribute-Centric
mapping:
Declare @rDoc int,
@sDoc varchar(4000)
Set @sDoc =
<ROOT>
<Team Sponsor = Yamaha>
<Class CC = 125>
<Rider>Ivan Tedesco</Rider>
</Class>
<Class CC=250>
<Rider>Jeremy McGrath</Rider>
<Rider>David Vuillemin</Rider>
<Rider>Damon Bradshaw</Rider>
</Class>
</Team>
<Team Sponsor = Honda>
<Class CC=125>
<Rider>Travis Preston</Rider>
</Class>
<Class CC=250>
<Rider>Ricky Carmichael</Rider>
<Rider>Sebastien Tortelli</Rider>
</Class>
</Team>
<Team Sponsor = Kawasaki>
<Class CC=125>
<Rider>James Stewart</Rider>
</Class>
<Class CC=250>
<Rider>Ezra Lusk</Rider>
</Class>
</Team>
<Team Sponsor = Suzuki>
<Class CC=125>
<Rider>Mike Brandes</Rider>

</Class>
<Class CC=250>
<Rider>Travis Pastrana</Rider>
<Rider>Kevin Windham</Rider>
</Class>
</Team>
</ROOT>
EXEC sp_xml_preparedocument @rDoc OUTPUT, @sDoc
SELECT Sponsor
FROM OPENXML (@rDoc, /ROOT/Team, 1)
With Team
EXEC sp_xml_removedocument @rDoc

Lets take a minute and examine each piece of this. The first thing we do is load our XML document
into the @sDoc variable and pass that into the sp_xml_preparedocument. This procedure
takes the incoming XML and gets it ready to be used by the OPENXML statement. It does this by
parsing the XML and then creating an internal representation of the document in memory.
In our OPENXML statement is an XPath statement /ROOT/Team which tells OPENXML to loop
through the Team nodes and then return all the values of the Sponsor column.
We then use the sp_xml_removedocument to remove the XML document from memory and
any references to it.
In this Attribute-Centric example, an attribute name maps directly to a column name of the same name
in the result set.
With Element-Centric mapping the only difference
In our example the SELECT statement would change to look like this:
SELECT *
FROM OPENXML (@rDoc, ROOT/Team/Class/Rider, 2)
WITH Rider --(RiderName varchar(50))

There will be times when our XML is not just strictly in the Attribute-Centric or Element-Centric form.
It is then that we have the option to use a Mixed mapping, a combination of both Attribute-Centric and
Element-Centric.
Once again our source XML does not change, only our SELECT statement. As shown in the example
below it is a simple change:
SELECT *
FROM OPENXML (@rDoc, ROOT/Team/Class/Rider, 3)
WITH Rider

Explicit Column mapping takes us one step further when our other options dont return the data we
would like to see. It allows us to explicitly map elements from our XML document to result set fields.
Our source XML does not change, but our SELECT statement does. For example, it could look like
the following:

SELECT *
FROM OPENXML (@rDoc, ROOT/Team/Class/Rider, 1)
WITH (Sponsor
varchar(50) ../../../@Sponsor,
Class
varchar(25) ../../@CC
RiderName
varchar(50) ../Rider)

Inserting, Updating and Deleting Records


We can also insert records using OPENXML and it is not that difficult. Our source XML stays the
same but our SQL Statement would look like this:
INSERT Team (Sponsor)
SELECT DISTINCT Sponsor
FROM OPENXML (@rDoc, ROOT/Team, 1)
WITH (Sponsor varchar(50) @Sponsor)

Now when go look in the Team table we should see a few records inserted into the table that look like
the following:

TeamID

Sponsor

1
2
3
4

Yamaha
Honda
Kawasaki
Suzuki

We could continue this and create follow-up INSERT statements to insert into Class and Rider tables
also. But well save this for the Worksheet.
If we can insert records we should be able to update records also, true? Not only is it true, but it is quite
easy. First, go into the Rider table and add a RiderNumber column. You can either do this manually
thru Enterprise Manager or run the following code in SQL Analyzer:
ALTER TABLE Rider ADD RiderNumber int NULL

Now, lets modify the source XML. Add a Number attribute to each Rider element as follows:
<Rider Number=45>Ivan Tedesco</Rider>
<Rider Number=2>Jeremy McGrath</Rider>

It doesnt matter what the actual numbers are just as long each Rider element has a Number attribute.
Now we modify our SQL to look like the following:
UPDATE Rider
SET RiderNumber = r.RiderNumber
FROM (
SELECT Number,
OPENXML (@rDoc, /ROOT/Team/Class/Rider, 1)
WITH (Number
int
@Number,
Class)) r,
Team t,

Rider rt
WHERE t.TeamID = r.TeamID
AND r.Rider = rt.RiderName

Now when we look in the Rider table we should see the new RiderNumber field and the new values in
the new column.

As one would expect OPENXML also gives us the ability to delete records. We wont cover
that here because that will be covered in Worksheet 4.

OPENXML and ADO


The last thing we need to cover in this section is using OPENXML with ADO. Included in the VB
project is an example of using OPENXML with ADO. I wont display the code here but the code in the
VB project is documented and should be easy to understand.
The assumption is made here that you have at least a basic understanding of Visual Basic and how to
retrieve data from SQL Server using ADO. The only difference in the examples is that we will be using
OPENXML with ADO to update records in SQL Server.
To look at the examples, pick OPENXML from the main form and walk thru the examples. Look at the
code to understand how OPENXML is done with ADO.

Worksheet 4
The first thing we want to accomplish in this worksheet is to insert and delete some records using

OPENXML. The table we want to modify is the Team table.


Use the XML document that was used for the examples through out this section and change the four
Sponsor values to KTM, Canondale, TM, and Husqavarna.
Create an OPENXML statement that when run inserts these new records into the Team table.
When you have that running successfully, change the OPENXML statement to now delete those same
records.

Answers
First, insert the records:
Declare @rDoc int,
@sDoc varchar(4000)
Set @sDoc =
<ROOT>
<Team Sponsor = KTM>
<Class CC = 125>
<Rider>Ivan Tedesco</Rider>
</Class>
<Class CC=250>
<Rider>Jeremy McGrath</Rider>

<Rider>David Vuillemin</Rider>
<Rider>Damon Bradshaw</Rider>
</Class>
</Team>
<Team Sponsor = Canondale>
<Class CC=125>
<Rider>Travis Preston</Rider>
</Class>
<Class CC=250>
<Rider>Ricky Carmichael</Rider>
<Rider>Sebastien Tortelli</Rider>
</Class>
</Team>
<Team Sponsor = TM>
<Class CC=125>
<Rider>James Stewart</Rider>
</Class>
<Class CC=250>
<Rider>Ezra Lusk</Rider>
</Class>
</Team>
<Team Sponsor = Husqavarna>
<Class CC=125>
<Rider>Mike Brandes</Rider>
</Class>
<Class CC=250>
<Rider>Travis Pastrana</Rider>
<Rider>Kevin Windham</Rider>
</Class>
</Team>
</ROOT>
EXEC sp_xml_preparedocument @rDoc OUTPUT, @sDoc
INSERT Team (Sponsor)
SELECT DISTINCT Sponsor
FROM OPENXML (@rDoc, ROOT/Team, 1)
WITH (Sponsor varchar(50) @Sponsor)
EXEC sp_xml_removedocument @rDoc

Now, delete the records:

Declare @rDoc int,


@sDoc varchar(4000)
Set @sDoc =
<ROOT>
<Team Sponsor = KTM>
<Class CC = 125>
<Rider>Ivan Tedesco</Rider>
</Class>
<Class CC=250>
<Rider>Jeremy McGrath</Rider>

<Rider>David Vuillemin</Rider>
<Rider>Damon Bradshaw</Rider>
</Class>
</Team>
<Team Sponsor = Canondale>
<Class CC=125>
<Rider>Travis Preston</Rider>
</Class>
<Class CC=250>
<Rider>Ricky Carmichael</Rider>
<Rider>Sebastien Tortelli</Rider>
</Class>
</Team>
<Team Sponsor = TM>
<Class CC=125>
<Rider>James Stewart</Rider>
</Class>
<Class CC=250>
<Rider>Ezra Lusk</Rider>
</Class>
</Team>
<Team Sponsor = Husqavarna>
<Class CC=125>
<Rider>Mike Brandes</Rider>
</Class>
<Class CC=250>
<Rider>Travis Pastrana</Rider>
<Rider>Kevin Windham</Rider>
</Class>
</Team>
</ROOT>
EXEC sp_xml_preparedocument @rDoc OUTPUT, @sDoc
DELETE
FROM Team
WHERE TeamID IN
(SELECT TeamID
FROM Team, (
SELECT Sponsor
FROM OPENXML (@rDoc, 'ROOT/Team', 1)
WITH (Sponsor varchar(50) '@Sponsor')
) t
WHERE Team.Sponsor = t.Sponsor)
EXEC sp_xml_removedocument @rDoc

SQL & XDR Schemas


Up to this point weve taken a look at SQL syntax such as FOR XML and OPENXML which allows us
to query data in SQL Server. In this section we are going to briefly cover XDR schemas. Using XDR,
or XML Data Reduced schemas, we can also query SQL data. It also provides one other piece of
functionality that the others do not. XDR gives us the ability to query and retrieve data over HTTP. In
this section we will concentrate on that specific piece; querying data using HTTP using XDR schemas.

The assumption going forward in this section is that you have knowledge of schemas, differences
between schemas and DTDs, and how schemas work so not a lot of time will be spent on the
intricacies of schemas.
XDR schema is a subset of the XSD schemas, or XML Schema Definition. The XSD schema will not
be covered in this tutorial because it is not supported in SQL Server at the time of writing. The XDR
schema, however, provides us quite a bit of functionality not found in DTDs such as:
1) One schema can reference the definition of another schema
2) Undefined elements or tags can exist in an XML document
3) Datatypes can be specified for both an attribute and element.
Probably the most import feature is what weve been preaching throughout this entire tutorial is the fact
that there is no need to learn another syntax to figure out how to create schemas because schemas are
XML documents.
So lets get started.
The first thing we need to do is set up the IIS virtual directories that we are going to use for both this
section and the following section. Lets spend a few minutes doing that first.
1) Create a directory on your local drive called SQLXMLTutorial
2) Open up the IIS Virtual Directory Manager for SQL Server. This can be found Start |
Programs | Microsoft SQL Server
3) Click the plus (+) next to the machine name. Right mouse click on Default Web Site and
select New | Virtual Directory.
4) On the General tab in the Virtual Directory Name space enter SQLXMLTutorial. In the
Local Path space, browse to the directory you created in Step 1.
5) Select the Security tab. In the credentials section, enter the sa login name and password you
use to log into SQL Server. Make sure that the account type for SQL Server is selected.
6) Select the Data Source tab. In the SQL Server section, the default is (local). That is OK. In
the Database section, select the database you created for this tutorial.
7) Select the Settings tab. Make sure Allow template queries, Allow XPath and URL queries is
selected.
8) Selected the Virtual Names tab. In the Defined virtual names section, select New. In the
Virtual Name Configuration box, enter Tutorial in the Virtual Name box, select template in
the Type box, and C:\SQLXMLTutorial\Tutorial for the Path.
9) Click Save.
10) Click OK on the New Virtual Directory Properties from.

The next couple of sections make the assumption that your web server is installed locally, so all the
URLs will begin with http://localhost/. If it is not then youll need to replace localhost with the name
of your webserver.

Annotations
Before we get to some examples we need to talk about one thing, and that is Annotations. Annotations
were introduced with SQL Server 2000 and they allow data to be retrieved in a specific XML format.
This is accomplished by defining a mapping, inside the XDR schema, between the database tables and

columns with the elements and attributes. Below is a list of available annotations that can be used
inside the XDR schema:

sql:relationship defines relationships between elements.


sql:field defines the mapping between elements and columns.
sql:id-prefix sql:is-constant
sql:key-fields
sql:limit-field
sql:limit-value
sql:map-field
sql:overflow-field
sql: relation
sql:target-namespace
sql:url-encode
sql:use-cdata

Well only be covering a couple of these in this tutorial but theyre listed here as a reference.
The best way to learn this is to do some examples, so lets do some. We will start with a default
mapping. Open up your favorite text editor and type the following:
<?xml version=1.0?>
<Schema name=UserSchema
xmlns=urn:schemas-microsoft-com:xml-data
xmlns:sql=urn:schemas-microsoft-com:xml-sql>
</Schema>

This is the basic structure of an XDR schema. Lets take a few minutes and explain each piece and then
well fill it in with some real examples.
The first line is our standard XML declaration. There are three attributes that can be used here
version, encoding, and standalone. Version, which specifies which version of XML we are using, is the
only one we will use in this example.
The next line defines the root element of the XDR schema, and in the case of XDR schemas the root
element is always <Schema>. The following two lines define the namespace for the schema. You
should notice that
Lets fill in our schema now. Add the following lines to your schema so it looks like the following:
<?xml version=1.0?>
<Schema name=UserSchema
xmlns=urn:schemas-microsoft-com:xml-data
xmlns:sql=urn:schemas-microsoft-com:xml-sql>
<ElementType name=Users>
<AttributeType name=UserID />
<AttributeType name=FirstName />
<AttributeType name=LastName />
<Attribute type=UserID />
<Attribute type=FirstName />
<Attribute type=LastName />

</ElementType>
</Schema>

Save this as XDRSchema.xdr in the SQLXMLTutorial directory you just created above. Open
up your browser and type in the following URL:
http://localhost/SQLXMLTutorial/Tutorial/XDRSchema.xdr/Users?root=root
Your results should look something like the following:
<?xml version=1.0 encoding=utf-8 ?>
- <root>
<Users UserID=1 FirstName=Scott LastName=Klein />
<Users UserID=2 FirstName=James LastName=Bond />

</root>
Lets talk about the code we just added. The <ElementType> element defines the characteristics
of an element. It has four possible attributes:

name This is required.


content Defines what kind of value this element will contain. Not required. Possible values
include:
o mixed default. Can contain a mix of child elements and values.
o textonly element can contain only a value, no child elements.
o Eltonly child elements only, no values.
o Empty cannot contain any elements or values. Can contain attributes.
Model Defines if the document allows extra content. The possible values are open (the default
value) and closed.
Order defines how child elements are ordered within the element.

Attributes are declared using the <AttributeType> element. This element, like the
<ElementType> element, has attributes that define its behavior. Those attributes are:

Name Again, this is required.


Required Possible values for this are Yes (the attribute must appear) or No (optional attribute).
Default a default value can be specified.

When we declare an <AttributeType> element we can reference that with the corresponding
<attribute> element as demonstrated above. These elements have the same three attributes as the
<AttributeType> element and behave much the same. The difference is that the type attribute
refers to a corresponding <AttributeType> with by its name value.
We could ask that a specific record to be returned by supplying a UserID in the URL like this:

http://localhost/SQLXMLTutorial/Tutorial/XDRSchema.xdr/Users[@UserID=1]
This would return the following:
<Users UserID=1 FirstName=Scott LastName=Klein />

One more quick example of default mapping and then well move on. Lets modify it so our data is
returned as elements and not attributes (element-centric). It should look like this:
<?xml version=1.0?>
<Schema name=UserSchema
xmlns=urn:schemas-microsoft-com:xml-data
xmlns:sql=urn:schemas-microsoft-com:xml-sql>
<ElementType name=UserID content=textonly />
<ElementType name=FirstName content=textonly />
<ElementType name=LastName content=textonly />
<ElementType name=Users />
<Attribute type=UserID />
<Attribute type=FirstName />
<Attribute type=LastName />
</ElementType>
</Schema>

Now our results should look like this:


<?xml version=1.0 encoding=utf-8 ?>
<root>
<Users>
<UserID>1</UserID>
<FirstName>Scott</FirstName>
<LastName>Klein</LastName>
</Users>
:
:
</root>

In this example the database field names are mapped directly to the elements because we included the
attribute content with a value of textonly.

Explicit Mapping
Lets do another example, and this time well use explicit mapping. If you remember above we
provided a list of available annotations that can be used in the XDR schema. One of those is the
sql:relation annotation which allows for the mapping of XML nodes to a SQL Server table.
This provides us a bit of extra functionality in that our element and/or attribute names can be different
than the names of the mapped column or table.
Lets make the following changes to our original schema so that it looks like the following:
<?xml version=1.0?>
<Schema name=UserSchema
xmlns=urn:schemas-microsoft-com:xml-data
xmlns:sql=urn:schemas-microsoft-com:xml-sql>
<ElementType name=Users sql:relation=[Users]>
<AttributeType name=UserID />
<AttributeType name=FirstName />
<AttributeType name=LastName />
<Attribute type=UserID />
<Attribute type=FirstName />
<Attribute type=LastName />
</ElementType>

</Schema>

In this example all we did was add the sql:relation annotation which maps the Users element to
the Users SQL table. Go ahead and save this into our SQLXMLTutorial directory as UserSchema.xdr.
Open up your browser and type in the following URL:

http://localhost/SQLXMLTutorial/Tutorial/XDRSchema.xdr/Users?root=root
Our output from this should look fairly similar to the result of the first example of this section:
<?xml version=1.0 encoding=utf-8 ?>
- <root>
<Users UserID=1 FirstName=Scott LastName=Klein />
<Users UserID=2 FirstName=James LastName=Bond />

</root>
Lets do one more example using the sql:field annotation. In the previous example we mapped
an element to a table. In this example we want to map a specific column to a node. Modify the
example above to look like this:
<?xml version=1.0?>
<Schema name=UserSchema
xmlns=urn:schemas-microsoft-com:xml-data
xmlns:sql=urn:schemas-microsoft-com:xml-sql>
<ElementType name=First sql:field=FirstName>
<ElementType name=Last sql:field=LastName>
<ElementType name=Users sql:relation=[Users]>
<Element type=First />
<Element type=Last />
</ElementType>
</Schema>

Save this schema as UserSchema1.xdr and type the same URL in your browser that we used in the
previous example. Here is the resultset:
<?xml version=1.0 encoding=utf-8 ?>
<root>
<Users>
<UserID>1</UserID>
<First>Scott</First>
<Last>Klein</Last>
</Users>
:
:
</root>

XDR schemas allow us to be extremely flexible. It is possible to join 2 or more tables together to create
complex relationships. For example, it is possible to join the Users and UserAddress tables together to
create a resultset that includes information from both those tables.
In this case, for example, we would use the sql:relationship annotation as an element instead
of an attribute. This annotation contains four required attributes:

key-relation
key
foreign-relation
foreign-key

Our schema would contain some code that looked like this:
<sql:relationship
key-relation=Users
key=UserID
foreign-relation=UserAddress
foreign-key=UserID
/>

You can see how easy and flexible XDR schemas are. Hopefully though these examples you can start
to see the possibilities that XDR schemas provide.

Worksheet 5
For this tutorial we want to do a few things. First, create a simple XDR schema that returns RiderName
and ClothingSponsor from the Rider table. The URL should return all the records from the table.
Next, use that same schema but in the URL specify a specific record based on the RiderID.
Lastly, modify the schema so that the Rider elements are mapped to the Rider SQL table. First return
all the records then return a specific record.

Answers
XDR Schema returning RiderName and ClothingSponsor:
<?xml version=1.0?>
<Schema name=UserSchema
xmlns=urn:schemas-microsoft-com:xml-data
xmlns:sql=urn:schemas-microsoft-com:xml-sql>
<ElementType name=Rider>
<AttributeType name=RiderName />
<AttributeType name=ClothingSponsor />
<Attribute type=RiderName />
<Attribute type=ClothingSponsor />
</ElementType>
</Schema>

If this is saved with the name XDRSchema.xdr then the URL would look like this:
http://localhost/SQLXMLTutorial/Tutorial/XDRSchema.xdr/Rider?root=root

Next, to return a specific rider by RiderID the schema needs to be modified to include the column
<?xml version=1.0?>

<Schema name=UserSchema
xmlns=urn:schemas-microsoft-com:xml-data
xmlns:sql=urn:schemas-microsoft-com:xml-sql>
<ElementType name=Rider>
<AttributeType name=RiderID />
<AttributeType name=RiderName />
<AttributeType name=ClothingSponsor />
<Attribute type=RiderID />
<Attribute type=RiderName />
<Attribute type=ClothingSponsor />
</ElementType>
</Schema>

and our URL would look like this:


http://localhost/SQLXMLTutorial/Tutorial/XDRSchema.xdr/Rider[@RiderID=1]

Finally, mapping the columns to a specific column:


<?xml version=1.0?>
<Schema name=UserSchema
xmlns=urn:schemas-microsoft-com:xml-data
xmlns:sql=urn:schemas-microsoft-com:xml-sql>
<ElementType name=RiderID sql:field=RiderID>
<ElementType name=RiderName sql:field=RiderName>
<ElementType name=ClothingSponsor
sql:field=ClothingSponsor>
<ElementType name=Rider sql:relation=[Rider]>
<Element type=RiderID />
<Element type=RiderName />
<Element type=ClothingSponsor />
</ElementType>
</Schema>

XML Templates
XML Templates are the coolest things since sliced bread. They are easy and extremely flexible. XML
Templates are well-formed XML documents that can have at least one SQL or XPath query inside.
This then allows us to perform an HTTP request and return the results right there on our web page.
Because it is XML, we could even specify an XSL style sheet to format the returned results nice and
neat. It doesnt stop there either. Instead of embedding the SQL directly in the XML template, our
template can execute a stored procedure from with our template. This also allows us to use parameters
within our template.
Lets get to some examples and well start with an easy one. Type this into your favorite editor:
<ROOT xmlns:sql="urn:schemas=microsoft-com:xml-sql">
<sql:query>
SELECT UserID, FirstName, LastName
FROM Users
ORDER BY LastName, FirstName
FOR XML AUTO

</sql:query>
</ROOT>

Save this as Users.xml in the SQLXMLTutorial directory you created earlier. Open up your browser
and type the following URL:
http://localhost/SQLXMLTutorial/Tutorial/users.xml
You should see a list of users in XML format. All though this was quite a simple example, we could
get more complicated by
Your resultset should look like this:
<ROOT xmlns:sql=urn:schemas-microsoft-com:xml-sql>
<Users UseriD=1 FirstName=Scott LastName=Klein />
:
:
</ROOT>

Whats cool about this is that the SQL statement can be as simple or complex as you like. The FOR
XML clause at the end of our SQL statement is needed; otherwise youll get an error. You can use any
of the options we discussed earlier about the FOR XML clause.
Lets move on to our next example by showing how to call a stored procedure from an XML Template.
Open up your editor and type in the following:
<ROOT xmlns:sql="urn:schemas=microsoft-com:xml-sql">
<sql:query>
EXEC GetUsers
</sql:query>
</ROOT>

Save this as spGetUsers.xml and type the following in your browser:


http://localhost/SQLXMLTutorial/Tutorial/spGetUsers.xml
It doesnt look too different from our first example. The only difference is that instead of executing
SQL directly from within our template, we executed a stored procedure.
Lets take it to the next step and pass a parameter to that stored proc.
<ROOT xmlns:sql="urn:schemas=microsoft-com:xml-sql">
<sql:header>
<sql:param name=UserID></sql:param>
</sql:header>
<sql:query>
EXEC GetUsers2 @UserID
</sql:query>
</ROOT>

Save this as spGetUsers2.xml and enter this URL in your browser:


http://localhost/SQLXMLTutorial/Tutorial/spGetUsers2.xml?UserID=1

The resultset should have only a single record in it like this:


<ROOT xmlns:sql=urn:schemas-microsoft-com:xml-sql>
<Users UseriD=1 FirstName=Scott LastName=Klein />
</ROOT>

Lets get a bit more complicated and include multiple SQL statements in our template. There are two
ways we can do this. The first way is to include them in a single <sql:query> tag like this:
<ROOT xmlns:sql="urn:schemas=microsoft-com:xml-sql">
<sql:query>
SELECT UserID, FirstName, LastName
FROM Users
ORDER BY LastName, FirstName
FOR XML AUTO
UPDATE Users
SET Address1 = 1212 XML Blvd.
WHERE UserID = 2
</sql:query>
</ROOT>

The second method is to wrap each statement in its own set of tags like this:
<ROOT xmlns:sql="urn:schemas=microsoft-com:xml-sql">
<sql:query>
SELECT UserID, FirstName, LastName
FROM Users
ORDER BY LastName, FirstName
FOR XML AUTO
</sql:query>
<sql:query>
UPDATE Users
SET Address1 = 1212 XML Blvd.
WHERE UserID = 2
</sql:query>
</ROOT>

There are pros and cons to doing it both ways. In the first example all three statements will be executed
as a batch. There is a downside to this, however. Suppose we had three statements in our first example,
the third one being a DELETE statement. In this scenario, if the second statement (the UPDATE
statement) failed then the third statement (the DELETE) statement would not run at all. Not good.
The second example fixes all of that. Each statement is run individually. If one fails, the others
continue to run regardless of the success or failure of the other statements. So in this example, even if
the UPDATE failed the DELETE would still occur.
Lets do one more example. We saw earlier that we could pass a parameter to a stored procedure and
execute that stored procedure using templates. Lets take that one step further and have a value
returned as a second output parameter.
<ROOT xmlns:sql="urn:schemas=microsoft-com:xml-sql">
<sql:header>

<sql:param name=UserID></sql:param>
<sql:param name=FirstName></sql:param>
<sql:param name=LastName></sql:param>
</sql:header>
<sql:query>
EXEC CreateUser @UserID OUTPUT, @FirstName, @LastName
INSERT INTO NewUsers
SET NewUserID = @UserID
</sql:query>
</ROOT>

First, save this as CreateUser.xml in the same directory you have been saving everything else to. To
make all of this work, enter the following URL in your browser:
http://localhost/SQLXMLTutorial/Tutorial/CreateUser.xml?FirstName=Don&amp;LastName=Jo
hnson
We should now see a new record entered into the NewUser tale with the new UserID just created.
We havent covered one other topic, and that is applying an XSL stylesheet to the results. Want to take
a guess as to what the worksheet for this section covers?

Worksheet 6
For this worksheet we only need to do two things. In the database is a stored procedure called
GetRiders.
Step one: write a Template that returns the riders and corresponding information. Save it as

GetRiderInfo.xml in the directory where we have been saving the other work.
What does the URL look like?
Step two: apply a style sheet to the results so that the results are formatted when they are
returned.
Answers
First, the Template.
<ROOT xmlns:sql=urn:schemas-microsoft-com:xml-sql>
<sql:query>
EXEC GetRiderInfo
</sql:query>
</ROOT>

http://localhost/SQLXMLTutorial/Tutorial/spGetRiderInfo.xml

What does the style sheet look like?


<?xml version1.0 encoding=UTF-8?>

<xsl:stylesheet xmlns:xsl-http://www.w3.org/1999/XSL/Transform
version=1.0>
<xsl:template match = *>
<xsl:apply-templates />
</xsl:template>
<xsl:template match = Rider>
<TR>
<TD><xsl:value-of select = @RiderID /></TD>
<TD><xsl:value-of select = @RiderName /></TD>
<TD><xsl:value-of select = @ClothingSponsor /></TD>
</TR>
</xsl:template>
<xsl:template match = />
<HTML>
<HEAD>
<STYLE>th</STYLE>
</HEAD>
<BODY>
<TABLE border=1 style=width:400;>
<TR><TH>RiderID</TH><TH>Rider
Name</TH><TH>ClothingSponsor</TH></TR>
<xsl:apply-templates select = root />
</TABLE>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>

Save this as RiderInfo.xsl in the same directory as your template. Then modify your template to
include the stylesheet reference:
<ROOT xmlns:sql=urn:schemas-microsoft-com:xml-sql
sql:xsl=RiderInfo.xsl>
<sql:query>
EXEC GetRiderInfo
</sql:query>
</ROOT>

Updategrams
Before we talk about Updategrams and Bulk Load the file that provides this functionality needs to be
installed. This file, xml for sql.exe is included with this tutorial. Save this file to your hard drive and
click on Start | Run. Type the path to the directory where you saved this file. Click OK. Click Next
on the Welcome screen. Follow the directions and make sure all options are installed. Click Finish
when done. It may prompt you to reboot your computer.
Behind sliced bread and XML Templates, Updategrams are the next greatest thing. Thats because
Updategrams are XML Templates with special tags. Instead of writing SQL statements to do your
INSERT, UPDATES, and DELETES these special tags are used like before and after images of the
database.

For example, instead of writing an INSERT statement to insert a record, the Upgrategrams allow me to
provide a before image of what the table look likes and an after image of the table, this inserting the
new record.
If this doesnt make sense now, it will when we do some examples. In simple form, the basic syntax
structure of an Updategram looks like this:
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:sync>
<updg:before>
:
</updg:before>
<updg:after>
:
</updg:after>
</updg:sync>
<ROOT>

The three main pieces of the Updategram are <sync>, <before>, and <after>. The
<before> and <after> blocks come in pairs to create a single transaction. These blocks are
contained within a <sync> block that defines the transaction. The <before> block is a before
image prior to the transaction taking place. The <after> block is the after image, post transaction
having taken place. In other words, the <before> block is the state of the existing data, the
<after> block is the state of the data after the transaction occurs.
Time for some examples. We will start simple and work our way to more complex examples. Lets
begin with an Insert. Using our model above, lets create the following Updategram to insert a new
user into our Users table.
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:sync>
<updg:before>
</updg:before>
<updg:after>
<Users FirstName=Brad LastName=Pitt />
</updg:after>
</updg:sync>
<ROOT>

Save this as NewUser.xml where you have been saving everything else, open up your browser and type
the following URL:
http://localhost/SQLXMLTutorial/Tutorial/NewUser.XML
All we are going to get back is a message stating that our Updategram ran successfully. If you were to
now select all the rows from the Users table, you should see the newly created record of Brad Pitt.
A quick explanation of how this worked is due. We first did not specify any detail inside the

<before> block. Inside our <after> block we specified some data, specifically a first name and
last name. When we ran this Updategram, SQL Server looked at what it was sent and noticed that the

<before> block was empty and that the <after> block contained some information. SQL Server
understood this as an INSERT instruction and therefore created a new user record in the Users table.
Realistically we could have completely left out the <before> block and it would have worked just
the same. Good coding practices say to leave it in for better reading.

Deleting & Updating


Lets do a DELETE. Modify the example above to look like this:
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:sync>
<updg:before>
<Users FirstName=Brad LastName=Pitt />
</updg:before>
<updg:after>
</updg:after>
</updg:sync>
<ROOT>

Save this as DeleteUser.xml and type the following URL into your browser:
http://localhost/SQLXMLTutorial/Tutorial/DeleteUser.XML
Again, all we get back is a message letting us know our statement executed successfully. Now go back
and look at the Users table. The record containing Brad Pitt is now gone. This operated the same way
the previous example did but in reverse order. SQL Server understood this to be a DELETE instruction
because the <before> block contained some user information and the <after> block was empty.
Weve done and INSERT and DELETE, now lets do a UPDATE. Modify your previous example to
look like this:
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:sync>
<updg:before>
<Users UserID=2 />
</updg:before>
<updg:after>
<Users FirstName=Brad LastName=Pitt />
</updg:after>
</updg:sync>
<ROOT>

Save this as UpdateUser.xml. Before executing this in your browser, open up Query Analyzer and
query all the records from the Users table. UserID 2 has a FirstName and Last name of James Bond,
correct? Now type the following URL into your browser:
http://localhost/SQLXMLTutorial/Tutorial/UpdateUser.XML

Go back into Query Analyzer and re-query all the records from the Users table. UserID 2 should now
have a FirstName and LastName of Brad Pitt.
As was with the last two examples you should get a message stating successful execution. The only
difference with this example was that we specified data in both the <before> block and <after>
block. SQL Server understood this as an update because the <before> block specifies the record to
be updated and the <after> block specifies how the record should look after the update.
Until now weve been dealing with single records. Lets do an example or two that deal with multiple
rows or records in one Updategram.
In this example I want to insert three new users. There are two ways I can do that. The first is by
including the new records in a single <after> block. It would look like this:
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:sync>
<updg:before>
</updg:before>
<updg:after>
<Users FirstName=James LastName=Bond />
<Users FirstName=Matt LastName=Damon />
<Users FirstName=Eddie LastName=Murphy />
</updg:after>
</updg:sync>
<ROOT>

Likewise, we could wrap each new user within an individual <after> block like so:
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:sync>
<updg:before>
</updg:before>
<updg:after>
<Users FirstName=James LastName=Bond />
</updg:after>
<updg:before>
</updg:before>
<updg:after>
<Users FirstName=Matt LastName=Damon />
</updg:after>
<updg:before>
</updg:before>
<updg:after>
<Users FirstName=Eddie LastName=Murphy />
</updg:after>
</updg:sync>
<ROOT>

Both of these accomplish the same thing, yielding the exact same results.
We could also combine the two to produce the following:
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:sync>
<updg:before>
</updg:before>
<updg:after>
<Users FirstName=James LastName=Bond />
</updg:after>
<updg:before>
</updg:before>
<updg:after>
<Users FirstName=Matt LastName=Damon />
<Users FirstName=Eddie LastName=Murphy />
</updg:after>
</updg:sync>
<ROOT>

Updategram are flexible


We have yet to combine an INSERT, UPDATE, and DELETE in a single Updategram, so lets do that
now because this is where the flexibility of Updategrams shine.
Using our previous example, lets create an Updategram that first updates record and then inserts a new
one. It should look like this:
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:sync>
<updg:before>
<Users UserID=3 />
</updg:before>
<updg:after>
<Users FirstName=Bruce LastName=Lee />
</updg:after>
<updg:before>
</updg:before>
<updg:after>
<Users FirstName=Chuck LastName=Norris />
</updg:after>
</updg:sync>
<ROOT>

Save this as UpdateInsert.xml and then browse to:

http://localhost/SQLXMLTutorial/Tutorial/UpdateInsert.xml
Looking into our Users table we should see that our FirstName and LastName values changed and we
now have a new user record.
Lets quickly cover how to handle NULL values because SQL Server accepts NULL values. A NULL
value is not the same as an empty string ( ) so it needs to be handled differently in an Updategram.
The way this is dealt with is by using the updg:nullvalue attribute which is an attribute of the
<sync> tag. Lets look at an example that shows this:
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:sync updg:nullvalue=NL>
<updg:before>
</updg:before>
<updg:after>
<Users FirstName=Bob Address1=NL />
</updg:after>
</updg:sync>
<ROOT>

As you can see in the example above the value of the updg:nullvalue attribute can be any string.
In this case, weve used the value NL which can be a short abbreviation for NULL. We then assign
that value to the Address1 field, which informs SQL Server that this field should be assigned a NULL
value.

Updategrams from HTML forms


OK, three more topics on Updategrams. The first is how to deal with Updategrams within an HTML
form. There are two parts to make this work. The first is the Updategram itself and the other is the
HTML form. Lets create the HTML form first. It will look like this:
<HTML>
<HEAD>
<TITLE>Updategram and HTML example</TITLE>
</HEAD>
<BODY>
<FORM
Action=http://localhost/SQLXMLTutorial/Tutorial/Insert.xml
method=post>
First Name:
<INPUT Type=text name=fn>
<BR>
Last Name
<INPUT Type=text name=ln>
<BR>
<INPUT Type=submit value=Submit>
</FORM>
</BODY>
</HTML>

Save it as Post.html. This is a simple form that lets the user enter a first name and last name. We use
the Post method of the <FORM> element to submit the information on the form.
Now lets create the Updategram. It will look like this:
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:header>
<updg:param name=fn />
</updg:param name=ln />
</updg:header>
<updg:sync>
<updg:before>
</updg:before>
<updg:after>
<Users FirstName=$fn LastName=$ln />
</updg:after>
</updg:sync>
<ROOT>

OK, this needs to be saved as Insert.xml because this is what the <FORM> posts in our html file. Open
up the html page in your browser. When information is entered into the form and the submit button
pushed and the data is taken from the form passed to the Updategram as parameters.
We use the <updg:param> tag with the name attribute to gather the parameters values passed in.
The Updategram takes the two values and inserts them into the Users table. When you run this you
should see a new record in the Users table.

Updategrams & schemas


Lets move on to the second topic which is Updategrams and XDR schemas. Up until this point all our
Updategrams have used implicit mapping meaning that the <before> and <after> attributes
match the table name and each attribute coincides with a column name in the table. This does not work
when our Updategram deals with multiple tables having primary-foreign key relationships. This is
where XDR schemas add value to the Updategram.
In order to use an annotated XDR schema in an Updategram we need to apply the mapping-schema
attribute of the <sync> element which specifies mapping information.
We did a multi-table example in the previous section when we covered XDR schemas so instead of
doing a single table example well jump right in and do a multiple table example.
Again, there are two parts to doing annotated XDR schemas with Updategrams. The first is the
Updategram and the second is the XDR schema. What we want to accomplish is similar to an example
we did earlier. Well pass in a TeamID and insert a new record into the Rider table using the TeamID
we passed in. Lets create the Updategram first and use our Team / Rider motocross tables.
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:header>
<updg:param name=TeamID />

</updg:header>
<updg:sync mapping-schema=mapsch.xml>
<updg:before>
<Team TeamID=$TeamID />
</updg:before>
<updg:after>
<Team TeamID=$TeamID >
<Rider RiderName=Broc Glover />
</Team>
</updg:after>
</updg:sync>
<ROOT>

Save this as UpdgSchema.xml. This looks like a standard Updategram except for the information in the

<updg:after> tag. The code within the <updg:after> tags takes the parameter and inserts a
new record into the Rider table. New to this is the mapping-schema attribute which points to the XDR
schema (well be creating that next), which maps actual tables to the names used in the schema.
Now lets take a look at the XDR schema. It looks like this:
<?xml version=1.0?>
<Schema name=UserSchema
xmlns=urn:schemas-microsoft-com:xml-data
xmlns:sql=urn:schemas-microsoft-com:xml-sql>
<ElementType name=Rider sql:relation=Rider >
<AttributeType name=TeamID />
<AttributeType name=RiderName />
<Attribute type=TeamID />
<Attribute type=RiderName />
</ElementType>
<ElementType name=Team sql:relation=Team >
<AttributeType name=TeamID />
<Attribute type=TeamID />
<element type=Rider >
<sql:relationship
key-relation=Team key=TeamID
foreign-relation=Rider foreign-key=TeamID
/>
</element>
</ElementType>
</Schema>

Save this as mapsch.xml (this is the name that the Updategram references). This schema establishes a
relationship between the Team and Rider table using the <updg:relation> annotation (we
covered that earlier so it should be familiar). Like the examples we did in the XDR Schema section, we
declared two elements in which the relationship is created and defined corresponding
Execute the Updategram by entering the following URL in your browser:
http://localhost/SQLXMLTutorial/Tutorial/UpdgSchema.xml?TeamID=1

When doing a query of the Rider table you should see a new record with a rider name of Broc Glover.
In all of these examples we have hard coded our data but in reality this will not be the case. So how do
we pass parameters to Updategrams? Lets talk about that now and use an update example.
Modify your update example to look like this:
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:header>
<updg:param name=UsrID/>
<updg:param name=LstNm/>
</updg:header>
<updg:sync>
<updg:before>
<Users UserID=$UsrID />
</updg:before>
<updg:after>
<Users LastName=$LstNm />
</updg:after>
</updg:sync>
<ROOT>

Save this as UpdateParam.XML, and now our URL will look like this:
http://localhost/SQLXMLTutorial/Tutorial/UpdateParam.xml?UsrID=2&amp;LstNm=Garratt
Our Brad Pitt record in the database is now Brad Garratt. Even though we are only updating a single
column, two parameters had to be passed. The first is the UserID of the record we need to update and
the second parameter is the value we need to update.
The Updategram has a <updg:header> section that receives the two parameters and then
executes the rest of the Updategram.
It is also possible to pass a parameter to an Updategram with the Updategram is included as part of a
form on a web page. It would look like this:
<HTML>
<HEAD>
<TITLE>Lets Add a User</TITLE>
</HEAD>
<BODY>
<FORM ACTION=http://localhost/SQLXMLTutorial METHOD=POST>
<INPUT TYPE=hidden NAME=contenttype VALUE=text/xml>
<INPUT TYPE=hidden NAME=template VALUE=
<ROOT xmlns:updg=urn:schemas=Microsoft-com:xml-updategram>
<updg:header>
<updg:param name=UsrID/>
<updg:param name=LstNm/>

</updg:header>
<updg:sync>
<updg:before>
<Users UserID=$UsrID />
</updg:before>
<updg:after>
<Users LastName=$LstNm />
</updg:after>
</updg:sync>
<ROOT>
>
:
:
rest of the code to include the input fields and submit button

Save this as AddUser.html and open it up in your browser. When data is entered and the Submit button
is clicked the record is updated.
You will notice that there are two hidden fields. This is important because the second one holds the
Updategram and the first one says that an XML document is being posted instead of a normal form.

Updategrams& ADO
Finally, lets talk about using Updategrams and ADO. Inside the included Visual Basic project are two
examples. The first uses the DOMDocument object of MSXML 3.0 to execute an Updategram directly.
The second uses ADO to accomplish the same thing. The code is not listed here but it is well
documented for easy reading.
Get the Visual Basic project source code
The basic idea is to take an Updategram, whether created in the code or read from a file, and pass the
Updategram to SQL Server via the CommandStream property on the Command object. An input
stream is created in which the Updategram is passed. Results are returned via the command objects
output stream.

XML Bulk Load


There are two methods that can be used to demonstrate XML Bulk Load. The first is to create a SQL
Server DTS package. This method requires some high level knowledge of SQL Server so in this
tutorial we will use the second method, which uses ADO. Both methods require an XML document
that contains the data that will be imported and an XDR Schema to maps the XML document to the
database.
The requirement in using XML Bulk Load, whether you plan on using ADO or DTS is the

SQLXMLBulkLoad object. In the Visual Basic example included with this tutorial is an example of
using XML Bulk Load using ADO. A code sample is shown below. The basic idea is to use the
connection on the Command object.
Dim cn

as New ADODB.Connection

Dim cmd
Dim xmlbl

as New ADODB.Command
as New SQLXMLBulkLoad

Cn.Open Provider=SQLOLEDB;Server=(local);database=SQLXMLTutorial;
Set cmd.ActiveConnection = cn
Xmlbl.ConnectionCommand = cmd
Xmlbl.Execute schemafile, xmlfile

The schemafile is the XDR schema and the xml file is the XML document. The full code can be
found in the Visual Basic project accompanying this tutorial. As with the other examples they are well
documented for good reading.

Worksheet 7
In this worksheet were going to cover an Insert, Update, and Delete Updategram.
1. First, create an Updategram that inserts a record into the Rider table.
2. Second, create an Updategram that updates the record you just inserted.
3. Third, create an Updategram that deletes the record you just inserted, plus inserts a
completely new record.
Answers
First, insert into the Rider table.
<ROOT xmlns:updg=urn:schemas-microsoft-com:xml-updategram>
<updg:sync>
<updg:before>
</ updg:before>
<updg:after>
<Rider RiderName=Bob Hannah ClothingSponsor=Fox />
<updg:after>
</updg:sync>
</ROOT>

Now, update that record (find out what the new RiderID is first and use that for the before
sectionfor this example Ill use the 5):
<ROOT xmlns:updg=urn:schemas-microsoft-com:xml-updategram>
<updg:sync>
<updg:before>
<Rider RiderID=5 />
</ updg:before>
<updg:after>
<Rider RiderName=Bob Hannah ClothingSponsor=JT />

<updg:after>
</updg:sync>
</ROOT>

Now delete it:


<ROOT xmlns:updg=urn:schemas-microsoft-com:xml-updategram>
<updg:sync>
<updg:before>
<Rider RiderID=5 />
</ updg:before>
<updg:after>
<updg:after>
</updg:sync>
</ROOT>

Summary
We talked about a lot in a short amount of space, ranging from simple FOR XML clauses to
more difficult topics such as using XML Templates and Updategrams. The idea was to show
you more than one way to handle data from both a retrieval and insert/update/delete
perspective.
Hopefully you have begun to see the flexibility provided with SQL Server to handle data in
XML. We have only scratched the surface with a lot of this so feel free to take what you have
learned here and build on it.
Here is an additional Visual Basic project which you can use to learn more about Updategrams.
Get the Visual Basic project source code
You can also download the PDF copy of this online tutorial. When youre ready, go ahead and do the
final exam!

You might also like