Академический Документы
Профессиональный Документы
Культура Документы
With SQL Server 2005 we can generate XML output using different methods.
Using TSQL keyword FOR XML along with AUTO, RAW, PATH and EXPLICIT we
could generate almost any XML structure that we might need. PATH is a very
powerful keyword which allows a great deal of customization on the structure
of the generated XML and is relatively easy to use. EXPLICIT provides more
control over the generated XML structure but it is much more complex then
other methods. Most of the times, we could generate the same output as
EXPLICIT by using PATH. But some times, the structure of the XML output
might be too complex for PATH to generate, and we will have to go with
EXPLICIT. PATH is available only in SQL Server 2005. If you are working with
SQL server 2000, you will have to work with EXPLICIT if you need control
over the XML structure being generated.
I had been helping some people on writing TSQL queries with EXPLICIT
recently, at some of the Internet forums. My observation is that most of the
times people get an error because of the sort order of the result set being
passed to FOR XML EXPLICIT I worked with Vimal Rughani recently on such
a query. After we wrote the query, he asked me if I could explain the flow of
the code. I thought that it would be a good idea to write down the steps I
went through while writing the query, so that it will help other people around
too. He wanted to generate the following XML output using FOR XML
EXPLICIT.
<Agents>
<Agent AgentID="1">
<Fname>Vimal</Fname>
<SSN>123-23-4521</SSN>
<AddressCollection>
<Address>
<AddressType>Home</AddressType>
<Address1>abc</Address1>
<Address2>xyz road</Address2>
<City>RJ</City>
</Address>
<Address>
<AddressType>Office</AddressType>
<Address1>temp</Address1>
<Address2>ppp road</Address2>
<City>RJ</City>
</Address>
</AddressCollection>
</Agent>
<Agent AgentID="2">
<Fname>Jacob</Fname>
<SSN>321-52-4562</SSN>
<AddressCollection>
<Address>
<AddressType>Home</AddressType>
<Address1>xxx</Address1>
<Address2>aaa road</Address2>
<City>NY</City>
</Address>
<Address>
<AddressType>Office</AddressType>
<Address1>ccc</Address1>
<Address2>oli Com</Address2>
<City>CL</City>
</Address>
<Address>
<AddressType>Temp</AddressType>
<Address1>eee</Address1>
<Address2>olkiu road</Address2>
<City>CL</City>
</Address>
</AddressCollection>
</Agent>
<Agent AgentID="3">
<Fname>Tom</Fname>
<SSN>252-52-4563</SSN>
<AddressCollection>
<Address>
<AddressType>Home</AddressType>
<Address1>ttt</Address1>
<Address2>loik road</Address2>
<City>NY</City>
</Address>
</AddressCollection>
</Agent>
</Agents>
The data should come from two tables Agents and Addresses. Before we
write the query, we need to create those tables and populate them with
some data. For the purpose of this example, we may not need any physical
tables. We could go with memory tables. The following code will create two
memory tables and fill them with data. The code is written by Kent in one of
the MSDN forums.
/*
Borrowed from Kent's code
*/
declare @agent table
(
AgentID int,
Fname varchar(5),
SSN varchar(11)
)
insert into @agent
select 1, 'Vimal', '123-23-4521' union all
select 2, 'Jacob', '321-52-4562' union all
select 3, 'Tom', '252-52-4563'
declare @address table
(
AddressID int,
AddressType varchar(12),
Address1 varchar(20),
Address2 varchar(20),
City varchar(25),
AgentID int
)
insert into @address
select 1, 'Home', 'abc', 'xyz road', 'RJ', 1 union all
select 2, 'Office', 'temp', 'ppp road', 'RJ', 1 union all
select 3, 'Home', 'xxx', 'aaa road', 'NY', 2 union all
select 4, 'Office', 'ccc', 'oli Com', 'CL', 2 union all
select 5, 'Temp', 'eee', 'olkiu road', 'CL', 2 union all
select 6, 'Home', 'ttt', 'loik road', 'NY', 3
Let us start writing the query. Because we write this query for learning
purpose, I would like to take an approach by which we will progressively
develop the complete query.
So let us start with the root node. Let us first create the query for generating
the root node.
SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'Agents!1!'
FOR XML EXPLICIT
<Agents />
Now let us write the code for generating next level. The next level is the
agent node. This information should come from the agent table. Let us add
the code for that.
SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'Agents!1!',
NULL AS 'Agent!2!AgentID'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
NULL,
AgentID
FROM @agent
FOR XML EXPLICIT
Note the code in yellow. This is what we added to the previous version. This
query generates the following output.
<Agents>
<Agent AgentID="1" />
<Agent AgentID="2" />
<Agent AgentID="3" />
</Agents>
Good so far. Let us add fname and ssn under the agent node as child
elements.
SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'Agents!1!',
NULL AS 'Agent!2!AgentID',
NULL AS 'Agent!2!Fname!Element',
NULL AS 'Agent!2!SSN!Element'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
NULL,
AgentID,
Fname,
SSN
FROM @agent
FOR XML EXPLICIT
<Agents>
<Agent AgentID="1">
<Fname>Vimal</Fname>
<SSN>123-23-4521</SSN>
</Agent>
<Agent AgentID="2">
<Fname>Jacob</Fname>
<SSN>321-52-4562</SSN>
</Agent>
<Agent AgentID="3">
<Fname>Tom</Fname>
<SSN>252-52-4563</SSN>
</Agent>
</Agents>
SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'Agents!1!',
NULL AS 'Agent!2!AgentID',
NULL AS 'Agent!2!Fname!Element',
NULL AS 'Agent!2!SSN!Element',
NULL AS 'AddressCollection!3!Element'
UNION ALL
SELECT
2 AS Tag, 1 AS Parent,
NULL, AgentID, Fname, SSN,
NULL
FROM @agent
UNION ALL
SELECT
3 AS Tag, 2 AS Parent,
NULL, NULL, NULL, NULL,
NULL
FROM @agent
FOR XML EXPLICIT
We added a new level for AddressCollection element. I used FROM @agent
because we need an AddressCollection element for each agent record. Here
is the output.
<Agents>
<Agent AgentID="1">
<Fname>Vimal</Fname>
<SSN>123-23-4521</SSN>
</Agent>
<Agent AgentID="2">
<Fname>Jacob</Fname>
<SSN>321-52-4562</SSN>
</Agent>
<Agent AgentID="3">
<Fname>Tom</Fname>
<SSN>252-52-4563</SSN>
<AddressCollection />
<AddressCollection />
<AddressCollection />
</Agent>
</Agents>
Note the rows in red. These are the records with tag 3. Note that they appear
at the bottom of the result set. That is the reason why they appear at the
bottom of the XML result. So to fix this, we need to change the order of the
rows. So to get the correct XML we need to have the query results in the
following order.
So at this stage, we need to write some kind of code to alter the sort order of
the records. There might be different ways to do that. What I did was to add
a calculated column for the sort order based on the AgentID. Here is the new
code.
SELECT
1 AS Tag,
NULL AS
Parent,
0 AS Sort,
NULL AS
'Agents!1!',
NULL AS
'Agent!2!AgentID',
NULL AS
'Agent!2!Fname!Element',
NULL AS
'Agent!2!SSN!Element',
NULL AS
'AddressCollection!3!Element'
UNION ALL
SELECT
2 AS
Tag, 1 AS Parent,
AgentID * 100 AS
Sort,
NULL, AgentID, Fname, SSN,
NULL
FROM @agent
UNION ALL
SELECT
3 AS Tag, 2 AS
Parent,
AgentID * 100 + 1 AS Sort,
Well, that worked. Note that the sort order holds correct values so that we
get the records in the desired order. There might be different ways to
generate the sort order column. For the purpose of this example, I made it
by multiplying the AgentID with 100, 101 etc. This approach may not work in
a different situation. It worked for the example. The KEY here is to sort the
records in the correct order (exactly in the order that we need them in the
XML results). You can apply your own logic that you feel right, to achieve
this. Let us generate the XML now.
SELECT
1 AS Tag,
NULL AS Parent,
0 AS Sort,
NULL AS 'Agents!1!',
NULL AS 'Agent!2!AgentID',
NULL AS 'Agent!2!Fname!Element',
NULL AS 'Agent!2!SSN!Element',
NULL AS 'AddressCollection!3!Element'
UNION ALL
SELECT
2 AS Tag, 1 AS Parent,
AgentID * 100 AS Sort,
NULL, AgentID, Fname, SSN,
NULL
FROM @agent
UNION ALL
SELECT
3 AS Tag, 2 AS Parent,
AgentID * 100 + 1 AS Sort,
NULL, NULL, NULL, NULL,
NULL
FROM @agent
ORDER BY Sort
FOR XML EXPLICIT
unfortunately, this code will not work. If you try to run this, you will get the
following error.
The error is caused by the "Sort" column that we just added. When we use
FOR XML EXPLICIT, all columns other than "Tag" and "Parent" should be in
the form of "[TAG]![TAGID]!ATTRIBUTE...". We need to hide the "Sort"
column. Lets create an outer query to do this.
SELECT
Tag,
Parent,
[Agents!1!],
[Agent!2!AgentID],
[Agent!2!Fname!Element],
[Agent!2!SSN!Element],
[AddressCollection!3!Element]
FROM (
SELECT
1 AS Tag,
NULL AS Parent,
0 AS Sort,
NULL AS 'Agents!1!',
NULL AS 'Agent!2!AgentID',
NULL AS 'Agent!2!Fname!Element',
NULL AS 'Agent!2!SSN!Element',
NULL AS 'AddressCollection!3!Element'
UNION ALL
SELECT
2 AS Tag, 1 AS Parent,
AgentID * 100 AS Sort,
NULL, AgentID, Fname, SSN,
NULL
FROM @agent
UNION ALL
SELECT
3 AS Tag, 2 AS Parent,
AgentID * 100 + 1 AS Sort,
NULL, NULL, NULL, NULL,
NULL
FROM @agent
)A
ORDER BY Sort
FOR XML EXPLICIT
Having fixed the problem with the sort order, let us go ahead with the rest of
the code. Let us add Addresses under the AddressCollection node and come
up with the final version of the code. We need to add a new level, Tag 4.
Note that I used AgentID * 102 to make sure that this record will come right
below the AddressCollection row of each Agent.
/*
Borrowed from Kent's code
*/
declare @agent table
(
AgentID int,
Fname varchar(5),
SSN varchar(11)
)
insert into @agent
select 1, 'Vimal', '123-23-4521' union all
select 2, 'Jacob', '321-52-4562' union all
select 3, 'Tom', '252-52-4563'
declare @address table
(
AddressID int,
AddressType varchar(12),
Address1 varchar(20),
Address2 varchar(20),
City varchar(25),
AgentID int
)
insert into @address
select 1, 'Home', 'abc', 'xyz road', 'RJ', 1 union all
select 2, 'Office', 'temp', 'ppp road', 'RJ', 1 union all
select 3, 'Home', 'xxx', 'aaa road', 'NY', 2 union all
select 4, 'Office', 'ccc', 'oli Com', 'CL', 2 union all
select 5, 'Temp', 'eee', 'olkiu road', 'CL', 2 union all
select 6, 'Home', 'ttt', 'loik road', 'NY', 3
/*
End Borrow
*/
SELECT Tag, Parent,
[Agents!1!],
[Agent!2!AgentID],
[Agent!2!Fname!Element],
[Agent!2!SSN!Element],
[AddressCollection!3!Element],
[Address!4!AddressType!Element],
[Address!4!Address1!Element],
[Address!4!Address2!Element],
[Address!4!City!Element]
FROM (
SELECT
1 AS Tag,
NULL AS Parent,
0 AS Sort,
NULL AS 'Agents!1!',
NULL AS 'Agent!2!AgentID',
NULL AS 'Agent!2!Fname!Element',
NULL AS 'Agent!2!SSN!Element',
NULL AS 'AddressCollection!3!Element',
NULL AS 'Address!4!AddressType!Element',
NULL AS 'Address!4!Address1!Element',
NULL AS 'Address!4!Address2!Element',
NULL AS 'Address!4!City!Element'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
AgentID * 100,
NULL, AgentID, Fname, SSN,
NULL,NULL, NULL, NULL, NULL
FROM @Agent
UNION ALL
SELECT
3 AS Tag,
2 AS Parent,
AgentID * 100 + 1,
NULL,NULL,NULL, NULL,
NULL, NULL, NULL, NULL, NULL
FROM @Agent
UNION ALL
SELECT
4 AS Tag,
3 AS Parent,
AgentID * 100 + 2,
NULL,NULL,NULL,NULL,NULL,
AddressType, Address1, Address2, City
FROM @Address
)A
ORDER BY Sort
FOR XML EXPLICIT
Output:
<Agents>
<Agent AgentID="1">
<Fname>Vimal</Fname>
<SSN>123-23-4521</SSN>
<AddressCollection>
<Address>
<AddressType>Home</AddressType>
<Address1>abc</Address1>
<Address2>xyz road</Address2>
<City>RJ</City>
</Address>
<Address>
<AddressType>Office</AddressType>
<Address1>temp</Address1>
<Address2>ppp road</Address2>
<City>RJ</City>
</Address>
</AddressCollection>
</Agent>
<Agent AgentID="2">
<Fname>Jacob</Fname>
<SSN>321-52-4562</SSN>
<AddressCollection>
<Address>
<AddressType>Home</AddressType>
<Address1>xxx</Address1>
<Address2>aaa road</Address2>
<City>NY</City>
</Address>
<Address>
<AddressType>Office</AddressType>
<Address1>ccc</Address1>
<Address2>oli Com</Address2>
<City>CL</City>
</Address>
<Address>
<AddressType>Temp</AddressType>
<Address1>eee</Address1>
<Address2>olkiu road</Address2>
<City>CL</City>
</Address>
</AddressCollection>
</Agent>
<Agent AgentID="3">
<Fname>Tom</Fname>
<SSN>252-52-4563</SSN>
<AddressCollection>
<Address>
<AddressType>Home</AddressType>
<Address1>ttt</Address1>
<Address2>loik road</Address2>
<City>NY</City>
</Address>
</AddressCollection>
</Agent>
</Agents>
This is a late addition to the 3 part FOR XML TUTORIAL I wrote last year. You
can find Part 1 here, Part 2 here and Part 3 here.
When generating the XML document, FOR XML EXPLICIT processes rows in
the same order as they are returned by the query. So, most of the times, you
need to specify an ORDER BY clause in your query, so that the XML output
will contain information in the desired order. In Part 3, we used a calculated
column to generate certain values and used those values to order the result.
Since we did not want the 'artificial sort column' in the XML output, we used
an outer query to filter out the sort column.
Here is the new version of the query using the 'hide' directive.
SELECT
1 AS Tag,
NULL AS Parent,
0 AS 'Agents!1!Sort!hide',
NULL AS 'Agents!1!',
NULL AS 'Agent!2!AgentID',
NULL AS 'Agent!2!Fname!Element',
NULL AS 'Agent!2!SSN!Element',
NULL AS 'AddressCollection!3!Element',
NULL AS 'Address!4!AddressType!Element',
NULL AS 'Address!4!Address1!Element',
NULL AS 'Address!4!Address2!Element',
NULL AS 'Address!4!City!Element'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
AgentID * 100,
NULL, AgentID, Fname, SSN,
NULL,NULL, NULL, NULL, NULL
FROM @Agent
UNION ALL
SELECT
3 AS Tag,
2 AS Parent,
AgentID * 100 + 1,
NULL,NULL,NULL, NULL,
NULL, NULL, NULL, NULL, NULL
FROM @Agent
UNION ALL
SELECT
4 AS Tag,
3 AS Parent,
AgentID * 100 + 2,
NULL,NULL,NULL,NULL,NULL,
AddressType, Address1, Address2, City
FROM @Address
ORDER BY [Agents!1!Sort!hide]
FOR XML EXPLICIT
Note the usage of the "hide" directive on the column we generated for
sorting. Columns marked with "hide" will be ignored by the XML processor.
FOR XML EXPLICIT supports a few other interesting directives too. I will cover
them in a future post.
FOR XML PATH - How to remove the <row>
element from the output of a FOR XML PATH
query?
When you write a simple FOR XML query with PATH, you will see that a
<row> element will be generated for each row in the result set. For example:
/*
<Employees>
<row>
<Name>Jacob</Name>
</row>
<row>
<Name>Steve</Name>
</row>
</Employees>
*/
Note that a <node> element is created for each row in the query result.
PATH is a very powerful operator that allows a great deal of flexibility. Most
of the operations previously possible only with EXPLICIT is now possible with
PATH. Let us see a few simple variations of the above query and see how we
could control the format of the output.
I like XML and many of you might have noted the reflection of this likeness in
my posts. When I try to solve a problem, I usually look for an XML based
approach before trying any other method. This does not mean that the XML
approach is always superior. There are times when it is good and there are
times when an XML approach is not desirable.
I have experienced that TSQL loops are very expensive (usually). So most of
the times, if you can re-write a loop to a batch/set operation, you could get
performance benefits (well, most of the times. There are times when this
may not be true, but such cases are very rare).
There are two common string operations where I used to write a TSQL loop in
the SQL server 2000 era.
The XML enhancements added to SQL Server 2005 made both these
operations easier with XML. I think, most of the times these operations are
done in small pieces of data. Though you can do these operations on
extremely large data, I don't think it is advisable. There are other ways to
handle large chunks of data.
In this post, lets see how we could generate a delimited string using FOR
XML PATH. I have covered the other topic "How to split a delimited string" in
another post.
/*
CompanyID CompanyString
----------- -------------------------
1 1|2
2 1|2|3|4
3 1|2
*/
One option is to run a loop that constructs a delimited string for each
CompanyID. Another option is to create a function that returns a delimited
string for each company ID. I am presenting a third option using FOR XML
PATH.
SELECT CompanyID,
(SELECT
CompanyCode AS 'data()'
FROM @companies c2
WHERE c2.CompanyID = c1.CompanyID
FOR XML PATH('')) AS CompanyString
FROM @companies c1
GROUP BY CompanyID/*
CompanyID CompanyString
----------- ------------------------
1 12
2 1234
3 12
*/
The above query uses FOR XML PATH to return a SPACE delimited string
containing the company code of each row. But this is not the final result that
we need. We need a pipe separated list and hence we need to apply a
REPLACE() operation.
SELECT CompanyID,
REPLACE((SELECT
CompanyCode AS 'data()'
FROM @companies c2
WHERE c2.CompanyID = c1.CompanyID
FOR XML PATH('')), ' ', '|') AS CompanyString
FROM @companies c1
GROUP BY CompanyID
/*
CompanyID CompanyString
----------- -------------------------
1 1|2
2 1|2|3|4
3 1|2
*/
I just helped some one to solve another XML shaping problem in the MSDN
forums and thought of sharing it here, because the problem seems to be
common. Position of elements is significant in XML. Hence, some times we
need to control the position of elements being generated. We might need to
have a certain element placed before another. Most of the times when we
write a FOR XML EXPLICIT query, we might need to have elements placed in
a given order. I have discussed it in the FOR XML EXPLICIT tutorial given
here.
Source data
/*
id NameA NameB NameC
----------- ---------- ---------- ----------
1 Value A Value B Value C
*/
Current Query
SELECT
1 AS Tag,
NULL AS Parent,
ID AS [Tab!1!ID],
NameA AS [Tab!1!NameA!Element],
NULL AS [Tab2!2!NameB!Element],
NameC AS [Tab!1!NameC!Element]
FROM tbl
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
ID,
NULL AS [Tab!1!NameA!Element],
NameB AS [Tab!1!NameB!Element],
NULL AS [Tab!1!NameC!Element]
FROM tbl
FOR XML EXPLICIT
<Tab ID="1">
<NameA>Value A</NameA>
<NameC>Value C</NameC>
<Tab2>
<NameB>Value B</NameB>
</Tab2>
</Tab>
<Tab ID="1">
<NameA>Value A</NameA>
<Tab2>
<NameB>Value B</NameB>
</Tab2>
<NameC>Value C</NameC>
</Tab>
Corrected Solution
We have two options here. We could either go with FOR XML PATH or FOR
XML EXPLICIT (FOR XML PATH is available only on SQL server 2005 and
above). Here is the query that uses FOR XML EXPLICIT.
DECLARE @tmp TABLE (
id Int,
NameA VarChar(10),
NameB VarChar(10),
NameC VarChar(10)
)
SELECT
1 AS Tag,
NULL AS Parent,
ID AS [Tab!1!ID],
NameA AS [Tab!1!NameA!Element],
NULL AS [Tab2!2!NameB!Element],
NULL AS [NameC!3]
FROM @tmp
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
ID, NULL,
NameB, NULL
FROM @tmp
UNION ALL
SELECT
3 AS Tag,
1 AS Parent,
NULL, NULL, NULL,
NameC
FROM @tmp
FOR XML EXPLICIT
/*
<Tab ID="1">
<NameA>Value A</NameA>
<Tab2>
<NameB>Value B</NameB>
</Tab2>
<NameC>Value C</NameC>
</Tab>
*/
SELECT
id AS '@ID',
NameA AS 'NameA',
NameB AS 'Tab2/NameB',
NameC AS 'NameC'
FROM @tmp
FOR XML PATH('Tab')
<Tab ID="1">
<NameA>Value A</NameA>
<Tab2>
<NameB>Value B</NameB>
</Tab2>
<NameC>Value C</NameC>
</Tab>
Note that the query that uses FOR XML PATH is much simpler than the
version using FOR XM EXPLICIT.
DECLARE @t TABLE (
id INT, Name1 VARCHAR(20),
Value1 VARCHAR(20), Name2 VARCHAR(20),
Value2 VARCHAR(20))
<T2Method>
<Parameter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>PrimaryID</Name>
<Value xsi:nil="true" />
</Parameter>
<Parameter>
<Name>LastName</Name>
<Value>Abiola</Value>
</Parameter>
</T2Method>
<T2Method>
<Parameter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>PrimaryID</Name>
<Value>200</Value>
</Parameter>
<Parameter>
<Name>LastName</Name>
<Value>Aboud</Value>
</Parameter>
</T2Method>
Note the "value" element in the first "paremeter" element. The value is NULL
and still an element is generated. Note the addition of a special attribute
"xsi:nil" to indicate that the element is empty. Note also the new namespace
added without your consent :-)
In this article, I have explained how to access the results of a FOR XML query
from ADO.NET. SQL Server can stream the output of a FOR XML query. It
means that SQL Server need not wait till the query execution completes, to
start sending you the data. Instead, it will start sending you the data as a
stream. As soon as a chunk of data is available, it is sent to you and SQL
Server will continue to execute the query to fetch the rest of the data.
See that the query is still executing, and you started getting part of the XML
result. The downside of this is that, if something goes wrong, say the query
timed out, you will end up with an incomplete XML document.
SQL Server 2005 introduced TYPE directive that converts the results of a FOR
XML query to a well-formed XML. When you use the TYPE directive, SQL
Server will not stream the results. Instead, it will read all the needed data,
create the result as XML data type which performs the necessary validations
to make sure that the XML document is well formed. SQL Server will start
sending you the data only after performing all these. This could add some
overhead at the server side as well as some delay in getting the results at
the client side.
Summary: Use the TYPE directive only if you really need it. By avoiding the
TYPE directive you can get some performance benefits in most of the cases.
Here is the XML document that he wanted to generate from the above
source data.
<Reference>
<Basic>
<Book Name="Book1" number="1" Valid="true">AH.KL.LO</Book>
</Basic>
<App>
<A Name="App1" number="1" Valid="true">AIK.LPO</A>
<A Name="App2" number="2" Valid="true">JUI.MKJ</A>
</App>
<Sub>
<B Name="SubA" number="1" Valid="false">LOP.MJH</B>
<B Name="SubB" number="2" Valid="false">GTY.JUI</B>
</Sub>
<DI>
<C number="1">PLW.KJU</C>
</DI>
</Reference>
Let us go ahead and try to write a FOR XML query to generate the above XML
document. First of all, let us create a table variable and fill it with the sample
data. Here is the script to create the sample data.
DECLARE @t TABLE (
RootNode VARCHAR(15),
ParentNode VARCHAR(10),
Node VARCHAR(5),
Name VARCHAR(5),
Number VARCHAR(1),
Valid VARCHAR(5),
Value VARCHAR(15)
)
INSERT INTO @t
SELECT 'Reference','Basic','Book','Book1','1','true','AH.KL.LO' UNION ALL
SELECT 'Reference','App','A','App1','1','true','AIK.LPO' UNION ALL
SELECT 'Reference','App','A','App2','2','true','JUI.MKJ' UNION ALL
SELECT 'Reference','Sub','B','SubA','1','false','LOP.MJH' UNION ALL
SELECT 'Reference','Sub','B','SubB','2','false','GTY.JUI' UNION ALL
SELECT 'Reference','DI','C',NULL,'1',NULL,'PLW.KJU'
Let us start writing the query. Let us first generate the "Book" node with its 3
attributes.
SELECT
name AS '@Name',
number AS '@number',
valid AS '@valid'
FROM @t
WHERE node = 'Book'
FOR XML PATH('Book')
The "Book" node holds a text value too. Let us write the code to generate the text
value.
SELECT
name AS '@Name',
number AS '@number',
valid AS '@valid',
value AS 'data()'
FROM @t
WHERE node = 'Book'
FOR XML PATH('Book')
SELECT
(
SELECT
name AS '@Name',
number AS '@number',
valid AS '@valid',
value AS 'data()'
FROM @t
WHERE node = 'Book'
FOR XML PATH('Book'), TYPE
) AS Basic
FOR XML PATH(''), ROOT('Reference')
<Reference>
<Basic>
<Book Name="Book1" number="1" valid="true">AH.KL.LO</Book>
</Basic>
</Reference>
SELECT
(
SELECT
name AS '@Name',
number AS '@number',
valid AS '@valid',
value AS 'data()'
FROM @t
WHERE node = 'Book'
FOR XML PATH('Book'), TYPE
) AS Basic,
(
SELECT
name AS '@Name',
number AS '@number',
valid AS '@valid',
value AS 'data()'
FROM @t
WHERE node = 'A'
FOR XML PATH('A'), TYPE
) AS App
FOR XML PATH(''), ROOT('Reference')
<Reference>
<Basic>
<Book Name="Book1" number="1" valid="true">AH.KL.LO</Book>
</Basic>
<App>
<A Name="App1" number="1" valid="true">AIK.LPO</A>
<A Name="App2" number="2" valid="true">JUI.MKJ</A>
</App>
</Reference>
SELECT
(
SELECT
name AS '@Name',
number AS '@number',
valid AS '@valid',
value AS 'data()'
FROM @t
WHERE node = 'Book'
FOR XML PATH('Book'), TYPE
) AS Basic,
(
SELECT
name AS '@Name',
number AS '@number',
valid AS '@valid',
value AS 'data()'
FROM @t
WHERE node = 'A'
FOR XML PATH('A'), TYPE
) AS App,
(
SELECT
name AS '@Name',
number AS '@number',
valid AS '@valid',
value AS 'data()'
FROM @t
WHERE node = 'B'
FOR XML PATH('B'), TYPE
) AS Sub
FOR XML PATH(''), ROOT('Reference')
<Reference>
<Basic>
<Book Name="Book1" number="1" valid="true">AH.KL.LO</Book>
</Basic>
<App>
<A Name="App1" number="1" valid="true">AIK.LPO</A>
<A Name="App2" number="2" valid="true">JUI.MKJ</A>
</App>
<Sub>
<B Name="SubA" number="1" valid="false">LOP.MJH</B>
<B Name="SubB" number="2" valid="false">GTY.JUI</B>
</Sub>
</Reference>
Let us now add the "DI" node and write the final version of the query.
SELECT
(
SELECT
name AS '@Name',
number AS '@number',
valid AS '@valid',
value AS 'data()'
FROM @t
WHERE node = 'Book'
FOR XML PATH('Book'), TYPE
) AS Basic,
(
SELECT
name AS '@Name',
number AS '@number',
valid AS '@valid',
value AS 'data()'
FROM @t
WHERE node = 'A'
FOR XML PATH('A'), TYPE
) AS App,
(
SELECT
name AS '@Name',
number AS '@number',
valid AS '@valid',
value AS 'data()'
FROM @t
WHERE node = 'B'
FOR XML PATH('B'), TYPE
) AS Sub,
(
SELECT
name AS '@Name',
number AS '@number',
valid AS '@valid',
value AS 'data()'
FROM @t
WHERE node = 'C'
FOR XML PATH('C'), TYPE
) AS DI
FOR XML PATH(''), ROOT('Reference')
<Reference>
<Basic>
<Book Name="Book1" number="1" valid="true">AH.KL.LO</Book>
</Basic>
<App>
<A Name="App1" number="1" valid="true">AIK.LPO</A>
<A Name="App2" number="2" valid="true">JUI.MKJ</A>
</App>
<Sub>
<B Name="SubA" number="1" valid="false">LOP.MJH</B>
<B Name="SubB" number="2" valid="false">GTY.JUI</B>
</Sub>
<DI>
<C number="1">PLW.KJU</C>
</DI>
</Reference>