parse xml as table - sql

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.

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;

Extracting attributes from XML column in SQL Server 2012

I have a column called MetaXML, which has XML stored inside. I only want to extract information relating to the URLpanel tag. How do I access the node/element down to the URLPanel tag. I also want to separate out the Cap, CaptionText, Height, HeightType, ID and URL (all contained in URLPanel). Is that possible?
Note: I've removed the schema info from the first line...
<DASHBOARD>
<COLUMNS>
<COLUMN Width="19" WidthType="Percent">
<PANELS>
<ACTIONLISTPANEL Cap="8fdd05da-c6b9-41c3-9e4d-5b661c3e134a"
CaptionText="My Action List" Height="14" HeightType="Percent"
ID="CL_Front9.2_c0_r0" />
<PROCESSPANEL Cap="11e934f8-1aed-4fd3-ad95-42049343a390" CaptionText="My
Processes" Height="19" HeightType="Percent" ID="CL_Front9.2_c0_r1">
<APPOBJECTS>
<APPOBJECT AppObjectID="CL_WfMyMattersActList" ID="8a690a0f-268c-
4eb0-8536-6ac09605b7f8" Type="Process" />
<APPOBJECT AppObjectID="CL_ModifyTimeNarrative" ID="d2f320e9-2797-
4631-b63a-5bdcb241d633" Type="Process" />
<APPOBJECT AppObjectID="Invoice Inquiry" ID="7749f30c-acdf-4c09-
b841-60026e274284" Type="Dashboard" />
</APPOBJECTS>
</PROCESSPANEL>
<DASHBOARDENTRY Cap="13d4cd47-a3cf-4222-b03b-932bd8ff6850"
CaptionText="Inquiry" DBID="CliMattInquiry2" Height="50"
HeightType="Percent" ID="CL_Front9.2_c0_r2" />
<REPORTPANEL Cap="9596ced2-76d2-4ebb-a7a3-4f2bd2450fde" CaptionText="My
Details" Height="17" HeightType="Percent" ID="CL_Front9.2_c0_r3">
<VIEWS CurrentViewID="CL_TkprStaticUser">
<VIEW Cap="5f99bd4b-1509-4a65-a54c-07bbc444aceb" CaptionText="My
Details" ReportLayoutID="CL_TkprStaticUser" Type="Report" />
</VIEWS>
</REPORTPANEL>
</PANELS>
</COLUMN>
<COLUMN Width="77" WidthType="Percent">
<PANELS>
<URLPANEL Cap="de940bd4-80f8-47f4-b721-76eed63c650f" CaptionText="Fee
Earner Dashboard" Height="100" HeightType="Percent" ID="CL_Front9.2_c1_r0"
Url="http://ccdc1sql119/ReportServer_SQLR_FINDW_PRD1/Pages/ReportViewer.aspx?%2fDWR%2fMIP+Phase+3%2fMIP_Landing_Indiv&rs:Command=Render&rc:Parameters=Collapsed" />
</PANELS>
</COLUMN>
</COLUMNS>
<SHORTLIST />
</DASHBOARD>
The XML above is quite small, but I cannot seem to extract the URLPanel tag. I've created the following SQL to try to extract the URLPanel tag:
DECLARE #DataID UNIQUEIDENTIFIER
SET #DataID = 'AEFFF874-5980-4340-92C1-ED6B292AA667'
BEGIN
SET NOCOUNT ON;
IF OBJECT_ID('tempdb..#TempXML') IS NOT NULL
DROP TABLE #TempXML
DECLARE #X XML
SET #X = (SELECT D.MetaXML AS 'MetaXML'
FROM NxFWKAppObjectData AS D
WHERE NxFWKAppObjectDataID = #DataID)
DECLARE #iX INT
EXEC sp_xml_preparedocument #ix output, #x
CREATE TABLE #TempXML (URLPanel NVARCHAR(MAX))
INSERT INTO #TempXML(URLPanel)
SELECT
URLPanel
FROM
OPENXML(#iX, '//DASHBOARD/COLUMNS/COLUMN/PANELS/URLPANEL', 2)
WITH
(URLPanel NVARCHAR(MAX))
SELECT *
FROM #TempXML AS DT
EXEC sp_xml_removedocument #iX
DROP TABLE #TempXML
END
I would recommend to use the built-in native XQuery support (instead of the legacy OPENXML approach which has its problems with memory leaks etc.).
Given your #X variable - try this:
SELECT
Cap = XC.value('#Cap', 'varchar(50)'),
CaptionText = XC.value('#CaptionText', 'varchar(250)'),
Height = XC.value('#Height', 'int'),
HeightType = XC.value('#HeightType', 'varchar(50)'),
ID = XC.value('#ID', 'varchar(50)'),
Url = XC.value('#Url', 'varchar(500)')
FROM
#X.nodes('/DASHBOARD/COLUMNS/COLUMN/PANELS/URLPANEL') AS XT(XC)
This hopefully should return the bits of data you're looking for.

Enrich XML document via stored procedure

Our middleware software receives an XML document and forwards it to another software. However, there is one field in the target software (ext_text_10), that I do NOT want to overwrite.
Upon receiving the XML, I would like to call a stored proc, that should query the target database table to find the current ext_text_10 value, and insert a new element in the incoming XML with the resulting value, and then pass it on. This way we will simply pass the current value for that field.
The procedure below is reading the XML, building it again by preserving everything from it, and then inserting one element.
However, it does not return any result. Below is my procedure, and my source XML.
I hope somebody can tell me what is wrong. This is SQL Server 2016.
CREATE PROCEDURE [dbo].[z_ION_GetCurrentCostCenterData] (#xmlData xml)
AS
BEGIN
-- Prevent extra result sets from interfering with SELECT statements
SET NOCOUNT ON;
-- Parse XML
DECLARE #parsedXmlData int;
EXEC sp_xml_preparedocument #parsedXmlData OUTPUT, #xmlData;
DECLARE
#tenantID varchar(50),
#acc_entity varchar(3),
#doc_id varchar(50),
#anl_code varchar(15),
#anl_dim_id varchar(2),
#lookup_code varchar(15),
#anl_code_name varchar(50),
#prohibit_posting varchar(1),
#statusCode varchar(1),
#anl_cat_id varchar(15),
#ext_text_6 varchar(50),
#ext_text_7 varchar(50),
#ext_text_8 varchar(50),
#ext_text_9 varchar(50),
#ext_num_1 varchar(50),
#ext_num_2 varchar(50),
#ext_num_3 varchar(50),
#ext_num_4 varchar(50),
#ext_num_5 varchar(50),
#ext_date_1 varchar(50),
#ext_date_2 varchar(50),
#ext_fixed_1 varchar(50),
#ext_fixed_2 varchar(50),
#ext_fixed_3 varchar(50),
#TableName NVarchar(255)
--Get data from XML
select
#tenantID = tenantID,
#acc_entity = acc_entity,
#doc_id = doc_id,
#anl_code = anl_code,
#anl_dim_id = anl_dim_id,
#lookup_code = lookup_code,
#anl_code_name = anl_code_name,
#prohibit_posting = prohibit_posting,
#statusCode = statusCode,
#anl_cat_id = anl_cat_id,
#ext_text_6 = ext_text_6,
#ext_text_7 = ext_text_7,
#ext_text_8 = ext_text_8,
#ext_text_9 = ext_text_9,
#ext_num_1 = ext_num_1,
#ext_num_2 = ext_num_2,
#ext_num_3 = ext_num_3,
#ext_num_4 = ext_num_4,
#ext_num_5 = ext_num_5,
#ext_date_1 = ext_date_1,
#ext_date_2 = ext_date_2,
#ext_fixed_1 = ext_fixed_1,
#ext_fixed_2 = ext_fixed_2,
#ext_fixed_3 = ext_fixed_3
from OPENXML (#parsedXmlData,'DataArea',2)
with
(tenantID varchar(50) 'Sync/TenantID',
acc_entity varchar(3) 'Sync/AccountingEntityID',
doc_id varchar(50) 'SunSystemsAnalysisCodes/DocumentID/ID',
anl_code varchar(15) 'SunSystemsAnalysisCodes/AnalysisCode',
anl_dim_id varchar(2) 'SunSystemsAnalysisCodes/AnalysisDimensionId',
lookup_code varchar(15) 'SunSystemsAnalysisCodes/LookupCode',
anl_code_name varchar(50) 'SunSystemsAnalysisCodes/Name',
prohibit_posting varchar(1) 'SunSystemsAnalysisCodes/ProhibitPosting',
statusCode varchar(1) 'SunSystemsAnalysisCodes/Status',
anl_cat_id varchar(15) 'SunSystemsAnalysisCodes/AnalysisCatID/AnlCat_SHead',
ext_text_6 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText6',
ext_text_7 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText7',
ext_text_8 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText8',
ext_text_9 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText9',
ext_num_1 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionNumber1',
ext_num_2 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionNumber2',
ext_num_3 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionNumber3',
ext_num_4 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionNumber4',
ext_num_5 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionNumber5',
ext_date_1 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionDate1',
ext_date_2 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionDate2',
ext_fixed_1 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionFixed1',
ext_fixed_2 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionFixed2',
ext_fixed_3 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionFixed3'
);
--Create XML to return
select
[EXT_TEXT_10] as 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText10'
from [dbo].[IMT_ANL_CODE_EXT]
where [ANL_CAT_ID] = #anl_cat_id and [ANL_CODE] = #anl_code
for XML PATH ('SunSystemsAnalysisCodes'), root('DataArea')
EXEC sp_xml_removedocument #parsedXmlData
END
Source XML:
<DataArea xmlns:my="http://schema.infor.com/InforOAGIS/2">
<Sync>
<TenantID>INFRA_TRN</TenantID>
<AccountingEntityID>IMT</AccountingEntityID>
<ActionCriteria>
<ActionExpression actionCode="Change" />
</ActionCriteria>
</Sync>
<SunSystemsAnalysisCodes>
<DocumentID>
<ID variationID="1495808583000">PRO 00002</ID>
</DocumentID>
<IONStatus>
<Code listID="GenericStatus">Open</Code>
</IONStatus>
<AnalysisCode>PRO17</AnalysisCode>
<AnalysisDimensionId>01</AnalysisDimensionId>
<LookupCode>Informatiesyste</LookupCode>
<Name>Informatiesysteem Relatics 10</Name>
<ProhibitPosting>0</ProhibitPosting>
<Status>0</Status>
<AnalysisCatID>
<AnlCat_SHead>COST CENTRE</AnlCat_SHead>
</AnalysisCatID>
<ExtendedAnalysis>
<ExtensionText6>SVE</ExtensionText6>
<ExtensionText7>Open</ExtensionText7>
<ExtensionText8 />
<ExtensionNumber1>81700</ExtensionNumber1>
<ExtensionNumber2 />
<ExtensionNumber3 />
<ExtensionNumber4 />
<ExtensionNumber5 />
<ExtensionDate1>01012016</ExtensionDate1>
<ExtensionDate2>31122016</ExtensionDate2>
<ExtensionFixed1>1</ExtensionFixed1>
<ExtensionFixed2>2</ExtensionFixed2>
<ExtensionFixed3>2</ExtensionFixed3>
</ExtendedAnalysis>
</SunSystemsAnalysisCodes>
</DataArea>
If this doesn't help you, please answer the questions from my comment below your question!
My magic crystall ball tells me, that you might be looking for something like this (shortened for brevity):
DECLARE #xml XML=
N'<DataArea xmlns:my="http://schema.infor.com/InforOAGIS/2">
<Sync>
<TenantID>INFRA_TRN</TenantID>
<!-- more elements -->
</Sync>
<SunSystemsAnalysisCodes>
<DocumentID>
<ID variationID="1495808583000">PRO 00002</ID>
</DocumentID>
<!-- more elements -->
<ExtendedAnalysis>
<ExtensionText6>SVE</ExtensionText6>
<ExtensionText7>Open</ExtensionText7>
<ExtensionText8 />
<ExtensionNumber1>81700</ExtensionNumber1>
<ExtensionNumber2 />
<ExtensionNumber3 />
<ExtensionNumber4 />
<ExtensionNumber5 />
<ExtensionDate1>01012016</ExtensionDate1>
<ExtensionDate2>31122016</ExtensionDate2>
<ExtensionFixed1>1</ExtensionFixed1>
<ExtensionFixed2>2</ExtensionFixed2>
<ExtensionFixed3>2</ExtensionFixed3>
</ExtendedAnalysis>
</SunSystemsAnalysisCodes>
</DataArea>';
--This is the content you want to introduce
DECLARE #ContentToAdd NVARCHAR(MAX)=N'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText10';
--This XML_DML-statement will insert your content with the given name before the <ExtensionNumber1> (which must exist! Other positions are possible of course)
SET #xml.modify(N'insert <ExtensionText10>{sql:variable("#ContentToAdd")}</ExtensionText10>
before (/DataArea/SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionNumber1)[1]');
--Check the output:
SELECT #xml;
<DataArea xmlns:my="http://schema.infor.com/InforOAGIS/2">
<Sync>
<TenantID>INFRA_TRN</TenantID>
<!-- more elements -->
</Sync>
<SunSystemsAnalysisCodes>
<DocumentID>
<ID variationID="1495808583000">PRO 00002</ID>
</DocumentID>
<!-- more elements -->
<ExtendedAnalysis>
<ExtensionText6>SVE</ExtensionText6>
<ExtensionText7>Open</ExtensionText7>
<ExtensionText8 />
<ExtensionText10>SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText10</ExtensionText10>
<ExtensionNumber1>81700</ExtensionNumber1>
<ExtensionNumber2 />
<ExtensionNumber3 />
<ExtensionNumber4 />
<ExtensionNumber5 />
<ExtensionDate1>01012016</ExtensionDate1>
<ExtensionDate2>31122016</ExtensionDate2>
<ExtensionFixed1>1</ExtensionFixed1>
<ExtensionFixed2>2</ExtensionFixed2>
<ExtensionFixed3>2</ExtensionFixed3>
</ExtendedAnalysis>
</SunSystemsAnalysisCodes>
</DataArea>

Converting XML node values to comma separated values in 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, '')

Modify several xml attributes, based on a list

From a previous post:
SQL Server XML add attribute if non-existent
What I would like to do is be able to modify multiple tags. Below is the code that shows what I would like to do, but cannot, since I get the error: The argument 1 of the XML data type method "exist" must be a string literal. Is there a way to modify the XML using variables, rather than literals?
ALTER FUNCTION [dbo].[ConvertXmlData](#xmlData XML)
RETURNS XML
AS
BEGIN
DECLARE #tags TABLE (
ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
TAG VARCHAR(25)
)
INSERT INTO #tags
SELECT 'xxx' UNION
SELECT 'yyy'
DECLARE #counter INT
DECLARE #count INT
DECLARE #id INT
DECLARE #tag VARCHAR(25)
DECLARE #exist VARCHAR(100)
DECLARE #insert VARCHAR(100)
DECLARE #existX VARCHAR(100)
DECLARE #insertX VARCHAR(100)
SET #exist = 'descendant::{0}[not(#runat)]'
SET #insert = 'insert attribute runat { "server" } into descendant::{0}[not(#runat)][1]'
SET #counter = 1
SELECT #count = COUNT(*) FROM #tags
WHILE #counter <= #count BEGIN
SELECT #tag = TAG FROM #tags WHERE ID = #counter
SET #existX = REPLACE(#existX, '[0]', #tag)
WHILE #xmlData.exist(#existX) = 1 BEGIN
SET #xmlData.modify(REPLACE(#insertX, '[0]', #tag));
END
SET #counter = #counter + 1
END
RETURN #xmlData
END
You can't use a variable as an argument to the xml-functions but you can use variables (and table columns) in the literal expression.
I guess this does what you want. At least it should give you an idea of what you can do.
declare #xmlData xml
set #xmlData =
'<something>
<xxx id="1"/>
<xxx id="2" runat="server" />
<xxx id="3"/>
<yyy id="3" />
<zzz id="1"/>
</something>'
declare #tags table
(
id int identity(1,1) primary key,
tag varchar(25)
)
insert into #tags
select 'xxx' union
select 'yyy'
declare #tag varchar(25)
declare #id int
select top 1
#id = id,
#tag = tag
from #tags
order by id
while ##rowcount > 0
begin
while #xmlData.exist('descendant::*[local-name() = sql:variable("#tag") and not(#runat)]') = 1
begin
set #xmlData.modify('insert attribute runat { "server" } into descendant::*[local-name() = sql:variable("#tag") and not(#runat)][1]');
end
select top 1
#id = id,
#tag = tag
from #tags
where id > #id
order by id
end
select #xmlData
Result:
<something>
<xxx id="1" runat="server" />
<xxx id="2" runat="server" />
<xxx id="3" runat="server" />
<yyy id="3" runat="server" />
<zzz id="1" />
</something>