SQL query hierarchical XML with multiple sub-elements - sql

I'm using Microsoft SQL server.
I have a simple hierarchy like a directional graph in Xml:
DECLARE #XML as XML = CAST(
'<ROOT>
<NODE NODE_ID="1">
<EDGE>2</EDGE>
<EDGE>3</EDGE>
<EDGE>4</EDGE>
</NODE>
<NODE NODE_ID="2">
<EDGE>1</EDGE>
<EDGE>3</EDGE>
</NODE>
</ROOT>' AS XML);
My desired output would be a table like this:
SOURCE_NODE_ID | DEST_NODE_ID
1 | 2
1 | 3
1 | 4
2 | 1
2 | 3
A query like this:
SELECT B.value('data(#NODE_ID)','int') AS SOURCE_NODE_ID,
A.B.value('(EDGE/text())[1]', 'int') AS DEST_NODE_ID
FROM #XML.nodes('/ROOT/NODE') AS A(B);
Only returns the first edge:
SOURCE_NODE_ID | DEST_NODE_ID
1 | 2
2 | 1
This one does a little better:
SELECT B.value('data(#NODE_ID)','int') AS SOURCE_NODE_ID,
B.query('EDGE').value('.', 'int') AS DEST_NODE_ID
FROM #XML.nodes('/ROOT/NODE') AS A(B);
Only it concatenates all edges into one cell:
SOURCE_NODE_ID | DEST_NODE_ID
1 | 234
2 | 13
How can I get my desired result? Should I join with an inner query or something? Probably I'm making it too complicated, surely there is a simple solution to this?

Try it like this
As there are many NODE elements, you need to call .nodes() for them. As there are many EDGE elements nested, you need to call CROSS APPLY .nodes() for them.
The rest is easy...
DECLARE #XML as XML = CAST(
'<ROOT>
<NODE NODE_ID="1">
<EDGE>2</EDGE>
<EDGE>3</EDGE>
<EDGE>4</EDGE>
</NODE>
<NODE NODE_ID="2">
<EDGE>1</EDGE>
<EDGE>3</EDGE>
</NODE>
</ROOT>' AS XML);
SELECT Nd.value('#NODE_ID','INT') AS SOURCE_NODE_ID
,Edg.value('.','INT') AS DEST_NODE_ID
FROM #XML.nodes('/ROOT/NODE') AS A(Nd)
CROSS APPLY A.Nd.nodes('EDGE') AS B(Edg)
The result
SOURCE_NODE_ID DEST_NODE_ID
1 2
1 3
1 4
2 1
2 3

Related

SQL query XML and return null if node is not existing [duplicate]

