Kiss the Middle-tier Goodbye with SQL Server Yukon

uch of the buzz surrounding the release of SQL Server Yukon seems to be about its new integration with the CLR. However, there are plenty of other worthwhile features in this lastest release?and at least one of them can make your database application programming whole a lot easier. Besides CLR integration, Yukon also offers three new XML support features that effectively eliminate the use of middle-tier components.

With SQL Server 2000, you needed datatypes like varchar or text to store XML data. Furthermore, there was no chance to query XML data directly on the database tier. You read your XML data in strings and passed them to the middle-tier components, which would then query against the XML data. The resulting XML was shown through the use of XSL transformations. Obviously, this was not a very elegant approach.

Yukon’s three new XML support features allow you to interact directly with your XML data.

  • The new XML datatype allows you to store native data inside Yukon.
  • Yukon allows you to run queries against XML data with XQuery or XPath. You can also embed XQuery statements directly into T-SQL statements. XML schemas allow you to assign a schema to the new XML datatype, so you can ensure that your XML data always matches the specified XML schema.
  • The XML Data Modification Language (XML DML) allows you to update XML data through the use of T-SQL statements. XML indices are used when you need a performance boost.

XML Datatypes
One way to think about the new datatype (xml) is to see it as similar to datatypes like varchar, datetime, or float. You can use it to declare columns, parameters, or variables. This new datatype allows you to:

  • Store XML data in a typed form. To accomplish this a XML schema must be registered within Yukon and assigned to the XML datatype.
  • Store XML data in an untyped form.
  • Query against the XML data directly in T-SQL statements or stored procedures, using query languages like XQuery or XPath.
  • Update XML data directly in T-SQL statements or stored procedures, using the new XML Data Modification Language (XML DML).
  • Create indices on XML datatype columns, improving database performance.

Typed or Untyped
What’s the difference bwteen storing data in typed or untyped form? First of all, XML data is stored by default in untyped form?without the use of an XML schema. This XML data must be well-formed. To store XML data in a typed form, you must reference an XML schema within the declaration of your columns, parameters, and variables. The following code shows how to use an untyped XML datatype:

