laika222
7/10/2017 - 7:18 PM

SELECT XML, FOR XML, FOR XML RAW, AUTO, FOR XML PATH, FOR XML EXPLICIT.sql

/*
You can use the FOR XML clause to take relational results and return them as XML code. There are different modes:

RAW - an XML representation of a rowset. It doesn't make any attempt to interpret the elements in the document and identify what they represent. Returns an element for each row in the rowset. Results can be element or attribute-centric. You can specify an optional root element and row element name. If you don't specify a row element name, it'll be called <row>.

AUTO - creates nested child-elements for joined tables, attempting to find a structure to the data. You don't have to specifically name the elements - they are named to match the table they're pulled from (or the alias you specify). Results can be element or attribute-centric, and you can specify a root element.

PATH - uses XML Path Language (XPath) to specify the XML format you want to return. Allows you to create nested data and what should be exposed as an element or an attribute. Easier to use than EXPLICIT mode.

EXPLICIT - based on a tabular representation of XML documents, and enables complete control over the XML structure. This allows you to mix and match it being attribute-centric AND element-centric. You can create multiple levels in the hierarchy by creating multiple SELECT satements and UNIONing them together (one SELECT statement per level in the hierarchy). Read up on this if you ever want to use this. Uses a very specific naming convention such as [Invoice!1!FName!Element].

*/

-- for all of the examples below, here is the original query and the relational data that it would normally return
SELECT c.CustomerID AS CustID, c.LastName AS PersonName,
soh.SalesOrderID, soh.OrderDate, soh.TotalDue
FROM sales.customerpii c
JOIN sales.salesorderheader soh
ON c.CustomerID = soh.CustomerID
ORDER BY c.CustomerID

/* TOP 5 of regular relational resultset returned:

CustID	PersonName	SalesOrderID	OrderDate	TotalDue
11000	Yang	43793	2011-06-21 00:00:00.000	3756.989
11000	Yang	51522	2013-06-20 00:00:00.000	2587.8769
11000	Yang	57418	2013-10-03 00:00:00.000	2770.2682
11001	Huang	51493	2013-06-18 00:00:00.000	2674.0227
11001	Huang	43767	2011-06-17 00:00:00.000	3729.364

*/

--------------
-- RAW MODE --
--------------

-- RAW - an XML representation of a rowset. It doesn't make any attempt to interpret the elements in the document and identify what they represent. Returns an element for each row in the rowset. Results can be element or attribute-centric. You can specify an optional root element and row element name. If you don't specify a row element name, it'll be called <row>.
SELECT c.CustomerID AS CustID, c.LastName AS PersonName,
soh.SalesOrderID, soh.OrderDate, soh.TotalDue
FROM sales.customerpii c
JOIN sales.salesorderheader soh
ON c.CustomerID = soh.CustomerID
ORDER BY c.CustomerID
FOR XML RAW;

/* Result of FOR XML RAW, which returns a single row resultset that contains XML code for the entire resultset - it creates a <row> element for each row, and places all of the data in as attributes

<row CustID="11000" PersonName="Yang" SalesOrderID="43793" OrderDate="2011-06-21T00:00:00" TotalDue="3756.9890" />
<row CustID="11000" PersonName="Yang" SalesOrderID="51522" OrderDate="2013-06-20T00:00:00" TotalDue="2587.8769" />
<row CustID="11000" PersonName="Yang" SalesOrderID="57418" OrderDate="2013-10-03T00:00:00" TotalDue="2770.2682" />
<row CustID="11001" PersonName="Huang" SalesOrderID="51493" OrderDate="2013-06-18T00:00:00" TotalDue="2674.0227" />
<row CustID="11001" PersonName="Huang" SalesOrderID="43767" OrderDate="2011-06-17T00:00:00" TotalDue="3729.3640" />
<row CustID="11001" PersonName="Huang" SalesOrderID="72773" OrderDate="2014-05-12T00:00:00" TotalDue="650.8008" />

*/

