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

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

Related

Extract child node value using SQL QUERY

I have XML in SQL, I want the below result from the query.
TabSeq
Path
SubTabSeq
1
//Tab/Family
1
1
//Tab/Location
2
2
//Tab/Address
1
2
//Tab/Insurance
2
Below is the XML.
<Tabs>
<Tab sequence="001">
<Family sequence="001">
<Data>Yes</Data>
</Family>
<Location sequence="002">
<Data>USA</Data>
</Location>
</Tab>
<Tab sequence="002">
<Address sequence="001">
<Data>XYZ</Data>
</Address>
<Insurance sequence="002">
<Data>Yes</Data>
</Insurance>
</Tab>
</Tabs>
I have tried below,
declare #xml xml='<Tabs><Tab sequence="001"><Family sequence="001"><Data>Yes</Data></Family><Location sequence="002"><Data>USA</Data></Location></Tab><Tab sequence="002"><Address sequence="001"><Data>XYZ</Data></Address><Insurance sequence="002"><Data>Yes</Data></Insurance></Tab></Tabs>'
SELECT t.c.value('#sequence', 'nvarchar(100)') As TabSeq
FROM #xml.nodes('//Tabs/child::node()') as t(c)
SELECT '//Tab' + '/' + c.value('local-name(.)[1]','nvarchar(100)') AS Path, t.c.value('#sequence', 'nvarchar(100)') As SubTabSeq
FROM #xml.nodes('//Tab/child::node()') as t(c)
Is it possible?
In your second query you're iteraing subtabs, so you need to reference a parent of those nodes, which is Tab. Like with those two methods (depends on how strict you want to be):
SELECT
TabSeq1 = t.c.value('../#sequence', 'nvarchar(100)'),
TabSeq2 = t.c.value('./parent::Tab/#sequence', 'nvarchar(100)'),
'//Tab' + '/' + c.value('local-name(.)[1]','nvarchar(100)') AS Path,
t.c.value('#sequence', 'nvarchar(100)') As SubTabSeq
FROM #xml.nodes('//Tab/child::node()') as t(c)
Please try the following solution.
SQL
declare #xml XML =
N'<Tabs>
<Tab sequence="001">
<Family sequence="001">
<Data>Yes</Data>
</Family>
<Location sequence="002">
<Data>USA</Data>
</Location>
</Tab>
<Tab sequence="002">
<Address sequence="001">
<Data>XYZ</Data>
</Address>
<Insurance sequence="002">
<Data>Yes</Data>
</Insurance>
</Tab>
</Tabs>';
SELECT c.value('for $i in . return count(../../*[. << $i])', 'INT') AS [TabSeq]
, '//Tab/' + c.value('local-name(.)','nvarchar(100)') AS Path
, t.c.value('#sequence', 'INT') As SubTabSeq
FROM #xml.nodes('/Tabs/Tab/*') as t(c);
Output
TabSeq
Path
SubTabSeq
1
//Tab/Family
1
1
//Tab/Location
2
2
//Tab/Address
1
2
//Tab/Insurance
2
As mentioned, you are getting the wrong sequence attribute.
You can feed the result of one .nodes call into another using CROSS APPLY, so you can first shred the Tab nodes, then the child nodes
declare #xml xml='<Tabs><Tab sequence="001"><Family sequence="001"><Data>Yes</Data></Family><Location sequence="002"><Data>USA</Data></Location></Tab><Tab sequence="002"><Address sequence="001"><Data>XYZ</Data></Address><Insurance sequence="002"><Data>Yes</Data></Insurance></Tab></Tabs>'
SELECT
TabSeq1 = t2.child.value('#sequence', 'nvarchar(100)'),
TabSeq2 = t1.tab.value('#sequence', 'nvarchar(100)'),
Path = '/Tabs/Tab/' + t2.child.value('local-name(.)','nvarchar(100)'),
Data = t2.child.value('(Data/text())[1]', 'nvarchar(100)')
FROM #xml.nodes('/Tabs/Tab') t1(tab)
CROSS APPLY t1.tab.nodes('*') as t2(child)
db<>fiddle
Note that // descendant axis is slow, you should instead use the / child axis.

Can yo help me with getting some data from a XML?

