Combine and modify XML in TSQL - sql-server

Using SQL Server 2005, is it possible to combine XML and add an attribute at same time?
Unfortunately, due to project restrictions, I need a SQL Server 2005 solution.
Consider the following, where I need to combine XML from multiple rows within a new <root> element...
; WITH [TestTable] AS (
SELECT 7 AS [PkId], CAST('<data><id>11</id><id>12</id></data>' AS XML) AS [Data]
UNION ALL
SELECT 12, CAST('<data><id>22</id></data>' AS XML)
UNION ALL
SELECT 43, CAST('<data><id>33</id></data>' AS XML)
)
SELECT (
SELECT XMLDATA as [*]
FROM (
SELECT [Data] AS [*]
FROM [TestTable]
FOR XML PATH(''), TYPE
) AS DATA(XMLDATA)
FOR XML PATH('root')
)
This produces the desired output of...
<root>
<data><id>11</id><id>12</id></data>
<data><id>22</id></data>
<data><id>33</id></data>
</root>
But what I need to do, if possible, is add an attribute to the existing data element in each of the rows with the PkId value. The desired output would then look like this...
<root>
<data pkid="7"><id>11</id><id>12</id></data>
<data pkid="12"><id>22</id></data>
<data pkid="43"><id>33</id></data>
</root>
My gut feeling is that this is going to be impossible without the use of a cursor, but if anybody knows a way of doing it I'd love to hear it.
At the request of #MattA, here is an example of some random data in the table...
[PkId] [UserId] [SubmittedDate] [Data]
1 1 2015-03-24 12:34:56 '<data><id>1</id><id>2</id></data>'
2 1 2015-03-23 09:15:52 '<data><id>3</id></data>'
3 2 2015-03-22 16:01:23 '<data><id>4</id><id>5</id></data>'
4 1 2015-03-21 13:45:34 '<data><id>6</id></data>'
Please note, that to make the question easier, I stated that I needed the PkId column as the attribute to the data. This is not actually the case - instead I need the [SubmittedDate] column to be used. I apologise if this caused confusion.
Using UserId=1 as a filter, the XML I would like from the above would be...
<root>
<data submitteddate="2015-03-24T12:34:56"><id>1</id><id>2</id></data>
<data submitteddate="2015-03-23T09:15:52"><id>3</id></data>
<data submitteddate="2015-03-21T13:45:34"><id>6</id></data>
</root>
The date would be formatted using the 126 date format available from CONVERT

Here's the quick answer for you. XML does support "modify", but shredding on a small data set like this works quite well too.
Code
--The existing XML
DECLARE #XML XML = '<root>
<data><id>11</id></data>
<data><id>22</id></data>
<data><id>33</id></data>
</root>'
--XML Shredded Back to a table
;WITH
ShreddedXML AS (
SELECT
ID = FieldAlias.value('(id)[1]','int')
FROM
#XML.nodes('/root/data') AS TableAlias(FieldAlias)
), ArbitraryPKGenerator AS (
SELECT CURRENT_TIMESTAMP AS PKid,
ID
FROM ShreddedXML
)
SELECT A.PKId AS "#PKid",
A.ID AS "id"
FROM ArbitraryPKGenerator AS A
FOR XML PATH('data'), ROOT('root')
And the XML
<root>
<data PKid="2015-03-24T09:44:55.770">
<id>11</id>
</data>
<data PKid="2015-03-24T09:44:55.770">
<id>22</id>
</data>
<data PKid="2015-03-24T09:44:55.770">
<id>33</id>
</data>
</root>

Related

Need help to format output of SQL Server XML sibling query