This question already has answers here:
CROSS APPLY compared to OUTER APPLY
(2 answers)
Closed last year.
I have xml files where a node (field2) is not always present. If the node is not present i want to get null value.
SELECT
field1, field2
FROM
(SELECT DISTINCT
xmlField1.value('text()[1]','VARCHAR(256)') AS field1,
xmlField2.value('text()[1]','VARCHAR(256)') AS field2
FROM
[dbo].XMLTable t
CROSS APPLY
[XMLData].nodes('/collection/test/field1') A(xmlField1)
CROSS APPLY
[XMLData].nodes('/collection/test/field2') B(xmlField2)) xmlData
This query works if I have both fields in the XML file, but if field2 is missing, I don't get any values returned.
A minimal reproducible example is not provided.
Please try the following conceptual example.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY(1,1) PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata) VALUES
(N'<collection>
<test>
<field1>fld11</field1>
<field2>fld12</field2>
</test>
<test>
<field1>fld12</field1>
</test>
<test>
<field1>fld13</field1>
<field2/>
</test>
</collection>');
-- DDL and sample data population, end
SELECT ID
, c.value('(field1/text())[1]','VARCHAR(256)') AS field1
, c.value('(field2/text())[1]','VARCHAR(256)') AS field2
FROM #tbl
CROSS APPLY xmldata.nodes('/collection/test') AS t(c);
Output
+----+--------+--------+
| ID | field1 | field2 |
+----+--------+--------+
| 1 | fld11 | fld12 |
| 1 | fld12 | NULL |
| 1 | fld13 | NULL |
+----+--------+--------+

How can I select from an xml type in a table?

I can get the values I need as follows:
DECLARE #xml xml;
SELECT TOP 1 #xml = InaSuQtyXml
FROM csn_inventory..tblInventoryCache_st4
WHERE InaDateUpd > '2014-11-01' and InaQty > 0;
SELECT b.value('#SuID', 'int') SuID, b.value('#Quantity', 'int') Quantity
FROM #xml.nodes('/Inventory/Supplier') as a(b);`
And it returns:
SuID | Quantity
-----+---------
275 | 333
7417 | -1
15203| 48
However, when I try to do the same and pull more information from the table as follows, I get an error message that says "nodes" is not a valid function, property, or field.
SELECT
TOP 1
InaPrSKU,
InaDateUpd,
(InaSuQtyXml).nodes('/Inventory/Supplier').value('#SuID', 'int') SuID,
(InaSuQtyXml).nodes('/Inventory/Supplier').value('#Quantity', 'int') Quantity
FROM csn_inventory..tblInventoryCache_st4
WHERE InaDateUpd > '2014-11-01' and InaQty > 0;
I was hoping that query would result in the following, but it doesn't seem to be working. Is there any way to get the following?
InaPruSKU | InaDateUpd | SuID | Quantity
----------+------------+------+---------
AA1433 | 2014-11-10 | 275 | 333
AA1433 | 2014-11-10 | 7417 | -1
AA1433 | 2014-11-10 | 15203| 48
You need to have the cross apply to get the value from node function in the select. Try changing your query like this.
SELECT TOP 1 InaPrSKU,
InaDateUpd,
SuID = Container.value('(SuID)[1]', 'INT'),
Quantity = Container.value('(Quantity)[1]', 'INT'),
FROM csn_inventory..tblInventoryCache_st4
CROSS apply InaSuQtyXml.nodes('/Root/Inventory/Supplier') AS T1 (Container)
WHERE InaDateUpd > '2014-11-01'
AND InaQty > 0;
Example :
CREATE TABLE T
(c3 XML)
INSERT INTO t
(c3)
VALUES ('<Root>
<ProductDescription ProductID="1" ProductName="Road Bike">
<Features>
<Warranty>1 year parts and labor</Warranty>
<Maintenance>3 year parts and labor extended maintenance is available</Maintenance>
</Features>
</ProductDescription>
</Root>')
SELECT Warranty = Container.value('(Warranty)[1]', 'varchar(50)'),
Maintenance = Container.value('(Maintenance)[1]', 'varchar(100)')
FROM T
CROSS apply c3.nodes('/Root/ProductDescription/Features') AS T1 (Container)

SQL server XML Parsing to Table

I am new to SQL xml operations. I have a problem with SQL & XML Parsing.
I have a Table named Task. It has 3 columns:
Name
Task
Date
Task is saving in Hours worked as an xml in the following format:
<TASK>
<A>1</A>
<B>4</B>
<C>0</C>
</TASK>
My table will look like this (with multiple employees)
Name | Task | Date
========================================================================
Employee2219 | <TASK><A>1</A><B>4</B><C>0</C></ TASK > | 2014-01-28
Employee2219 | <TASK><A>3</A><B>2</B><C>1</C></ TASK > | 2014-01-29
....
I need to calculate the hours spend per employee for each task in a week like:
Name | A | B | C | D
===============================
Employee2219 | 4 | 6 | 1 | 0
....
I am new to XML and I could not work this out.
It is straightforward to extract the xml using XQuery:
SELECT
Name,
SUM(TaskA) AS ATotal,
SUM(TaskB) AS BTotal,
SUM(TaskC) AS CTotal,
SUM(TaskD) AS DTotal
FROM
(
SELECT
t.NAME,
t.Date,
Nodes.node.value('(A)[1]', 'int') AS TaskA,
Nodes.node.value('(B)[1]', 'int') AS TaskB,
Nodes.node.value('(C)[1]', 'int') AS TaskC,
Nodes.node.value('(D)[1]', 'int') AS TaskD
FROM
TASK t
cross apply t.Task.nodes('/TASK') as Nodes(node)
) x
WHERE
[Date] BETWEEN '2014-01-01' AND '2014-01-07' -- Your week range
GROUP BY
Name;
SqlFiddle here
Please post your question in text - many corporate SO users are blocked from sites like imgur, so answering becomes guess work.

Query XML File uploaded to single column in SQL Server

I am trying to learn XQuery and Xpath in SQL Server
I created a sample file and uploaded it to a Table with 2 columns ID, XMLDoc. The below code is within the document in the XMLDoc column so it is the only record in the column.
I am trying to query the file so it will show all the results in a table like a normal select statement would. How would you construct the select statement to select all the information like a select * ? How would you select one field like all suppliers? I would like to select the supplier, requestor for each item.
Here is the xml:
<tst:Document xmlns:tst ="http://www.w3.org/2001/XMLSchema" SchemaVersion="0.1" Classification="Test" UniqueIdentifier="1234" Title="Test">
<tst:Revision RevNumber="0" TimeStamp="2013-01-21T12:56:00">
<tst:Author Name="Me" Guid="1234" />
</tst:Revision>
<tst:Formats>
<tst:A12 Item="1">
<tst:Requestor Name="ADC" />
<tst:Supplier Name="BBC" />
<tst:Code>B</tst:Code>
<tst:IsRequirement>true</tst:IsRequirement>
<tst:IsNotRequired>false</tst:IsInformation>
<tst:Remarks>ADC (Random Input Section)</tst:Remarks>
<tst:Notes>Next Round.</tst:Notes>
<tst:Events>
<tst:SubTest Item="0">
<tst:BLDG>BLDG1</tst:BLDG>
<tst:BLDG2>BLDG2</tst:BLDG2>
<tst:Function>Testing</tst:Function>
<tst:Desciption>Normal Flow</tst:Desciption>
</tst:SubTest>
</tst:Events>
<tst:IsReady>true</tst:IsReady>
<tst:IsNotReady>false</tst:IsNotReady>
</tst:A12>
<tst:A12 Item="2">
<tst:Requestor Name="ADC" />
<tst:Supplier Name="BBC" />
<tst:Code>A</tst:Code>
<tst:IsRequirement>true</tst:IsRequirement>
<tst:IsInformation>false</tst:IsInformation>
<tst:Remarks>Requirement Not yet met.</tst:Remarks>
<tst:Notes>Ready.</tst:Notes>
<tst:Events>
<tst:SubTest Item="0">
<tst:BLDG>BLDG3</tst:BLDG>
<tst:BLDG2>BLDG4</tst:BLDG2>
<tst:TotalEvents>1</tst:TotalEvents>
<tst:Function>Development</tst:Function>
<tst:Desciption>Process Flow</tst:Desciption>
</tst:SubTest>
</tst:Events>
<tst:IsReady>true</tst:IsReady>
<tst:IsNotReady>false</tst:IsNotReady>
</tst:A12>
</tst:Formats>
</tst:Document>
Query I ran
I just got a return, but it is still showing it in xml form:
Select XMLDoc.query('/*/*/*/*[local-name()=("Requestor", "Supplier")]')
From XMLLoad
I Updated the xml snippet, sry had a typo! It will load now
INSERT INTO TableName(ColumnName)
SELECT * FROM OPENROWSET(
BULK 'C:\Users\Filepath.xml',
SINGLE_BLOB) AS x;
SQL Fiddle
MS SQL Server 2008 Schema Setup:
create table XMLDoc (XMLLoad xml);
insert into XMLDoc(XMLLoad) values('
<tst:Document xmlns:tst ="http://www.w3.org/2001/XMLSchema" SchemaVersion="0.1" Classification="Test" UniqueIdentifier="1234" Title="Test">
<tst:Revision RevNumber="0" TimeStamp="2013-01-21T12:56:00">
<tst:Author Name="Me" Guid="1234" />
</tst:Revision>
<tst:Formats>
<tst:A12 Item="1">
<tst:Requestor Name="ADC" />
<tst:Supplier Name="BBC" />
<tst:Code>B</tst:Code>
<tst:IsRequirement>true</tst:IsRequirement>
<tst:IsInformation>false</tst:IsInformation>
<tst:Remarks>ADC (Random Input Section)</tst:Remarks>
<tst:Notes>Next Round.</tst:Notes>
<tst:Events>
<tst:SubTest Item="0">
<tst:BLDG>BLDG1</tst:BLDG>
<tst:BLDG2>BLDG2</tst:BLDG2>
<tst:Function>Testing</tst:Function>
<tst:Desciption>Normal Flow</tst:Desciption>
</tst:SubTest>
</tst:Events>
<tst:IsReady>true</tst:IsReady>
<tst:IsNotReady>false</tst:IsNotReady>
</tst:A12>
<tst:A12 Item="2">
<tst:Requestor Name="ADC" />
<tst:Supplier Name="BBC" />
<tst:Code>A</tst:Code>
<tst:IsRequirement>true</tst:IsRequirement>
<tst:IsInformation>false</tst:IsInformation>
<tst:Remarks>Requirement Not yet met.</tst:Remarks>
<tst:Notes>Ready.</tst:Notes>
<tst:Events>
<tst:SubTest Item="0">
<tst:BLDG>BLDG3</tst:BLDG>
<tst:BLDG2>BLDG4</tst:BLDG2>
<tst:TotalEvents>1</tst:TotalEvents>
<tst:Function>Development</tst:Function>
<tst:Desciption>Process Flow</tst:Desciption>
</tst:SubTest>
</tst:Events>
<tst:IsReady>true</tst:IsReady>
<tst:IsNotReady>false</tst:IsNotReady>
</tst:A12>
</tst:Formats>
</tst:Document>');
Query 1:
with xmlnamespaces('http://www.w3.org/2001/XMLSchema' as tst)
select A12.X.value('#Item', 'int') as A12,
A12.X.value('tst:Requestor[1]/#Name', 'varchar(25)') as Requestor,
A12.X.value('tst:Supplier[1]/#Name', 'varchar(25)') as Supplier,
A12.X.value('(tst:Code/text())[1]', 'varchar(25)') as Code,
A12.X.value('(tst:IsRequirement/text())[1]', 'bit') as IsRequirement,
A12.X.value('(tst:IsInformation/text())[1]', 'bit') as IsInformation,
A12.X.value('(tst:Remarks/text())[1]', 'varchar(50)') as Remarks,
A12.X.value('(tst:Notes/text())[1]', 'varchar(50)') as Notes,
ST.X.value('#Item', 'int') as SubTest,
ST.X.value('(tst:BLDG/text())[1]', 'varchar(25)') as BLDG,
ST.X.value('(tst:BLDG2/text())[1]', 'varchar(25)') as BLDG2,
ST.X.value('(tst:TotalEvents/text())[1]', 'int') as TotalEvents,
ST.X.value('(tst:Function/text())[1]', 'varchar(25)') as [Function],
ST.X.value('(tst:Desciption/text())[1]', 'varchar(50)') as Desciption
from XMLDoc as X
cross apply X.XMLLoad.nodes('/tst:Document/tst:Formats/tst:A12') as A12(X)
cross apply A12.X.nodes('tst:Events/tst:SubTest') as ST(X)
Results:
| A12 | REQUESTOR | SUPPLIER | CODE | ISREQUIREMENT | ISINFORMATION | REMARKS | NOTES | SUBTEST | BLDG | BLDG2 | TOTALEVENTS | FUNCTION | DESCIPTION |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 1 | ADC | BBC | B | 1 | 0 | ADC (Random Input Section) | Next Round. | 0 | BLDG1 | BLDG2 | (null) | Testing | Normal Flow |
| 2 | ADC | BBC | A | 1 | 0 | Requirement Not yet met. | Ready. | 0 | BLDG3 | BLDG4 | 1 | Development | Process Flow |
Check out value() and nodes().

xQuery and T-SQL to Extract Data

Got some basic XML as a XML datatype within SQL 2005. One record/row looks like this
<doc>
<level1>
<level2>
<name>James</name>
<age>12</age>
</level2>
<level2>
<name>John</name>
<age>23</age>
</level2>
</level1>
</doc>
When I perform some basic T_SQL
SELECT TOP 1
DocumentXML.query('data(//doc/name)'),
DocumentXML.query('data(//doc/age)')
FROM [DBNAME].[dbo].[TBLNAME]
I get
ID | Name | Age
----------------------
1 | JamesJohn | 1223
How do I re-write the T-SQL so it displays as
ID | Name | Age
--------------------
1 | James | 12
2 | John | 23
Your example doesn't work for me; the second level2 opens with </level2>. And //doc/name doesn't exist; might be //doc/level1/level2/name.
Here's an example of how to retrieve a rowset from an XML:
declare #t table (id int identity, doc xml)
insert #t (doc) values (
'<doc>
<level1>
<level2>
<name>James</name>
<age>12</age>
</level2>
<level2>
<name>John</name>
<age>23</age>
</level2>
</level1>
</doc>')
SELECT x.a.value('(name)[1]','varchar(50)') as col1
, x.a.value('(age)[1]','varchar(50)') as col2
FROM #t t
cross apply
t.doc.nodes('//level2') x(a)
This prints:
col1 col2
James 12
John 23