-- Creating a new XML datatype variableDECLARE @xml-- Creating a new table with a xml datatype columnCREATE TABLE MyTable(ID int primary key,    MyXMLColumn xml)-- Creating a stored procedure with an xml datatype-- parameterCREATE PROCEDURE MyStoredProcedure(ID int,    MyXMLValue xml)AS...-- Creating a new table with a xml datatype column -- with a default valueCREATE TABLE MyTable(ID int primary key,    MyXMLColumn xml default   '")

To use a typed XML datatype, reference an XML schema during the declaration of the datatype. You must register the XML schema inside of Yukon in order to reference it. During this registration process, the XML schema is analyzed and all found types are imported and registered in Yukon’s metasystem. Apparently, XML schemas are not simply stored as text blobs in Yukon. Because of this, it’s impossible to restore the XML schema to its original state.

Luckily, Yukon provides a function to recreate the XML schema from the metadata. However, keep in mind that annotations, like comments, can’t be reproduced because they are not stored in the metasystem. To register a XML schema within Yukon, use the new T-SQL statement CREATE XMLSCHEMA:

CREATE XMLSCHEMA'                                    /         '

After its registered, the XML schema can then be referenced through the target namespace http://www.csharp.at/Conferences/2004/02/ASPKonferenz.

For the new XML schema, a new record is inserted into the system table sys.xml_namespaces. This new record consists of the namespace name and a unique ID assigned to that namespace. With this ID, relations to other system tables are established. For each new type within the XML schema, a new record in the system table sys.xml_components is inserted. In the example, a new record was created for the element and for each attribute as well.

With the system function xml_schema_namespace, Yukon allows you to regenerate the XML schema out of the stored metadata. You can call this function from a SELECT statement. As a parameter, you pass in the namespace name of the registered XML schema that should be regenerated:

SELECT xml_schema_namespace (N'http://www.csharp.at/Conferences/2004/02/ASPKonferenz)

To delete an already registered XML schema, use the T-SQL statement DROP XMLSCHEMA. Remember, the deleted XML schema must have no references to other objects (like tables or stored procedures):

DROP XMLSCHEMA NAMESPACE'http://www.csharp.at/Conferences/2004/02/ASPKonferenz'

Here’s how to create a typed XML datatype column within a table:

CREATE TABLE Customers(   ID int primary key,   CustomerDocument xml       ('http://www.csharp.at/Conferences/      2004/02/ASPKonferenz'))

As you can see, the target namespace of the referencing XML schema must be provided during the creation of the new table.

Loading the XML Datatype
How do you load XML data into the new XML datatype? With Yukon, you can convert string data to XML data implicitly or explicitly by using the T-SQL functions CAST and CONVERT. Take a look:

INSERT INTO Customers (ID, CustomerDocument)VALUES(   1,    '      Alfreds Futterkiste   ')INSERT INTO Customers(ID, CustomerDocument)VALUES(   2,    CAST('      Microsoft Corporation   ' AS xml))INSERT INTO Customers(ID, CustomerDocument)VALUES(   3,    CONVERT(xml, '      TechTalk   '))

You need to provide the target namespace in order to insert XML data to a typed XML datatype. Without the the target namespace, Yukon raises an exception and the data won’t be inserted. This guarantees that all inserted XML data is validated and matches the provided XML schema.

Creating Constraints
You can also create constraints during the creation of a table with the use of the T-SQL function check:

CREATE TABLE Customers(   Col1 int primary key,   Col2 xml check(      Col2::exist('/Customer/@CustomerID' = 1))-- The following insert operation succeedsINSERT INTO Customers (Col1, Col2)VALUES (1, '')-- The following insert operation didnt succeedsINSERT INTO Customers (Col1, Col2)VALUES (2, 'Alfreds Futterkiste')

You need to check the constraint in order to make sure that the attribute CustomerID exists in the inserted XML data. The following code shows the creation of a constraint that verifies that the column CustomerID has the same value as the attribute CustomerID of the XML element S:

CREATE TABLE Customers(   CustomerID int primary key,   CustomerDocument xml,   Constraint CustomerDocument_Check check   (      CustomerDocument::value('/Customer/@CustomerID',          'int') = CustomerID   ))-- The following insert operation succeedsINSERT INTO Customers (CustomerID, CustomerDocument)VALUES (1, '')-- The following insert operation didn't succeedsINSERT INTO Customers (CustomerID, CustomerDocument)VALUES (2, '')

XML Datatype Methods
With the help of these four functions, you can now embed XQuery statements directly into T-SQL code:

  • modify(): This method updates XML data. Since these updates also use the XML Data Modification Language (XML DML), I’ll disuss this method later in more detail.
  • query(): This method executes a XQuery statement on an XML datatype. The result is an untyped XML fragment:
    DECLARE @myDoc xmlSET @myDoc ='                  1 year         3 years         'SELECT @myDoc::query('/Root/ProductDescription/Features')-- Result   1 year   3 years/Maintenance>

    These functions are not limited to use on XML variables. The following code shows how to use query() on a XML column:

    USE AdventureWorksSELECT CatalogDescription::query('   namespace PD="http://www.adventure-works.com/   schemas/products/description"   )' AS ResultFROM ProductModelWHERE CatalogDescription::exist('namespace PD="   http://www.adventure-works.com/schemas/product/   description" namespace wm="http://www.adventure-works.   com/schemas/products/WarrAndMain"   /PD:ProductDescription/PD:Features/wm:Warranty') = 1-- Result

    In the previous code, the column CatalogDescription is a typed XML column. Because it is, you need to provide the XML namespace names in the XQuery statements. This query constructs the XML element , which consists of the attribute ProductIDS. From the column CatalogDescription, you only return the elements that contain the element. This example shows very nicely how T-SQL code can be combined with XQuery statements.

  • value(): This method returns a native SQL Server type like datetime, varchar, or int. Here’s what this looks like:
    USE AdventureWorksSELECT CatalogDescription::value('namespace    PD="http://www.adventure-works.com/schemas/products/   deescription"   /PD:ProductDescription/@ProductModelID', 'int')   AS ResultFROM ProductModelORDER BY Result DESC

    value() expects a XQuery statement in the first parameter and the native SQL Server datatype to which the result should be converted in the second parameter. The following table shows how the most important XSD datatypes are mapped to the SQL Server datatypes:

    XSD datatype

    SQL Server datatype

    string

    nvarchar(4000)

    boolean

    bit

    decimal

    numeric

    float

    real

    double

    float

    duration

    varbinary

    datetime

    varbinary

    time

    varbinary

    date

    varbinary

  • exist(): This method determines if a specified node or value exists in the XML data. exist() returns 1 (true) if the node/value exists and returns 0 (false) if it doesn’t. The following code uses exist() to determine whether a element with the ID 50 exists in the XML data:
    DECLARE @x xml ('http://schemas.adventure-   works.com/ManufInstructions/ByProdModel') SELECT @x = Instructions FROM ProductPlanWHERE ProductModelID = 67 DECLARE @f intSET @f = @x::exist('namespace AWMI="   http://schemas.adventure-works.com/   ManufInstructions/ByProdModel"   /AWMI:root/AWM:WorkCenter[@WorkCenterID=50]') SELECT @f

XQuery Extended
You can see that Yukon uses XQuery statements to produce some very powerful T-SQL code. But what if you want to reference another column or variable within your XQuery statement? The XQuery specification doesn’t provide any support for this. For that reason, Microsoft has extended XQuery with two important functions:

  • sql:column(): This function accesses columns within your XQuery statement. To do this, your column name must be fully qualified with the table name, for instance Customer.CustomerID. Take a look:
    USE AdventureWorksSELECT P.ProductID, CatalogDescription::query('   namespace pd="http://www.adventure-works.com/   schemas/products/description"         {         if (not(empty(/pd:ProductDescription)))         then            attribute ProductModelName            {               /pd:ProductDescription[1]/               @ProductModelName            }         else ()      }   ' AS ResultFROM ProductModel PM, Product PWHERE PM.ProductModelID = P.ProductModelIDORDER BY PM.ProductModelID-- Result...

    Notice that the values of the attributes ProductID, ProductName, ProductPrice, and ProductModelID are taken from the corresponding columns in the tables Product and ProductModel. In contrast to this, the attribute ProductModelName is taken from the XML column CatalogDescription.

  • sql:variable(): This function references a variable (like in a stored procedure) within the XQuery statement. Here’s how to use this function:
    USE AdventureWorksDECLARE @price moneySET @price = 2500.00DECLARE @x xmlSELECT @x = CatalogDescription::query('   namespace pd="http://www.adventure-works.com/schemas/   products/description"   'FROM Product, ProductModelWHERE Product.ProductModelID =    ProductModel.ProductModelID AND ProductID = 168SELECT @x-- Result

The XML Data Modification Language (XML DML)
What if you want to alter XML data stored within Yukon? In SQL 200, you had to load the data from the database and pass it to a middle-tier component. The component then changed the data and wrote the changes back to the database.

Eliminating this step, Microsoft has extended the XQuery specification with the XML Data Modification Language. Based on XQuery, this language provides the necessary functionality to change XML data directly on the database tier.

Access the XML Data Modification Language using the XML data method modify(). The XML DML uses three keywords, insert, delete, and update.

  • insert: Take a look at the syntax of the insert keyword:
    insert Expression1(   {as first | as last} into | after | before   Expression2)

    The following table describes each parameter:

    Parameter

    Description

    Expression1

    References the nodes that should be inserted. This can be an XQuery statement or a static XML fragment.

    into

    The XML nodes specified through Expression1 are inserted directly as children to the XML nodes specified by Expression2.

    as first

    Indicates that the nodes of Expression1 are inserted first into the nodes specified by Expression2.

    as last

    Indicates that the nodes of Expression1 are inserted last into the nodes specified by Expression2.

    after

    Indicates that the nodes of Expression1 are inserted directly after the nodes specified by Expression2 as siblings.

    before

    Indicates that the nodes of Expression1 are inserted directly before the nodes specified by Expression2 as siblings.

    Expression2

    Specifies the XML nodes in which the nodes from Expression1 should be inserted. This is an XQuery statement.

    The following code shows how to use the insert keyword:

    DECLARE @myDoc xmlSET @myDoc = '                  'SELECT @myDocSET @myDoc::modify('insert 3 years    into /Root/ProductDescription/   Features')SELECT @myDocSET @myDoc::modifiy('insert 1 year    as first into    /Root/ProductDescription/Features')SELECT @myDocSET @myDoc::modify('insert Aluminum    as last into    /Root/ProductDescription/Features')SELECT @myDocSET @myDoc::modify('insert Strong    after    /Root/ProductDescription/Features/Material')SELECT @myDoc

    The attribute attribute of the insert keyword allows you to insert new attributes into XML data:

    DECLARE @myDoc xmlSET @myDoc =         Step 1      Step 2   SELECT @myDocSET @myDoc::modify(insert attribute LaborHours {.5}    into /Root/WorkCenter[@WorkCenterID=10])SELECT @myDocDECLARE @Hrs floatSET @Hrs = .2SET @myDoc::modify(insert attribute MachineHours   {sql:variable(@Hrs)} into   /Root/WorkCenter[@WorkCenterID=10])SELECT @myDocSET @myDoc::modify(insert   (      attribute SetupHours {.5 },      attribute SomeOtherAtt {.2 }   )   into /Root/WorkCenter[@WorkCenterID=10])SELECT @myDoc
  • delete: This keyword, naturally, deletes nodes from your XML data. This keyword expects only one parameter: an XQuery statement that selects the nodes to be deleted. Here’s how to use it:
    DECLARE @myDoc xmlSET @myDoc = '            Some text 1      Step 1      Step 2   'SELECT @myDocSET @myDoc::modify('delete /Root/WorkCenter/step[2]')SELECT @myDocSET @myDoc::modify('delete    /Root/WorkCenter/@MachineHours')SELECT @myDocSET @myDoc::modify('delete /Root/WorkCenter/text()')SELECT @myDocSET @myDoc::modify('delete //processing-instruction()')SELECT @myDoc
  • update: As to be expected, this keyword updates XML data nodes:
    update [value of]   Expression1to   Expression2

    The following table describes each parameter in more detail:

    Parameter

    Description

    Expression1

    Specifies the XML node that should be updated. The specified XQuery statement must return only 1 node. If 0 or more than 1 nodes are returned an exception is raised.

    Expression2

    Specifies the new value of the node specified by Expression1. This can be also an XQuery statement.

    [value of]

    Optional parameter. If this parameter is used then Expression2 is not treated as an XQuery statement, but as plain XML data.

    The following code shows how to use this keyword:

    DECLARE @myDoc xmlSET @myDoc = '         Manu steps are described here      Step 1      Step 2   'SELECT @myDocSET @myDoc::modify('update    /Root/WorkCenter/step[1]/text() to "new text    describing the manu step"')SELECT @myDocSET @myDoc::modify('update /Root/WorkCenter/@LaborHours    to "100.0"')SELECT @myDoc

Yukon’s XML Limitations
As you’ve seen, SQL Server Yukon is first class for storing, querying, and updating XML Data. However, there are some some limitations when working with the new XML datatype and XML schemas:

  1. XML can’t be casted to text or ntext.
  2. An XML column can’t be part of a primary?or foreign key constraint.
  3. Only strings can be casted to XML.
  4. An XML column can’t be used in a GROUP BY statement.
  5. An XML column can’t be part of an index.
  6. It is only possible to create 32 XML columns per table.

The XML schema support in Yukon has the following limitations:

  1. Annotations (like comments) are not stored in the metasystem of Yukon.
  2. The XSD ID attribute is not supported.
  3. Default values can’t be longer than 4000 unicode characters.
  4. XML schemas can’t be converted to their origin state. Therefore, you need to manage XML schemas separately.

Even with these limitations, the ability to directly query and modify XML data on the database tier is a great step forward. If you work with XML data in your applications, it is well worth your time to investigate Yukon and find out whether you can benefit from these new features.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

More From DevX