AnnualSales=”{$N/AnnualSales}” SalesPlusRevenue=”{$Total}”/> ‘ FROM Sales.Store WHERE Demographics.exist‘//AnnualRevenue[xs:integer.=300000]’ = 1 The where Clause Just like the WHEREclau
Trang 1for $outer in /outernode,
$inner in $outer/innernode
return
<Outside letter=”{$outer/@name}”>
<Inside number=”{$inner}”/>
</Outside>
‘)
go
<Outside letter=”a”>
<Inside number=”1” />
</Outside>
<Outside letter=”a”>
<Inside number=”2” />
</Outside>
<Outside letter=”a”>
<Inside number=”3” />
</Outside>
<Outside letter=”b”>
<Inside number=”4” />
</Outside>
<Outside letter=”b”>
<Inside number=”5” />
</Outside>
<Outside letter=”b”>
<Inside number=”6” />
</Outside>
The let Clause New to SQL Server 2008, the letclause is the L in FLWOR It performs
the critical function of enabling variable value assignments in XQuery expressions Here’s
an example:
WITH XMLNAMESPACES
(
DEFAULT
‘http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey’
)
SELECT TOP 1
Demographics.query(
‘
for $N in /StoreSurvey
let $Total := ($N/AnnualRevenue + $N/AnnualSales)
order by $N/AnnualSales
return
<Statement
AnnualRevenue=”{$N/AnnualRevenue}”
Trang 2AnnualSales=”{$N/AnnualSales}”
SalesPlusRevenue=”{$Total}”/>
‘)
FROM Sales.Store
WHERE Demographics.exist(‘(//AnnualRevenue[xs:integer(.)=300000])’) = 1
The where Clause Just like the WHEREclause in T-SQL, XQuery’s whereclause restricts the
nodes in the selected node list to those matching a certain expression Here’s an example:
SELECT TOP 1 Resume.query(‘
declare namespace
ns=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume”;
for $ResumeNode in /ns:Resume
where count($ResumeNode/ns:Employment) > 2
return
$ResumeNode/ns:Employment/ns:Emp.JobTitle
‘)
FROM HumanResources.JobCandidate
go
<ns:Emp.JobTitle
xmlns:ns=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume”>
Lead Machinist
</ns:Emp.JobTitle>
<ns:Emp.JobTitle
xmlns:ns=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume”>
Machinist
</ns:Emp.JobTitle>
<ns:Emp.JobTitle
xmlns:ns=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume”>
Assistant Machinist
</ns:Emp.JobTitle>
Here, you use the T-SQL-analogouscount()aggregate function to restrict the result set
tons:Resumenodes having three or morens:Employmentchildren The standard
aggre-gate functions are available in XQuery expressions They aremax(),min(),avg(),sum(),
andcount().
The order by Clause Just like T-SQL’s ORDER BY, XQuery’s order byis used to reorder
the selected nodes from the default document order to a new order, based on an
expres-sion The order may be set to descendingorascending(the default).
Trang 3The following example casts a node value to an instance of the xs:datetype and orders
the results from most to least recent date:
SELECT Resume.query(‘
declare namespace
ns=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume”;
<Achievements>
{
for $EducationNode in //ns:Education
order by xs:date(string($EducationNode/ns:Edu.EndDate[1])) descending
return
<Degree>
<DateAwarded>
{ string($EducationNode/ns:Edu.EndDate[1]) }
</DateAwarded>
<Name>
{ string($EducationNode/ns:Edu.Degree[1]) }
</Name>
</Degree>
}
</Achievements>
‘)
FROM HumanResources.JobCandidate
WHERE JobCandidateId = 2
go
<Achievements>
<Degree>
<DateAwarded>1997-06-03Z</DateAwarded>
<Name>Bachelor of Science</Name>
</Degree>
<Degree>
<DateAwarded>1993-06-12Z</DateAwarded>
<Name>Diploma</Name>
</Degree>
</Achievements>
The expression xs:date(string($EducationNode/ns:Edu.EndDate[1]))requires some
explanation Working from the inside out: ns:Edu.EndDateis selected, using the child
node of the node stored in the bound context variable $EducationNode For the string()
typecasting function to work, a singleton, or single node, must be specified; this is why the
positional predicate [1]must be specified Finally, the string is cast to xs:date (Note that
in the returnstatement, the string value of the same node is used.)
This example illustrates not only the type-related aspects of FLWOR expressions, but also
the capability to generate a root node without using FOR XML ROOT All that is required
is that a root node (in this case, Achievements), followed by curly braces, surround the
entire FLWOR statement.
Trang 4The return Clause Similar to T-SQL’s SELECTstatement, the returnclause executes once
for every selected context node This is the section where you specify the structure and
content of the resulting XML The key aspect of it is the use of node constructors.
TIP
When using attribute constructors in thereturnclause, you need to make sure your
curly braces are directly adjacent to the attribute’s begin and end quotes, with no
whitespace in between (for example,attribute=”{$Node}”), or SQL Server raises an
error The reason is that string literals (even blank spaces) cannot be mixed with
attribute constructors.
Put simply, constructors create the nodes and node values to be output There are two
types of constructors:
Computed constructors—These are placed inside curly-braced expressions and
evaluated against the context node (for example, attribute=”{$N}”).
Direct constructors—These are constant node strings used in the FLWOR statement
(for example,<Achievements>) Listing 47.16 illustrates a variety of constructors.
LISTING 47.16 Using XQuery Constructors
SELECT Resume.query(‘
declare namespace
ns=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume”;
for $N in //ns:Education
return
<NodeConstructor attributeConstructor=”{string($N/ns:Edu.School[1])}”>
{ $N/ns:Edu.Major }
<?PI processing-instruction constructor PI?>
<! comment constructor >
</NodeConstructor>
‘)
FROM HumanResources.JobCandidate
WHERE JobCandidateId = 1
go
<NodeConstructor attributeConstructor=”Midwest State University”>
<ns:Edu.Major
xmlns:ns=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume”>
Mechanical Engineering
</ns:Edu.Major>
<?PI processing-instruction constructor PI?>
Trang 5<! comment constructor >
</NodeConstructor>
Exactly the same XML result can be generated a third way: using the alternative
node-type-name constructors (for example, element,attribute,text) in a comma-delimited list
within curly braces Here’s an example:
SELECT Resume.query(‘
declare namespace
ns=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume”;
for $N in //ns:Education
return
element NodeConstructor
{
attribute attributeConstructor { string($N/ns:Edu.School[1])},
text { string($N/ns:Edu.Major[1]) },
<?PI processing-instruction constructor PI?>,
<! comment constructor >
}
‘)
FROM HumanResources.JobCandidate
WHERE JobCandidateId = 1
TIP
Becausequery()returns an instance of xml, the xmldata type methods can be
stacked on its result, allowing for powerful XQuery subqueries, such as
query(‘’).query(‘’).exist(‘’).
Testing XML by Using exist()
A common task when working with XML is the need to check for the existence of a node
or node value The exist()method does just that, returning 1if the node test returns
nonempty, or 0if empty.
Listing 47.17 tests whether the annual revenue of a surveyed store exceeds $100,000.
LISTING 47.17 Using exist() to Test for a Specific Node Value
WITH XMLNAMESPACES
(
DEFAULT
‘http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey’
)
SELECT Demographics.query(
Trang 6‘
for $N in /StoreSurvey
order by $N/AnnualSales
return
if ($N/AnnualSales >= 3000000)
then
<Money
Bank=”{$N/BankName}”
AnnualRevenue=”{$N/AnnualRevenue}”
AnnualSales=”{$N/AnnualSales}”
Comments=”really big bucks”/>
else
<Money
AnnualRevenue=”{$N/AnnualRevenue}”
AnnualSales=”{$N/AnnualSales}”
Comments=”big bucks”/>
‘)
FROM Sales.Store
WHERE Demographics.exist(‘
(//AnnualRevenue[xs:integer(.)>100000])
‘) = 1
go
<p1:Money
xmlns:p1=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey” AnnualRevenue=”150000” AnnualSales=”1500000”
Comments=”big bucks” />
<p1:Money
xmlns:p1=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey” AnnualRevenue=”150000” AnnualSales=”1500000”
Comments=”big bucks” />
<p1:Money
xmlns:p1=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey” AnnualRevenue=”150000” AnnualSales=”1500000”
Comments=”big bucks” />
<p1:Money
xmlns:p1=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey” AnnualRevenue=”150000” AnnualSales=”1500000”
Comments=”big bucks” />
<p1:Money
xmlns:p1=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey” Bank=”International Bank” AnnualRevenue=”300000”
AnnualSales=”3000000” Comments=”really big bucks” />
{ }
Listing 47.17 also illustrates the use of the XQuery if-then-elseconstruct, which is used
to conditionally generate the BankNameattribute and change the value of the Comments
attribute In the WITH XMLNAMESPACESstatement that precedes the query, you use the
DEFAULTkeyword to specify a default namespace for the selection.
Trang 7Converting a Node Value to a T-SQL Data Type by Using value()
Thevalue()function allows for a selected node value to be cast to a T-SQL–data typed
value It has two parameters: the first is a string-literal XPath expression that selects the
desired node value The second is a string-literal T-SQL data type name.
The code in Listing 47.18 queries an Extensible Application Markup Language (XAML)
document by using value()to select the Heightattribute of Canvasnodes and cast them
to decimal Notice that the returned results are rows rather than XML.
LISTING 47.18 Using value() to Retrieve and Convert a Node Value
WITH XMLNAMESPACES
(
‘http://schemas.microsoft.com/winfx/2006/xaml’ as x,
DEFAULT ‘http://schemas.microsoft.com/winfx/2006/xaml/presentation’
)
SELECT
IllustrationID,
Diagram.value(‘(//Canvas/@Height)[1]’, ‘decimal(16,4)’) HeightAsSQLDecimal
FROM Production.Illustration
go
IllustrationID HeightAsSQLDecimal
-
-3 147.7061
4 314.9819
5 105.7393
6 213.6152
7 177.5449
(5 row(s) affected)
Accessing Relational Columns and T-SQL Variables in XQuery Expressions Besidesvalue(),
two other bridges between T-SQL and XQuery are the XQuery functions sql:column()and
sql:variable().
sql:column(), as the name implies, allows for the selection of a relational column value
in a FLWOR statement In Listing 47.19, contact name data is pulled from Person.Person
into an XQuery element constructor and then selected back out again as a node value In
addition, the value of the declared T-SQL variable TotalPurchaseYTDis compared against
the value of the node of the same name in the XQuery whereclause, using
sql:variable().
LISTING 47.19 Using sql:column() and sql:variable() in XQuery
DECLARE @TotalPurchaseYTD decimal(6,2)
SET @TotalPurchaseYTD = 0
Trang 8SELECT Demographics.query(‘
declare default element namespace
“http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/IndividualSurvey”;
for $IS in /IndividualSurvey
where $IS/TotalPurchaseYTD[.= sql:variable(“@TotalPurchaseYTD”)]
return
element Contact
{
attribute ID { sql:column(“C.BusinessEntityID”) },
attribute YTDTotal { sql:variable(“@TotalPurchaseYTD”) },
element FullName { concat(sql:column(“FirstName”), “ “,
sql:column(“LastName”)) }
}
‘)
FROM Sales.SalesPerson I
JOIN Person.Person C ON
C.BusinessEntityID = I.BusinessEntityID
AND C.BusinessEntityID = 285
concat()is one of several string functions built into XQuery, in addition to contains(),
substring(), and string-length().
Using the nodes() Method to Shred XML
In the section “XML as Relational Data: Using OPENXML,” earlier in this chapter, you
learned how to decompose XML directly into relational rows that could be mapped to
values in existing tables or used any other T-SQL way.
nodes()is kind of likeOPENXML’s big brother: given an XML input document and an
XQuery expression, it generates a table with anxmlcolumn against which subsequent
XQuery queries can be run.nodes()can be applied to bothxmlvariables andxmlcolumns.
Each row in the generated table contains a copy of the original input content The context
node for each row is based on the XQuery expression parameter It is possible to shred the
input in multiple ways by running multiple XQuery queries on the generated column in
the same SELECTstatement For example, one query might return a relational value from
each context node, using the value()method Another could transform and return each
content node to a different XML schema.
Let’s examine a simple example that shows how this works Listing 47.20 illustrates how
an XML document is shredded into relational rows and columns by applying six different
XQuery queries on each generated row, each of which creates a new relational column.
Trang 9LISTING 47.20 Shredding XML Six Ways, Using nodes()
DECLARE @XmlVar xml
SET @XmlVar = ‘
<alphnumerics>
<item>
<alph name=”A” val=”65”/>
</item>
<item>
<alph name=”B” val=”66”/>
</item>
<item>
<alph name=”C” val=”67”/>
</item>
<item>
<num name=”1” val=”49”/>
</item>
<item>
<num name=”2” val=”50”/>
</item>
<item>
<num name=”3” val=”51”/>
</item>
</alphnumerics>’
SELECT
XmlTable.XmlColumn.query(‘alph’) AS ANode,
XmlTable.XmlColumn.value(‘alph[1]/@name’, ‘char(1)’) AS AName,
XmlTable.XmlColumn.value(‘alph[1]/@val’, ‘int’) AS AVal,
XmlTable.XmlColumn.query(‘num’) AS NNode,
XmlTable.XmlColumn.value(‘num[1]/@name’, ‘int’) AS NName,
XmlTable.XmlColumn.value(‘num[1]/@val’, ‘int’) AS NVal
FROM @XmlVar.nodes(‘/alphnumerics/item’) AS XmlTable(XmlColumn)
The syntax of nodes()is as follows:
nodes(XQuery) AS GeneratedTableName(GeneratedXmlColumnName)
Note that it is not possible to directly select the xmlcolumn generated by nodeswithout
using one of the xmldata type methods Using the XML from the preceding example, the
following code would raise an error:
SELECT XmlTable.XmlColumn
FROM @XmlVar.nodes(‘/alphnumerics/item’) AS XmlTable(XmlColumn)
You can also usenodes()withCROSS APPLYorOUTER APPLYto executenodes()once for
every row returned in the outer table In this way, you can combine relational data with
multiple XQuery queries against a relational rowset Listing 47.21 illustrates this technique.
Trang 10LISTING 47.21 Using nodes() with CROSS APPLY
WITH XMLNAMESPACES(
‘http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume’ as ns
)
SELECT
JC.JobCandidateId,
E.BusinessEntityID,
ResumeTable.XmlColumn.value(‘ns:Emp.JobTitle[1]’, ‘nchar(50)’) JobTitle
FROM HumanResources.JobCandidate JC
CROSS APPLY JC.Resume.nodes(‘
/ns:Resume/ns:Employment[2]
‘) as ResumeTable(XmlColumn)
JOIN HumanResources.Employee E ON
E.BusinessEntityID = JC.BusinessEntityID
go
JobCandidateId EmployeeId JobTitle
-4 27 -4 Sales Associate
8 212
(2 row(s) affected.)
Using modify() to Insert, Update, and Delete XML
A frequent requirement when working with XML is the insertion, deletion, and
modifi-cation of nodes and node values These operations are known as XML Data Modifimodifi-cation
Language (XML DML) statements, and they are supported by thexmldata type’s
modify()method.
When you are working with typed XML, modify()performs type and structural checks
that allow operations to succeed only if they result in valid XML, so it’s important to
know your schema well.
When document order is important, it’s also crucial to know the exact location and
posi-tion of the nodes or values to be changed In the case of untyped or loosely constrained
typed XML, it may not matter all that much where a new node is placed.
XQuery provides a few functions and operators related to node order position()returns
the numeric position of a node (starting at 1).last()returns the numeric position of the
last node in a selected node list They are both performed against a context node.
In addition, you can use the node order comparison operators <<and>>to compare the
relative positions of two selected nodes The Boolean isoperator is also provided to test
whether two selected nodes are actually the same node.
modify()allows for three main operations in its XQuery expression parameter: insert,
replace value of, and delete Let’s look at deletefirst.