-- you can also rename the <row> element by placing it in parenthesis after the word RAW, and you can include a ROOT clause to add a root element.
SELECT c.CustomerID AS CustID, c.LastName AS PersonName,
soh.SalesOrderID, soh.OrderDate, soh.TotalDue
FROM sales.customerpii c
JOIN sales.salesorderheader soh
ON c.CustomerID = soh.CustomerID
ORDER BY c.CustomerID
-- renames the <row> element as <order>, and wraps everything within a root element called <orders>
FOR XML RAW('Order'), ROOT('Orders');

/*
<Orders>
  <Order CustID="11000" PersonName="Yang" SalesOrderID="43793" OrderDate="2011-06-21T00:00:00" TotalDue="3756.9890" />
  <Order CustID="11000" PersonName="Yang" SalesOrderID="51522" OrderDate="2013-06-20T00:00:00" TotalDue="2587.8769" />
  <Order CustID="11000" PersonName="Yang" SalesOrderID="57418" OrderDate="2013-10-03T00:00:00" TotalDue="2770.2682" />
  <Order CustID="11001" PersonName="Huang" SalesOrderID="51493" OrderDate="2013-06-18T00:00:00" TotalDue="2674.0227" />
  <Order CustID="11001" PersonName="Huang" SalesOrderID="43767" OrderDate="2011-06-17T00:00:00" TotalDue="3729.3640" />
  ...
*/

-- you can also make the results element-centric by adding the ELEMENTS keyword, which will return all row values as elements instead of attributes
SELECT c.CustomerID AS CustID, c.LastName AS PersonName,
soh.SalesOrderID, soh.OrderDate, soh.TotalDue
FROM sales.customerpii c
JOIN sales.salesorderheader soh
ON c.CustomerID = soh.CustomerID
ORDER BY c.CustomerID
-- renames <row> element as <order>, creates a root element <orders>, and specifies results to be returned as elements instead of attributes
FOR XML RAW('Order'), ROOT('Orders'), ELEMENTS;

/* Results of RAW using ELEMENT-centric results rather than attribute-centric results
<Orders>
  <Order>
    <CustID>11000</CustID>
    <PersonName>Yang</PersonName>
    <SalesOrderID>43793</SalesOrderID>
    <OrderDate>2011-06-21T00:00:00</OrderDate>
    <TotalDue>3756.9890</TotalDue>
  </Order>
  <Order>
    <CustID>11000</CustID>
    <PersonName>Yang</PersonName>
    <SalesOrderID>51522</SalesOrderID>
    <OrderDate>2013-06-20T00:00:00</OrderDate>
    <TotalDue>2587.8769</TotalDue>
  </Order>
  <Order>
    <CustID>11000</CustID>
    <PersonName>Yang</PersonName>
    <SalesOrderID>57418</SalesOrderID>
    <OrderDate>2013-10-03T00:00:00</OrderDate>
    <TotalDue>2770.2682</TotalDue>
  </Order>
  ...
*/

---------------
-- AUTO MODE --
---------------

-- AUTO - creates nested child-elements for joined tables, attempting to find a structure to the data. You don't have to specifically name the elements - they are named to match the table they're pulled from (or the alias you specify). Results can be element or attribute-centric, and you can specify a root element.
SELECT c.CustomerID AS CustID, c.LastName AS PersonName,
soh.SalesOrderID, soh.OrderDate, soh.TotalDue
FROM sales.customerpii c
JOIN sales.salesorderheader soh
ON c.CustomerID = soh.CustomerID
ORDER BY c.CustomerID
-- FOR XML AUTO, note that you don't have to name the rows, it'll pull the rows based on the table name (or in this case the alias name).
FOR XML AUTO, ROOT('Orders');

