Converting XML node values to comma separated values in SQL - sql

I am trying to convert XML node values to comma separated values but, getting a
Incorrect syntax near the keyword 'SELECT'.
error message
declare #dataCodes XML = '<Root>
<List Value="120" />
<List Value="110" />
</Root>';
DECLARE #ConcatString VARCHAR(MAX)
SELECT #ConcatString = COALESCE(#ConcatString + ', ', '') + Code FROM (SELECT T.Item.value('#Value[1]','VARCHAR(MAX)') as Code FROM #dataCodes.nodes('/Root/List') AS T(Item))
SELECT #ConcatString AS Result
GO
I tried to follow an article but not sure how to proceed further. Any suggestion is appreciated.
Expectation:
Comma separated values ('120,110') stored in a variable.

Try this;
DECLARE #dataCodes XML = '<Root>
<List Value="120" />
<List Value="110" />
</Root>';
DECLARE #ConcatString VARCHAR(MAX)
SELECT #ConcatString = COALESCE(#ConcatString + ', ', '') + Code
FROM (
SELECT T.Item.value('#Value[1]', 'VARCHAR(MAX)') AS Code
FROM #dataCodes.nodes('/Root/List') AS T(Item)
) as TBL
SELECT #ConcatString AS Result
GO
You just need to add an alias to your sub SQL query.

For future readers, XML data can be extracted into arrays, lists, vectors, and variables for output in comma separated values more fluidly using general purpose languages. Below are open-source solutions using OP's needs taking advantage of XPath.
Python
import lxml.etree as ET
xml = '<Root>\
<List Value="120" />\
<List Value="110" />\
</Root>'
dom = ET.fromstring(xml)
nodes = dom.xpath('//List/#Value')
data = [] # LIST
for elem in nodes:
data.append(elem)
print((", ").join(data))
120, 110
PHP
$xml = '<Root>
<List Value="120" />
<List Value="110" />
</Root>';
$dom = simplexml_load_string($xml);
$node = $dom->xpath('//List/#Value');
$data = []; # Array
foreach ($node as $n){
$data[] = $n;
}
echo implode(", ", $data);
120, 110
R
library(XML)
xml = '<Root>
<List Value="120" />
<List Value="110" />
</Root>'
doc<-xmlInternalTreeParse(xml)
data <- xpathSApply(doc, "//List", xmlGetAttr, 'Value') # LIST
print(paste(data, collapse = ', '))
120, 110

To do this without a variable, you can use the nodes method to convert the xml nodes into a table format with leading commas, then use FOR XML PATH('') to collapse it into a single line of XML, then wrap that in STUFF to convert it to varchar and strip off the initial leading comma:
DECLARE #dataCodes XML = '<Root>
<List Value="120" />
<List Value="110" />
</Root>';
SELECT STUFF(
(
SELECT ', ' + T.Item.value('#Value[1]', 'VARCHAR(MAX)')
FROM #dataCodes.nodes('/Root/List') AS T(Item)
FOR XML PATH('')
), 1, 2, '')

Related

How to update XML elements with new values in SQL script