Consider the following SQL Server XML output:
<CUSTOMER>
<CUST_ID>TEST_CUSTOMER_01</CUST_ID>
<ORG_CODE>MY_ORG</ORG_CODE>
<CUSTOMER_TYPE CUST_TYPE="RETAIL" />
<CUSTOMER_COUNTRY CTRY_CODE="US" />
</CUSTOMER>
It was generated by the following SQL statement.
SELECT
CUSTOMER.CUST_ID, CUSTOMER.ORG_CODE,
(SELECT CUSTOMER_TYPE.CUST_TYPE
FROM CUSTOMER_TYPE
WHERE CUSTOMER.CUST_ID = CUSTOMER_TYPE.CUSTOMER_ID
FOR XML AUTO, TYPE),
(SELECT CUSTOMER_COUNTRY.CTRY_CODE
FROM CUSTOMER_COUNTRY
WHERE CUSTOMER.CUST_ID = CUSTOMER_COUNTRY.CUSTOMER_ID
FOR XML AUTO, TYPE)
FROM
CUSTOMER
WHERE
CUSTOMER.CUST_ID = 'TEST_CUSTOMER_01'
FOR XML AUTO, ELEMENTS
GO
It's required that the output look like the output below. Substituting ELEMENTS for the two TYPE words in the query above doesn't do it.
How then do I do it?
<CUSTOMER>
<CUST_ID>TEST_CUSTOMER_01</CUST_ID>
<ORG_CODE>MY_ORG</ORG_CODE>
<CUSTOMER_TYPE>
<CUST_TYPE>SHIP_TO</CUST_TYPE>
</CUSTOMER_TYPE>
<CUSTOMER_COUNTRY>
<CTRY_CODE>US</CTRY_CODE>
</CUSTOMER_COUNTRY>
</CUSTOMER>
Thanks!
The best (best in usage and performance!) is FOR XML PATH. It is very intuitive and easy to define any output you want simply by naming them.
SELECT
'TEST_CUSTOMER_01' AS [CUST_ID]
,'MY_ORG' AS [ORG_CODE]
,'SHIP_TO' AS [CUSTOMER_TYPE/CUST_TYPE]
,'US' AS [CUSTOMER_COUNTRY/CTRY_CODE]
--FROM
-- CUSTOMER
--WHERE
-- CUSTOMER.CUST_ID = 'TEST_CUSTOMER_01'
FOR XML PATH('CUSTOMER')
Try this query.
select CUST_ID,ORG_CODE,( SELECT CUSTOMER_TYPE.CUST_TYPE AS CUST_TYPE
FROM #customer_type CUSTOMER_TYPE
WHERE CUSTOMER.CUST_ID = CUSTOMER_TYPE.CUSTOMER_ID
FOR XML path (''),TYPE) AS 'CUSTOMER_TYPE' ,(
SELECT CUSTOMER_COUNTRY.CTRY_CODE as CTRY_CODE
FROM #customer_country CUSTOMER_COUNTRY
WHERE CUSTOMER.CUST_ID = CUSTOMER_COUNTRY.CUSTOMER_ID
for xml path (''),TYPE
) AS 'CUSTOMER_COUNTRY' from
#customer CUSTOMER
for xml auto, ELEMENTS
Using the above query you will get the result as
<CUSTOMER>
<CUST_ID>TEST_CUSTOMER_01</CUST_ID>
<ORG_CODE>MY_ORG</ORG_CODE>
<CUSTOMER_TYPE>
<CUST_TYPE>SHIP_TO</CUST_TYPE>
</CUSTOMER_TYPE>
<CUSTOMER_COUNTRY>
<CTRY_CODE>US</CTRY_CODE>
</CUSTOMER_COUNTRY>
</CUSTOMER>

Extract data from xml column in SQL Server

I have an XML column in SQL Server populated with:
<concession>
<schema>
<data schemaItem="title">Re-label to all boards</data>
<data schemaItem="problem">These boards have been tested</data>
<data schemaItem="solution">Ask to print new label and add the word "B" on the old serial numbers. so all serial numbers will be modified to new on B .......</data>
<data schemaItem="justification">Will help UK test resource</data>
<data schemaItem="liability">Us</data>
<data schemaItem="parts">
<part>075</part>
<part>076</part>
</data>
<data schemaItem="products">
<product>Pdq </product>
</data>
<data schemaItem="faultCode">ILB</data>
<data schemaItem="processCode">MAT</data>
<data schemaItem="quantity">273</data>
<data schemaItem="requestedExpiry">14/12/2011</data>
</schema>
</concession>
How do I extract the quantity value, ie: 273?
I've tried but no joy:
SELECT
[guid],XMLData,
(select xmlData.value('(/concession/schema/data)[1]', 'varchar(100)' )),
(select xmlData.value('(/concession/schema/data[schemaItem="quantity"])[0]', 'varchar(100)' ))
FROM
tc_Concession
I get the title ok but not the quantity.
Try using the Outer Apply With the nodes() method,
SELECT
m.c.value('#schemaItem', 'varchar(max)') as SchemaItem,
m.c.value('(text())[1]', 'nvarchar(max)') as Value
FROM Yourtablename
OUTER APPLY xml_data.nodes('concession/schema/data') as m(c)
This will give you the output as,
SchemaItem Value
title Re-label to all boards
problem These boards have been tested
solution Ask to print new label and add the word "B" on the old serial numbers. so all serial numbers will be modified to new on B .......
justification Will help UK test resource
liability Us
parts NULL
products NULL
faultCode ILB
processCode MAT
quantity 273
requestedExpiry 14/12/2011
You are very close. The attribute name needs to be preceeded by #, and the index should be 1-based. You don't need the sub-select - the .value() method can be used on it's own.
SELECT
[guid],
XMLData,
xmlData.value('(/concession/schema/data)[1]', 'varchar(100)' ),
xmlData.value('(/concession/schema/data[#schemaItem="quantity"])[1]', 'varchar(100)' )
FROM
tc_Concession
If the XML isn't guaranteed to be ordered then you should use the 2nd approach for the title too.

Return XML cell data in XML query with SQL Server 2012

