Extract child node value using SQL QUERY - sql

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.

Related

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

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

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

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