I have XML in one of the column in the XYZ table, and I need to update the Amount element with a new value instead of 0.00, and the PolicyReference and AccountReference elements with two different values instead of blank.
For example:
<PolicyReference>7657576567</PolicyReference>
<AccountReference>7657576875</AccountReference>
This is my XML in the column :
<document>
<StatusCode>ACV</StatusCode>
<PaymentMethodDetail>
<EFT>
<AccountNumber>123456789</AccountNumber>
<AccountName>ABCDEFGHIJK</AccountName>
</EFT>
</PaymentMethodDetail>
<PaymentExtendedData>
<CHECK>
<Source>System</Source>
<SourceType>ACH</SourceType>
</CHECK>
</PaymentExtendedData>
<PostMarkDate />
<EntryUserId>1</EntryUserId>
<Amount>0.00</Amount>
<EntryDate />
<PolicyLineOfBusiness>LOL</PolicyLineOfBusiness>
<PolicyReference />
<AccountReference />
<AccountId>2034001793</AccountId>
</document>
This is what I have tried:
UPDATE XYZ
SET XmlPayload.modify('replace value of (//document/PolicyReference/)[1] with "<PolicyReference>275654</PolicyReference>"')
WHERE PaymentSearchId = 18785
I am getting an error:
Msg 9341, Level 16, State 1, Line 4
XQuery [XYZ.XmlPayload.modify()]: Syntax error near ')', expected a step expression
I think this is a good question as it presents an interesting challenge of having an existing element without a text value. This is handled differently than simply adding a new element or replacing the contents of an existing one.
First, though, your provided XML was broken. If that is the XML you're receiving, you have other issues. For instance, in your original question, you had </AccountReference> which is invalid syntax by itself. I corrected this to <AccountReference /> both in your question as well as in my example.
With empty XML elements, you need to call the insert text DML of the XML.modify method.
DECLARE #xml XML =
'<document>
<StatusCode>ACV</StatusCode>
<PaymentMethodDetail>
<EFT>
<AccountNumber>123456789</AccountNumber>
<AccountName>ABCDEFGHIJK</AccountName>
</EFT>
</PaymentMethodDetail>
<PaymentExtendedData>
<CHECK>
<Source>System</Source>
<SourceType>ACH</SourceType>
</CHECK>
</PaymentExtendedData>
<PostMarkDate />
<EntryUserId>1</EntryUserId>
<Amount>0.00</Amount>
<EntryDate />
<PolicyLineOfBusiness>LOL</PolicyLineOfBusiness>
<PolicyReference />
<AccountReference />
<AccountId>2034001793</AccountId>
</document>';
DECLARE
#Amount DECIMAL(18,2) = 99.95,
#AccountReference VARCHAR(20) = '7657576875',
#PolicyReference VARCHAR(20) = '7657576567';
/* Update Amount */
SET #xml.modify('
replace value of (/document/Amount/text())[1]
with sql:variable("#Amount")
');
/* Insert the AccountReference text */
SET #xml.modify('
insert text {sql:variable("#AccountReference")} into (/document/AccountReference[1])[1]
');
/* Insert the PolicyReference text */
SET #xml.modify('
insert text {sql:variable("#PolicyReference")} into (/document/PolicyReference[1])[1]
');
/* Show updated XML */
SELECT #xml;
The updated XML is now:
<document>
<StatusCode>ACV</StatusCode>
<PaymentMethodDetail>
<EFT>
<AccountNumber>123456789</AccountNumber>
<AccountName>ABCDEFGHIJK</AccountName>
</EFT>
</PaymentMethodDetail>
<PaymentExtendedData>
<CHECK>
<Source>System</Source>
<SourceType>ACH</SourceType>
</CHECK>
</PaymentExtendedData>
<PostMarkDate />
<EntryUserId>1</EntryUserId>
<Amount>99.95</Amount>
<EntryDate />
<PolicyLineOfBusiness>LOL</PolicyLineOfBusiness>
<PolicyReference>7657576567</PolicyReference>
<AccountReference>7657576875</AccountReference>
<AccountId>2034001793</AccountId>
</document>
An example of updating a table:
DECLARE #xyz TABLE ( PaymentSearchId INT, XmlPayload XML );
INSERT INTO #xyz VALUES ( 18785,
'<document>
<StatusCode>ACV</StatusCode>
<PaymentMethodDetail>
<EFT>
<AccountNumber>123456789</AccountNumber>
<AccountName>ABCDEFGHIJK</AccountName>
</EFT>
</PaymentMethodDetail>
<PaymentExtendedData>
<CHECK>
<Source>System</Source>
<SourceType>ACH</SourceType>
</CHECK>
</PaymentExtendedData>
<PostMarkDate />
<EntryUserId>1</EntryUserId>
<Amount>0.00</Amount>
<EntryDate />
<PolicyLineOfBusiness>LOL</PolicyLineOfBusiness>
<PolicyReference />
<AccountReference />
<AccountId>2034001793</AccountId>
</document>' );
DECLARE
#PaymentSearchId INT = 18785,
#Amount DECIMAL(18,2) = 99.95,
#AccountReference VARCHAR(20) = '7657576875',
#PolicyReference VARCHAR(20) = '7657576567';
/* Update Amount */
UPDATE #xyz
SET XmlPayload.modify('
replace value of (/document/Amount/text())[1]
with sql:variable("#Amount")
')
WHERE PaymentSearchId = #PaymentSearchId;
/* Insert the AccountReference text */
UPDATE #xyz
SET XmlPayload.modify('
insert text {sql:variable("#AccountReference")} into (/document/AccountReference[1])[1]
')
WHERE PaymentSearchId = #PaymentSearchId;
/* Insert the PolicyReference text */
UPDATE #xyz
SET XmlPayload.modify('
insert text {sql:variable("#PolicyReference")} into (/document/PolicyReference[1])[1]
')
WHERE PaymentSearchId = #PaymentSearchId;
/* Show updated XML */
SELECT XmlPayload FROM #xyz WHERE PaymentSearchId = #PaymentSearchId;

How to get XML subnodes as strings along with parent attributes?

I need to parse xml which consist of nodes having attributes and subnodes. The result should be attribute of parent with xml of child node
declare #xml xml
set #xml = '<root>
<group Description="firstgroup">
<nodeA age="10" birthplace="Anchorage"/>
<nodeB mode="A" ability="read"/>
</group>
<group Description="nextgroup">
<nodeA age="10" birthplace="London"/>
<nodeB count="2" birthplace="Paris"/>
</group>
</root>'
select
c.value('#Description', 'varchar(max)') as 'Description'
from #xml.nodes('/root/*') as T(c)
The output is
Description
===========
firstgroup
nextgroup
But I need
Description nodeBXML
=========== ========
firstgroup <nodeB mode="A" ability="read"/>
nextgroup <nodeB count="2" birthplace="Paris"/>
select
c.value('#Description', 'varchar(max)') as 'Description'
, c.query('./nodeB') as Content
from #xml.nodes('/root/*') as T(c)
-- Results to:
Description Content
firstgroup <nodeB mode="A" ability="read" />
nextgroup <nodeB count="2" birthplace="Paris" />
Perhaps something like this:
Example
Select c.value('#Description', 'varchar(max)') as 'Description'
,AsString = convert(varchar(max),c.query('./*[2]') )
,AsXML = c.query('./*[2]')
From #xml.nodes('/root/*') as T(c)
Returns

dc:creator from XML into SQL table

I am trying to store an XML file (Code below) but the the dc:creator is causing an error. I have found from other related questions on here stating that I should use ;WITH XMLNAMESPACES(''http://purl.org/dc/elements/1.1/'' AS dc) but this has not worked either any ideas on what might be the problem/solution ? .
XML file:
<?xml version="1.0" encoding="UTF-8"?>
-<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="http://talksport.com/rss/sports-news/football/feed" version="2.0">
-<channel>
<title>Football</title>
<link>http://talksport.com/rss/sports-news/football/feed</link>
<description/>
<language>en</language>
<atom:link type="application/rss+xml" rel="self" href="http://talksport.com/rss/sports-news/football/feed"/>
-<item>
<title>Hillsborough families 'back introduction of rail seating' as bereaved family says 'standing did not kill our 96'</title>
<link>http://talksport.com/football/hillsborough-families-back-introduction-rail-seating-bereaved-family-says-standing-did-not</link>
<description/>
<pubDate>Wed, 19 Jul 2017 08:18:37 +0000</pubDate>
<dc:creator>talkSPORT</dc:creator>
<guid isPermaLink="false">247276 at http://talksport.com</guid>
</item>
</rss>
This is the stored procedure:
CREATE PROCEDURE feed.usp_importXML(#file VARCHAR(8000))
AS
BEGIN
DECLARE #Query VARCHAR(8000)
SET #Query ='
DECLARE #xmlFile as XML
SET #xmlFile = ( cast
SELECT CONVERT(XML,BulkColumn) as BulkColumn
FROM OPENROWSET (BULK '''+#file+''', SINGLE_BLOB) AS t)
INSERT INTO feed.tempXML (title,link,source)
SELECT
title = t.value (''title[1]'', ''NVARCHAR(300)''),
link = t.value (''link[1]'', ''NVARCHAR(300)''),
source = t.value(''(dc:creator)[1]'',''NVARCHAR(30)'')
FROM #xmlFile.nodes(''/rss/channel/item'') AS xTable(t);'
EXEC(#Query)
END
GO
In your case it might be enough to replace the dc: with a wildcard: *:. Assuming your XML is written into an XML file already, you might try as such:
DECLARE #xmlFile XML=
N'<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="http://talksport.com/rss/sports-news/football/feed" version="2.0">
<channel>
<title>Football</title>
<link>http://talksport.com/rss/sports-news/football/feed</link>
<description />
<language>en</language>
<atom:link type="application/rss+xml" rel="self" href="http://talksport.com/rss/sports-news/football/feed" />
<item>
<title>Hillsborough families ''back introduction of rail seating'' as bereaved family says ''standing did not kill our 96''</title>
<link>http://talksport.com/football/hillsborough-families-back-introduction-rail-seating-bereaved-family-says-standing-did-not</link>
<description />
<pubDate>Wed, 19 Jul 2017 08:18:37 +0000</pubDate>
<dc:creator>talkSPORT</dc:creator>
<guid isPermaLink="false">247276 at http://talksport.com</guid>
</item>
</channel>
</rss>';
SELECT
title = t.value ('title[1]', 'NVARCHAR(300)'),
link = t.value ('link[1]', 'NVARCHAR(300)'),
source = t.value('(*:creator)[1]','NVARCHAR(30)')
FROM #XmlFile.nodes('/rss/channel/item') AS xTable(t);
This will work too:
WITH XMLNAMESPACES('http://purl.org/dc/elements/1.1/' AS dc)
SELECT
title = t.value ('title[1]', 'NVARCHAR(300)'),
link = t.value ('link[1]', 'NVARCHAR(300)'),
source = t.value('(dc:creator)[1]','NVARCHAR(30)')
FROM #XmlFile.nodes('/rss/channel/item') AS xTable(t);
And you can declare the namespace within .value too:
SELECT
title = t.value ('title[1]', 'NVARCHAR(300)'),
link = t.value ('link[1]', 'NVARCHAR(300)'),
source = t.value('declare namespace dc="http://purl.org/dc/elements/1.1/";(dc:creator)[1]','NVARCHAR(30)')
FROM #XmlFile.nodes('/rss/channel/item') AS xTable(t);

parse xml as table

I have some xml like:
<MyDetails>
<detail key="key1" value="value1" />
<detail key="key2" value="value2" />
<detail key="key3" value="value3" />
<detail key="key4" value="value4" />
</MyDetails>
And I want to be able to parse it in a table format of two columns Key, and Value. How can I create a SQL function to achieve this by specifying a Node path i.e. in this case '/MyDetails/detail' and KeyAttributeName and ValueAttributeName. I created the following function but it gives me the error:
ALTER FUNCTION [dbo].[GetXmlTable] (
#XmlSource XML,
#HierarchyPath NVARCHAR(50) = '',
#SpecificKey NVARCHAR(255) = NULL,
#KeyAttributeName NVARCHAR(50) = 'key',
#ValueAttributeName NVARCHAR(50) = 'value'
)
RETURNS #Table TABLE (
[Key] NVARCHAR(255),
[Value] NVARCHAR(500)
)
AS
BEGIN
DECLARE #KeyAttribute NVARCHAR (50) = '#' + #KeyAttributeName
DECLARE #ValueAttribute NVARCHAR (50) = '#' + #ValueAttributeName
DECLARE #Path NVARCHAR (50) = '/' + #HierarchyPath
INSERT INTO #Table
SELECT XmlElement.Attribute.value(#KeyAttribute, 'nvarchar(255)') AS [Key]
,XmlElement.Attribute.value(#ValueAttribute, 'nvarchar(500)') AS [Value]
FROM #XmlSource.nodes(#Path) AS XmlElement(Attribute)
WHERE #SpecificKey IS NULL OR XmlElement.Attribute.value(#KeyAttribute, 'nvarchar(255)') = #SpecificKey
RETURN
END
GO
Error:
Msg 8172, Level 16, State 1, Procedure GetXmlTable, Line 12 The
argument 1 of the XML data type method "nodes" must be a string
literal.
Looking to call the function like this:
select * from dbo.GetXmlTable(CAST('<MyDetails>
<detail key="key1" value="value1" />
<detail key="key2" value="value2" />
<detail key="key3" value="value3" />
<detail key="key4" value="value4" />
</MyDetails>' as XML), 'MyDetails/detail', default, default, default)
UPDATE---------------
I tried using sql variable syntax but the table returned is blank. Can you please point out what I might be doing wrong:
DECLARE #KeyAttr VARCHAR(50) = N'#' + #KeyAttributeName
DECLARE #ValueAttr VARCHAR(50) = N'#' + #ValueAttributeName
DECLARE #Path VARCHAR(100) = '/' + #HierarchyPath
INSERT INTO #Table
SELECT XmlElement.Attribute.value('(*[local-name() = sql:variable("#KeyAttr")])[1]', 'nvarchar(255)') AS [Key]
,XmlElement.Attribute.value('(*[local-name() = sql:variable("#ValueAttr")][1])', 'nvarchar(500)') AS [Value]
FROM #XmlSource.nodes('(*[local-name() = sql:variable("#Path")])') AS XmlElement(Attribute)
WHERE #SpecificKey IS NULL OR XmlElement.Attribute.value('(*[local-name() = sql:variable("#KeyAttr")])[1]', 'nvarchar(255)') = #SpecificKey
This query will shred your XML into a simple table;
declare #xml xml = '<MyDetails>
<detail key="key1" value="value1" />
<detail key="key2" value="value2" />
<detail key="key3" value="value3" />
<detail key="key4" value="value4" />
</MyDetails>'
select
t.c.value('#key', 'varchar(100)') as [key],
t.c.value('#value', 'varchar(100)') as value
from
#xml.nodes('/MyDetails/detail') as t(c)
The error "The argument 1 of the XML data type method "nodes" must be a string literal." means exactly what it says - you can't pass a variable as the argument to the nodes() method. You can however reference variables in the XQuery passed to Nodes() - see sql:variable() for more info.

Searching for "|" pipe symbol in XML column in SQL server

I am using a script like this to look for a "|" symbol in a XML column no matter where it appears. I know there are pipes in there but the below query gives me empty results
SELECT TOP 100 *
FROM
[DB].[dbo].[InputData]
WHERE
Content.exist('//.[text() = "|"]') = 1
AND DataFileId = '75d48aed6327'
What am I doing wrong? This is the xml content of the column Content:
<CLAIM version="native">
<INPUT>
<HEADER BIRTH_DT="1/1/1941">
<DIAG_CODES>
<DX CODE="7234" />
</DIAG_CODES>
<CON_CODES>
<CON_CODE VALUE="M0" />
</CON_CODES>
<VAL_CODES>
<VAL_CODE CODE="A2" AMT="604.03" />
</VAL_CODES>
</HEADER>
</CLAIM>
Hi was looking the answer and found it here https://stackoverflow.com/a/11738172/1692632
DECLARE #xmlTable TABLE (xmlData XML)
INSERT INTO #xmlTable SELECT N'
<CLAIM version="native">
<HEADER BIRTH_DT="1/1/1941">
<DIAG_CODES>
<DX CODE="7234" />
</DIAG_CODES>
<CON_CODES>
<CON_CODE VALUE="M0" />
</CON_CODES>
<VAL_CODES>
<VAL_CODE CODE="A2" AMT="604.03" />
</VAL_CODES>
</HEADER>
</CLAIM>
'
INSERT INTO #xmlTable SELECT N'
<CLAIM version="native">
<HEADER BIRTH_DT="1/1/1941">
<DIAG_CODES>
<DX CODE="72|34" />
</DIAG_CODES>
<CON_CODES>
<CON_CODE VALUE="M0" />
</CON_CODES>
<VAL_CODES>
<VAL_CODE CODE="A2" AMT="604.03" />
</VAL_CODES>
</HEADER>
</CLAIM>
'
SELECT T.*
FROM #xmlTable AS T
CROSS APPLY T.xmlData.nodes('//#*') as tx(r)
WHERE tx.r.value('contains((.),"|")','bit')=1
Also you can try this one as you tried (this gives without duplicates):
SELECT TOP 100 *
FROM
#xmlTable
WHERE
xmlData.exist('//#*[contains(., "|")]') = 1
SELECT TOP 100 *
FROM [DB].[dbo].[InputData]
WHERE DataFileId = '75d48aed6327'
and charindex('|',Content) > 1