/* Results for AUTO mode. Note the elements are named after the table aliases, and it's automatically created the rows from the SalesOrderHeader table as child elements to the rows from the Customer table.
<Orders>
  <c CustID="11000" PersonName="Yang">
    <soh SalesOrderID="43793" OrderDate="2011-06-21T00:00:00" TotalDue="3756.9890" />
    <soh SalesOrderID="51522" OrderDate="2013-06-20T00:00:00" TotalDue="2587.8769" />
    <soh SalesOrderID="57418" OrderDate="2013-10-03T00:00:00" TotalDue="2770.2682" />
  </c>
  <c CustID="11001" PersonName="Huang">
    <soh SalesOrderID="51493" OrderDate="2013-06-18T00:00:00" TotalDue="2674.0227" />
    <soh SalesOrderID="43767" OrderDate="2011-06-17T00:00:00" TotalDue="3729.3640" />
    <soh SalesOrderID="72773" OrderDate="2014-05-12T00:00:00" TotalDue="650.8008" />
  </c>
  <c CustID="11002" PersonName="Torres">
    <soh SalesOrderID="43736" OrderDate="2011-06-09T00:00:00" TotalDue="3756.9890" />
    <soh SalesOrderID="51238" OrderDate="2013-06-02T00:00:00" TotalDue="2535.9640" />
    <soh SalesOrderID="53237" OrderDate="2013-07-26T00:00:00" TotalDue="2673.0613" />
  </c>
  ...

*/

-- you can also make it element-centric using ELEMENTS
SELECT c.CustomerID AS CustID, c.LastName AS PersonName,
soh.SalesOrderID, soh.OrderDate, soh.TotalDue
FROM sales.customerpii c
JOIN sales.salesorderheader soh
ON c.CustomerID = soh.CustomerID
ORDER BY c.CustomerID
-- FOR XML AUTO, note that you don't have to name the rows, it'll pull the rows based on the table name (or in this case the alias name).
FOR XML AUTO, ROOT('Orders'), ELEMENTS;

/* Results for AUTO mode. Note each table has its own element, and the columns are then returned as child elements.
<Orders>
  <c>
    <CustID>11000</CustID>
    <PersonName>Yang</PersonName>
    <soh>
      <SalesOrderID>43793</SalesOrderID>
      <OrderDate>2011-06-21T00:00:00</OrderDate>
      <TotalDue>3756.9890</TotalDue>
    </soh>
    <soh>
      <SalesOrderID>51522</SalesOrderID>
      <OrderDate>2013-06-20T00:00:00</OrderDate>
      <TotalDue>2587.8769</TotalDue>
    </soh>
    <soh>
      <SalesOrderID>57418</SalesOrderID>
      <OrderDate>2013-10-03T00:00:00</OrderDate>
      <TotalDue>2770.2682</TotalDue>
    </soh>
  </c>
  ...

*/

---------------
-- PATH MODE --
---------------

-- PATH - uses XML Path Language (XPath) to specify the XML format you want to return. Allows you to create nested data and what should be exposed as an element or an attribute. Easier to use than EXPLICIT mode.

-- example of using XPATH. Everything with an @ symbol will be returned as an attribute, and everything without an @ will be returned as an element. Apparently all of the attributes in a sibling grouping must be listed first, then the elements.
SELECT c.CustomerID AS '@CustID', 
soh.SalesOrderID AS '@SalesID', 
c.LastName AS 'PersonName',
soh.OrderDate AS 'OrderDate', 
soh.TotalDue AS 'TotalDue'
FROM sales.customerpii c
JOIN sales.salesorderheader soh
ON c.CustomerID = soh.CustomerID
ORDER BY c.CustomerID
-- FOR XML PATH, instead of <row> element name the element <Invoice> 
FOR XML PATH('Invoice');

/* Results, showing an Invoice elemnent for each row

<Invoice CustID="11000" SalesID="43793">
  <PersonName>Yang</PersonName>
  <OrderDate>2011-06-21T00:00:00</OrderDate>
  <TotalDue>3756.9890</TotalDue>
</Invoice>
<Invoice CustID="11000" SalesID="51522">
  <PersonName>Yang</PersonName>
  <OrderDate>2013-06-20T00:00:00</OrderDate>
  <TotalDue>2587.8769</TotalDue>
</Invoice>
<Invoice CustID="11000" SalesID="57418">
  <PersonName>Yang</PersonName>
  <OrderDate>2013-10-03T00:00:00</OrderDate>
  <TotalDue>2770.2682</TotalDue>
</Invoice>

*/