Getting SQL data from multiple XML
I already tried to put the code in an XML variable and select OrderNumber, ProductionLine and ItemId's but having some troubles with the query.
DECLARE #DXML XML = '<ComDecom OrderNumber="101983026"
ProductionLine="14" BatchNumber="02-00" ItemObjectTypeId="1"
ItemFlag="20" EventGuid="989bfdb4-9dd8-40be-9872-1e0bae7cc4d6"
LastMessage="false" HostName="PMIPTLISWCT0014+1">
<Item ItemId="LESTCNNGxDDCPq1bSF1S119052306" TimeStamp="2019-05-23
07:56:07.475 +01:00" SeqNumber="175660" />
<Item ItemId="LESTCNNGxDDCPq1bSF1S119052306" TimeStamp="2019-05-23
07:56:07.519 +01:00" SeqNumber="175661" />
<Item ItemId="LESTCNoTmCiiVu1bSF1S119052306" TimeStamp="2019-05-23
07:56:08.487 +01:00" SeqNumber="175662" />
</ComDecom>'
SELECT ComDeCom.value('#OrderNumber', 'int') AS OrderNumber
,ComDecom.value('#ProductionLine', 'int') AS ProductionLine
,ItemTbl.value('#ItemId', 'varchar') AS Item
FROM #dxml.nodes('/ComDecom/') AS ComDecomTbl(ComDecom)
CROSS APPLY ComDecom.Item.nodes('Site') AS ItemTbl(Item)
I think you are looking for this-
DECLARE #DXML XML=
'<ComDecom OrderNumber="101983026" ProductionLine="14" BatchNumber="02-00" ItemObjectTypeId="1" ItemFlag="20" EventGuid="989bfdb4-9dd8-40be-9872-1e0bae7cc4d6" LastMessage="false" HostName="PMIPTLISWCT0014+1">
<Item ItemId="LESTCNNGxDDCPq1bSF1S119052306" TimeStamp="2019-05-23 07:56:07.475 +01:00" SeqNumber="175660" />
<Item ItemId="LESTCNNGxDDCPq1bSF1S119052306" TimeStamp="2019-05-23 07:56:07.519 +01:00" SeqNumber="175661" />
<Item ItemId="LESTCNoTmCiiVu1bSF1S119052306" TimeStamp="2019-05-23 07:56:08.487 +01:00" SeqNumber="175662" />
</ComDecom>';
SELECT
T.N.value('#OrderNumber', 'int') AS OrderNumber,
T.N.value('#ProductionLine', 'int') AS ProductionLine,
T2.N2.value('#ItemId', 'varchar(MAX)') AS Item
FROM #dxml.nodes('/ComDecom') AS T(N)
CROSS APPLY #dxml.nodes('/ComDecom/Item') AS T2(N2)
Output is-
OrderNumber ProductionLine Item
101983026 14 LESTCNNGxDDCPq1bSF1S119052306
101983026 14 LESTCNNGxDDCPq1bSF1S119052306
101983026 14 LESTCNoTmCiiVu1bSF1S119052306

FORXML SQL Group By Element