I have a table in SQL Server 2012 that contains some customer data. One of the columns in the table contains license data that is stored as XML. The type of the cell is nvarchar(MAX).
Is it possible to use FOR XML (or some other method) so that when the data is returned the XML from the license data is included as XML rather than a formatted string?
If I simply use FOR XML RAW, then the result is:
<Customers id="1" CustomerName="FirstCustomer"
LicenseData="<license customerid="1">...More data here...</license>" />
What I would liket to get is:
<Customers id="1" CustomerName="FirstCustomer">
<license customerid="1">
...More data here...
</license>
</Customers>
Is there any way to make that happen?
If the XML is a valid fragment then you can simply CAST it to XML.
SELECT CAST(MyColumn as XML) as MyXml
declare #temp table (id int, customername nvarchar(128), data nvarchar(max))
insert into #temp
select 1, 'FirstCustomer', '<license customerid="1"><element id="2">data1</element><element id="3"/></license>'
select id, customername, cast(data as xml)
from #temp
for xml raw
And you'll get results like this:
<row id="1" customername="FirstCustomer">
<license customerid="1">
<element id="2">data1</element>
<element id="3" />
</license>
</row>

SQL - Read an XML node from a table field

I am using SQL Server 2008. I have a field called RequestParameters in one of my SQL table called Requests with XML data. An example would be:
<RequestParameters xmlns="http://schemas.datacontract.org/2004/07/My.Name.Space" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" z:Id="1">
<Data z:Id="2" i:type="CheckoutRequest">
<UserGuid>7ec38c44-5aa6-49e6-9fc7-25e9028f2148</UserGuid>
<DefaultData i:nil="true" />
</Data>
</RequestParameters>
I ultimately want to retrieve the value of UserGuid. For that, I am doing this:
SELECT RequestParameters.value('(/RequestParameters/Data/UserGuid)[0]', 'uniqueidentifier') as UserGuid
FROM Requests
However, the results I am seeing are all NULL. What am I doing wrong?
You have to specify the default namespace and use [1] instead of [0].
WITH XMLNAMESPACES(default 'http://schemas.datacontract.org/2004/07/My.Name.Space')
SELECT RequestParameters.value('(/RequestParameters/Data/UserGuid)[1]', 'uniqueidentifier') as UserGuid
FROM Requests;
SQL Fiddle
declare #XML xml
set #XML = "<RequestParameters xmlns="http://schemas.datacontract.org/2004/07/My.Name.Space" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" z:Id="1">
<Data z:Id="2" i:type="CheckoutRequest">
<UserGuid>7ec38c44-5aa6-49e6-9fc7-25e9028f2148</UserGuid>
<DefaultData i:nil="true" />
</Data>
</RequestParameters>"
select #XML.value('(/RequestParameters/Data /UserGuid)[1]', 'varchar')
'

Microsoft SQL Server xml data

This site has a technique to pass xml data around in Microsoft SQL Server:
DECLARE #productIds xml
SET #productIds ='<Products><id>3</id><id>6</id><id>15</id></Products>'
SELECT
ParamValues.ID.value('.','VARCHAR(20)')
FROM #productIds.nodes('/Products/id') as ParamValues(ID)
But what is the syntax if I add another field?
The following does NOT work:
DECLARE #productIds xml
SET #productIds ='<Products><id>3</id><descr>Three</descr><id>6</id><descr>six</descr><id>15</id><descr>Fifteen</descr></Products>'
SELECT
ParamValues.ID.value('.','VARCHAR(20)')
,ParamValues.descr.value('.','VARCHAR(20)')
FROM #productIds.nodes('/Products/id') as ParamValues(ID)
Note: Maybe I've constructed my xml wrong.
You need to use something like:
SELECT
ParamValues.ID.value('(id)[1]','VARCHAR(20)'),
ParamValues.ID.value('(descr)[1]','VARCHAR(20)')
FROM
#productIds.nodes('/Products') as ParamValues(ID)
That FROM statement there defines something like a "virtual table" called ParamValues.ID - you need to select the <Products> node into that virtual table and then access the properties inside it.
Furthermore, your XML structure is very badly chosen:
<Products>
<id>3</id>
<descr>Three</descr>
<id>6</id>
<descr>six</descr>
<id>15</id>
<descr>Fifteen</descr>
</Products>
You won't be able to select the individual pairs of id/descr - you should use something more like:
<Products>
<Product>
<id>3</id>
<descr>Three</descr>
</Product>
<Product>
<id>6</id>
<descr>six</descr>
</Product>
<Product>
<id>15</id>
<descr>Fifteen</descr>
</Product>
</Products>
Then you could retrieve all items using this SQL XML query:
SELECT
ParamValues.ID.value('(id)[1]','VARCHAR(20)') AS 'ID',
ParamValues.ID.value('(descr)[1]','VARCHAR(20)') AS 'Description'
FROM
#productIds.nodes('/Products/Product') as ParamValues(ID)
ID Descrition
3 Three
6 six
15 Fifteen
You must wrap each set of id and descr into one parent node. Say Row. Now you can access each pair like this.
DECLARE #productIds xml
SET #productIds ='<Products><Row><id>3</id><descr>Three</descr></Row><Row><id>6</id><descr>six</descr></Row><Row><id>15</id><descr>Fifteen</descr></Row></Products>'
SELECT
ParamValues.Row.query('id').value('.','VARCHAR(20)'),
ParamValues.Row.query('descr').value('.','VARCHAR(20)')
FROM #productIds.nodes('/Products/Row') as ParamValues(Row)

Resources