-- you can also next elements within other elements by placing a nested subquery within your SELECT statement. Everything with an @ symbol will be returned as an attribute, and everything without an @ will be returned as an element. Apparently all of the attributes in a sibling grouping must be listed first, then the elements.
SELECT c.CustomerID AS '@CustID', 
c.LastName AS 'PersonName',
-- begins inner SELECT which will be nested under the overall <OuterQuery> element for each row definied in the outer FOR XML PATH statement. You can nest an element within another element by spelling it out in the XPath. In this case, the <customer> element will be enclosed in a <customers> element
(SELECT	FirstName AS 'customer/firstname',
		LastName AS 'customer/lastname'
		FROM Sales.customerpii AS c2
		WHERE c.CustomerID = c2.CustomerID
		-- inner FOR XML PATH,, with a blank so each customer isn't placed into its own element, placed within a root element of <customers>
		FOR XML PATH(''), ROOT('customers')),
soh.SalesOrderID AS 'SalesID', 
soh.OrderDate AS 'OrderDate', 
soh.TotalDue AS 'TotalDue'
FROM sales.customerpii c
JOIN sales.salesorderheader soh
ON c.CustomerID = soh.CustomerID
WHERE c.CustomerID IN ( 19169, 17423, 22728)
ORDER BY c.CustomerID
-- the outer FOR XML PATH, each row will get an element <outerquery>, and it will all be encapslated in an <invoice> root element 
FOR XML PATH('OuterQuery'), ROOT('Invoice');

/* Results, showing an Invoice elemnent for each row. Note that it treats the results of the inner query as literals, so instead of '<' you'll get '&lt;' and instead of '>' you'll get '&gt;'
<Invoice>
  <OuterQuery CustID="17423">
    <PersonName>Alexander</PersonName>&lt;customers&gt;&lt;customer&gt;&lt;firstname&gt;Arianna&lt;/firstname&gt;&lt;lastname&gt;Alexander&lt;/lastname&gt;&lt;/customer&gt;&lt;/customers&gt;<SalesID>70717</SalesID><OrderDate>2014-04-15T00:00:00</OrderDate><TotalDue>2538.4944</TotalDue></OuterQuery>
  <OuterQuery CustID="19169">
    <PersonName>Bryant</PersonName>&lt;customers&gt;&lt;customer&gt;&lt;firstname&gt;Arianna&lt;/firstname&gt;&lt;lastname&gt;Bryant&lt;/lastname&gt;&lt;/customer&gt;&lt;/customers&gt;<SalesID>51657</SalesID><OrderDate>2013-06-27T00:00:00</OrderDate><TotalDue>1293.3804</TotalDue></OuterQuery>
  <OuterQuery CustID="22728">
    <PersonName>Russell</PersonName>&lt;customers&gt;&lt;customer&gt;&lt;firstname&gt;Arianna&lt;/firstname&gt;&lt;lastname&gt;Russell&lt;/lastname&gt;&lt;/customer&gt;&lt;/customers&gt;<SalesID>53264</SalesID><OrderDate>2013-07-27T00:00:00</OrderDate><TotalDue>165.6727</TotalDue></OuterQuery>
</Invoice>

To fix this, you can do a Find and Replace in SQL Server and replace '&lt;' with '<' and '&gt;' with '>', and you'll end up with normal XML code:

<Invoice>
  <OuterQuery CustID="17423">
    <PersonName>Alexander</PersonName>
	<customers>
		<customer>
			<firstname>Arianna</firstname>
			<lastname>Alexander</lastname>
		</customer>
	</customers>
	<SalesID>70717</SalesID>
	<OrderDate>2014-04-15T00:00:00</OrderDate>
	<TotalDue>2538.4944</TotalDue>
  </OuterQuery>
  <OuterQuery CustID="19169">
    <PersonName>Bryant</PersonName>
	<customers>
		<customer>
			<firstname>Arianna</firstname>
			<lastname>Bryant</lastname>
		</customer>
	</customers>
	<SalesID>51657</SalesID>
	<OrderDate>2013-06-27T00:00:00</OrderDate>
	<TotalDue>1293.3804</TotalDue>
  </OuterQuery>
  <OuterQuery CustID="22728">
    <PersonName>Russell</PersonName>
	<customers>
		<customer>
			<firstname>Arianna</firstname>
			<lastname>Russell</lastname>
		</customer>
	</customers>
	<SalesID>53264</SalesID>
	<OrderDate>2013-07-27T00:00:00</OrderDate>
	<TotalDue>165.6727</TotalDue>
  </OuterQuery>
</Invoice>
*/