I am trying to group some elements together under one node. This is my current SQL;
declare #xml xml
set #xml = (
select (
select
'DERIVED' '#type',
m.NuixDerivedFieldName '#name', (
SELECT
NuixFieldType as 'metadata/#type',
NuixFieldName as 'metadata/#name'
from eddsdbo.MetadataMapping m1
where m1.NuixDerivedFieldName = m.NuixDerivedFieldName
for xml path ('first-non-blank'), type
)
from (select distinct NuixDerivedFieldName from eddsdbo.MetadataMapping) m
for xml path ('metadata'))
)
;WITH XMLNAMESPACES(DEFAULT 'http://nuix.com/fbi/metadata-profile')
select #xml for XML PATH ('metadata-list'), ROOT ('metadata-profile')
Which gives me the following output;
<metadata-profile xmlns="http://nuix.com/fbi/metadata-profile">
<metadata-list>
<metadata type="DERIVED" name="Barcode" xmlns="">
<first-non-blank>
<metadata type="CUSTOM" name="Barcode" />
</first-non-blank>
<first-non-blank>
<metadata type="EVIDENCE" name="Barcode" />
</first-non-blank>
</metadata>
I want to group together elements together which have the same 'name' attribute of the metadata element under the <first-non-blank> element.
The desired output should be;
<metadata-profile xmlns="http://nuix.com/fbi/metadata-profile">
<metadata-list>
<metadata type="DERIVED" name="Barcode" xmlns="">
<first-non-blank>
<metadata type="CUSTOM" name="Barcode" />
<metadata type="EVIDENCE" name="Barcode" />
</first-non-blank>
</metadata>
...
My database looks something like this;
NuixFieldName NuixFieldType NuixDerivedFieldName
------------------------------ ------------------------------ ------------------------------
_EmailEntryID PROPERTY EmailEntryID
Audited Audited Audited
Author PROPERTY Author
Barcode CUSTOM Barcode
Barcode EVIDENCE Barcode
I would also like to remove the xlmns namespace identifier from the metadata elements.
Thanks in advance!
You could try this
DECLARE #SampleData AS TABLE
(
NuixFieldName varchar(20),
NuixFieldType varchar(20),
NuixDerivedFieldName varchar(20)
)
INSERT INTO #SampleData
VALUES
('_EmailEntryID','PROPERTY','EmailEntryID'),
('Audited', 'Audited ','Audited'),
('Author ', 'PROPERTY','Author '),
('Barcode', 'CUSTOM ','Barcode'),
('Barcode', 'EVIDENCE','Barcode')
DECLARE #xml XML
SET #xml = (
SELECT
-- sd.NuixDerivedFieldName AS [#name],
'DERIVED' AS [#type],
sd.NuixDerivedFieldName AS [#name],
(
SELECT
sd2.NuixFieldType as '#type',
sd2.NuixFieldName as '#name'
FROM #SampleData sd2 WHERE sd2.NuixDerivedFieldName = sd.NuixDerivedFieldName
FOR XML PATH ('metadata'),ROOT('first-non-blank'), TYPE
)
FROM (select DISTINCT sd.NuixDerivedFieldName from #SampleData sd ) sd
FOR XML PATH('metadata'), ROOT('metadata-list'),TYPE
)
;WITH XMLNAMESPACES(DEFAULT 'http://nuix.com/fbi/metadata-profile')
SELECT #xml FOR XML PATH (''),ROOT('metadata-profile')
return:
<metadata-profile xmlns="http://nuix.com/fbi/metadata-profile">
<metadata-list>
<metadata type="DERIVED" name="Audited">
<first-non-blank>
<metadata type="Audited " name="Audited" />
</first-non-blank>
</metadata>
<metadata type="DERIVED" name="Author ">
<first-non-blank>
<metadata type="PROPERTY" name="Author " />
</first-non-blank>
</metadata>
<metadata type="DERIVED" name="Barcode">
<first-non-blank>
<metadata type="CUSTOM " name="Barcode" />
<metadata type="EVIDENCE" name="Barcode" />
</first-non-blank>
</metadata>
<metadata type="DERIVED" name="EmailEntryID">
<first-non-blank>
<metadata type="PROPERTY" name="_EmailEntryID" />
</first-non-blank>
</metadata>
</metadata-list>
</metadata-profile>

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, '')

FOR XML multiple control by attribute in tree concept

I want to figure out one issue.
I already had question about simple ordering issue but I want to order more detail.
check below this link :
SQL Server : FOR XML sorting control by attribute
I made a example case.
SQL Query.
select (
select '123' AS '#id', (
select
(
select 'test' AS '#testid' , '20' AS '#order'
FOR XML path ('tree') , TYPE
),
(
select 'test2' AS '#testid' , '30' AS '#order'
FOR XML path ('tree-order') , TYPE
),
(
select 'test' AS '#testid' , '10' AS '#order'
FOR XML path ('tree') , TYPE
)
FOR XML path ('Node') , TYPE
)
FOR XML path ('Sample') , TYPE
),
(select '456' AS '#id', (
select
(
select 'test' AS '#testid' , '20' AS '#order'
FOR XML path ('tree') , TYPE
),
(
select 'test2' AS '#testid' , '30' AS '#order'
FOR XML path ('tree-order') , TYPE
),
(
select 'test' AS '#testid' , '10' AS '#order'
FOR XML path ('tree') , TYPE
)
FOR XML path ('Node') , TYPE
)
FOR XML path ('Sample') , TYPE)
FOR XML path ('Main') , TYPE
Result :
<Main>
<Sample id="123">
<Node>
<tree testid="test" order="20" />
<tree-order testid="test2" order="30" />
<tree testid="test" order="10" />
</Node>
</Sample>
<Sample id="456">
<Node>
<tree testid="test" order="20" />
<tree-order testid="test2" order="30" />
<tree testid="test" order="10" />
</Node>
</Sample>
</Main>
Expected result :
<Main>
<Sample id="123">
<Node>
<tree testid="test" order="10" />
<tree testid="test" order="20" />
<tree-order testid="test2" order="30" />
</Node>
</Sample>
<Sample id="456">
<Node>
<tree testid="test" order="10" />
<tree testid="test" order="20" />
<tree-order testid="test2" order="30" />
</Node>
</Sample>
</Main>
final result :
<Main>
<Sample id="123">
<Node>
<tree testid="test" />
<tree testid="test" />
<tree-order testid="test2" />
</Node>
</Sample>
<Sample id="456">
<Node>
<tree testid="test" />
<tree testid="test" />
<tree-order testid="test2" />
</Node>
</Sample>
</Main>
That's order by tree-order.
finally I don't want to show order information in attribute
Any one has great Idea?
Thank you for everybody who interesting to this.
Updated ----------------------------------------
Thank you every body finally I solved problem as below about order by and remove attribute issue :
declare #resultData xml = (select #data.query('
element Main {
for $s in Main/Sample
return element Sample {
$s/#*,
for $n in $s/Node
return element Node {
for $i in $n/*
order by $i/#order
return $i
}
}
}'));
SET #resultData.modify('delete (Main/Sample/Node/tree/#order)');
SET #resultData.modify('delete (Main/Sample/Node/tree-order/#order)');
select #resultData
select #data.query('
element Main {
for $s in Main/Sample
return element Sample {
$s/#*,
for $n in $s/Node
return element Node {
for $i in Node/*
order by $i/#order
return
if ($i/self::tree)
then element tree { $i/#testid }
else element tree-order { $i/#testid }
}
}
}
}')
What's interesting to me is that in your original post, you're stating that you're generating the XML as the result of a SQL query. If it were me, I'd control the ordering at that level.