-------------------
-- EXPLICIT MODE --
-------------------

-- EXPLICIT - based on a tabular representation of XML documents, and enables complete control over the XML structure. This allows you to mix and match it being attribute-centric AND element-centric. You can create multiple levels in the hierarchy by creating multiple SELECT satements and UNIONing them together (one SELECT statement per level in the hierarchy). Read up on this if you ever want to use this. Uses a very specific naming convention such as [Invoice!1!FName!Element].
SELECT 1 AS Tag, NULL AS Parent,
CustomerID AS [Invoice!1!CustID],
FirstName AS [Invoice!1!FName!Element], 
LastName AS [Invoice!1!LName!Element]
FROM sales.customerpii 
FOR XML EXPLICIT;

-- detail of the EXPLICIT example above

-- Tag sets the base attribute (?)
SELECT 1 AS Tag, NULL AS Parent,
-- naming convention, Invoice creates a base element, and CustID will be a child element underneath Invoice - this lacks the word Element, so this row will be created as an attribute in the XML
CustomerID AS [Invoice!1!CustID],
-- Element keyword used to create an element for this column
FirstName AS [Invoice!1!FName!Element], 
LastName AS [Invoice!1!LName!Element]
FROM sales.customerpii 
FOR XML EXPLICIT;

/* Results as XML, creating an Invoice element, placing CustID as an attribute, and creating FName and LName as child elements within Invoice

<Invoice CustID="19169">
  <FName>Arianna</FName>
  <LName>Bryant</LName>
</Invoice>
<Invoice CustID="17423">
  <FName>Arianna</FName>
  <LName>Alexander</LName>
</Invoice>
<Invoice CustID="17722">
  <FName>Jerome</FName>
  <LName>Serrano</LName>
</Invoice>
<Invoice CustID="22728">
  <FName>Arianna</FName>
  <LName>Russell</LName>
</Invoice>

*/

-----------------------------------
-- USING NAMESPACES WITH FOR XML --
-----------------------------------

-- you can define a namespace by adding WITH XMLNAMESPACES at the beginning of your query. Supported only for RAW, AUTO and PATH modes. An example is:
WITH XMLNAMESPACES ('http://aw/order' AS ord)

-- example of adding XML namespace using RAW mode. Note that you use the prefix 'ord' before each element to associate that element with the namespace you've prefixed 'ord' in the first line
WITH XMLNAMESPACES ('http://aw/order' AS ord)
SELECT c.CustomerID AS 'ord:CustID', c.LastName AS 'ord:PersonName',
soh.SalesOrderID, soh.OrderDate, soh.TotalDue
FROM sales.customerpii c
JOIN sales.salesorderheader soh
ON c.CustomerID = soh.CustomerID
ORDER BY c.CustomerID
FOR XML RAW('ord:order'), ELEMENTS;

/* Results, note that a namespace of 'http://aw/order', aliased 'ord', has been added to the Orders root element

<Orders xmlns:ord="http://aw/order">
  <ord:order>
    <ord:CustID>11000</ord:CustID>
    <ord:PersonName>Yang</ord:PersonName>
    <SalesOrderID>43793</SalesOrderID>
    <OrderDate>2011-06-21T00:00:00</OrderDate>
    <TotalDue>3756.9890</TotalDue>
  </ord:order>
  <ord:order>
    <ord:CustID>11000</ord:CustID>
    <ord:PersonName>Yang</ord:PersonName>
    <SalesOrderID>51522</SalesOrderID>
    <OrderDate>2013-06-20T00:00:00</OrderDate>
    <TotalDue>2587.8769</TotalDue>
  </ord:order>
...
*/

----------------------------------
-- PREVIOUS NOTES ABOUT FOR XML --
----------------------------------

SELECT TOP 5 playerID, teamID, HR FROM batting

/* Results from that basic query:

playerID      teamID   HR
------------  -------  ----
berrych02     PHA      0
bettshu01     BSN      0
birkora01     PIT      0
bishoma01     BOS      1
blaehge01     SLA      0

*/

-- FOR XML AUTO, which will take the results set and return it as XML - you specify the ROOT which will become the root element, and then it will create an element named after the table you are selecting from, and that element will be repeated for each row in the table, with the column values being added as attributes in each repeated element.
SELECT TOP 5 playerID, teamID, HR FROM batting
FOR XML AUTO, ROOT('Player_Stats');

/* Results:

<Player_Stats>
  <batting playerID="berrych02" teamID="PHA" HR="0" />
  <batting playerID="bettshu01" teamID="BSN" HR="0" />
  <batting playerID="birkora01" teamID="PIT" HR="0" />
  <batting playerID="bishoma01" teamID="BOS" HR="1" />
  <batting playerID="blaehge01" teamID="SLA" HR="0" />
</Player_Stats>

*/

-- you can also use FOR XML PATH, which allows you to determine what will columns will be listed as an XML attribute and which will be listed as an XML element. An element called row will be created for each row in the results set. If you select a column AS '@columnname' (with an @ sign surrounded by quotes), the column will be pulled as an attribute. If you do not include an @ sign before the column, it will be pulled as an element. In the example below, playerID and teamID will be pulled as attributes, and HR and RBI will be pulled as elements.
SELECT TOP 5 playerID AS '@playerID', teamID AS '@teamID', HR, RBI
FROM Batting 
FOR XML PATH, ROOT('Player_Stats');

/* Results:

<Player_Stats>
  <row playerID="berrych02" teamID="PHA">
    <HR>0</HR>
    <RBI>34</RBI>
  </row>
  <row playerID="bettshu01" teamID="BSN">
    <HR>0</HR>
    <RBI>2</RBI>
  </row>
  <row playerID="birkora01" teamID="PIT">
    <HR>0</HR>
    <RBI>11</RBI>
  </row>
  <row playerID="bishoma01" teamID="BOS">
    <HR>1</HR>
    <RBI>22</RBI>
  </row>
  <row playerID="blaehge01" teamID="SLA">
    <HR>0</HR>
    <RBI>2</RBI>
  </row>
</Player_Stats>

*/

-- using FOR XML PATH, you can also use a slash to contain your column elements within parent elements. Below, each row in the results set will be added as a row element with playerID as an attribute. The HR, Doubles, and RBI columns will be pulled as elements called HomeRuns, Doubles, and RunsBattedIn, and all three elements will be contained within a parent element BattingStats (since in the query the AS clause is preceded with BattingStats/ for each one). SB and CS will be pulled as the elements StolenBases and CaughtStealing, and since they are both preceded by RunningStats/, those elements will be contained within the parent element RunningStats. 
SELECT TOP 3 playerID AS '@playerID',
HR AS 'BattingStats/HomeRuns',
Doubles AS 'BattingStats/Doubles',
RBI AS 'BattingStats/RunsBattedIn',
SB AS 'RunningStats/StolenBases',
CS AS 'RunningStats/CaughtStealing'
FROM Batting
FOR XML PATH, ROOT('Player_Stats');

/* Results:

<Player_Stats>
  <row playerID="berrych02">
    <BattingStats>
      <HomeRuns>0</HomeRuns>
      <Doubles>10</Doubles>
      <RunsBattedIn>34</RunsBattedIn>
    </BattingStats>
    <RunningStats>
      <StolenBases>1</StolenBases>
      <CaughtStealing>0</CaughtStealing>
    </RunningStats>
  </row>
  <row playerID="bettshu01">
    <BattingStats>
      <HomeRuns>0</HomeRuns>
      <Doubles>1</Doubles>
      <RunsBattedIn>2</RunsBattedIn>
    </BattingStats>
    <RunningStats>
      <StolenBases>0</StolenBases>
      <CaughtStealing>0</CaughtStealing>
    </RunningStats>
  </row>
  <row playerID="birkora01">
    <BattingStats>
      <HomeRuns>0</HomeRuns>
      <Doubles>3</Doubles>
      <RunsBattedIn>11</RunsBattedIn>
    </BattingStats>
    <RunningStats>
      <StolenBases>0</StolenBases>
      <CaughtStealing>0</CaughtStealing>
    </RunningStats>
  </row>
</Player_Stats>

*/