Parsing XML Data using T-SQL - sql-server

I am attempting to parse XML data using SQL Server 2019.
I have the following dataset:
<?xml version='1.0' encoding='UTF-8'?>
<wd:Report_Data xmlns:wd="urn:com.test.report/Reported_Time_Blocks_for_a_Worker_s__-_Test_Worked_Hours_-_Copy">
<wd:Report_Entry>
<wd:Worker_group>
<wd:Employee_ID>111111</wd:Employee_ID>
<wd:Legal_Name_-_First_Name>TestFirstName</wd:Legal_Name_-_First_Name>
<wd:Legal_Name_-_Last_Name>TestLastName</wd:Legal_Name_-_Last_Name>
</wd:Worker_group>
<wd:Employee_Type wd:Descriptor="Property">
<wd:ID wd:type="WID">xxxxxxxxxxxxxxxxxxxxxxxxxxx</wd:ID>
<wd:ID wd:type="Employee_Type_ID">Regular</wd:ID>
</wd:Employee_Type>
<wd:Pay_Rate_Type wd:Descriptor="Salary">
<wd:ID wd:type="WID">xxxxxxxxxxxxxxxxxxxxxxxxxxx</wd:ID>
<wd:ID wd:type="Pay_Rate_Type_ID">Salary</wd:ID>
</wd:Pay_Rate_Type>
<wd:Pay_Rate>0</wd:Pay_Rate>
<wd:Home_Cost_Center wd:Descriptor="CC0001 Test Property">
<wd:ID wd:type="WID">xxxxxxxxxxxxxxxxxxxxxxxxxx</wd:ID>
<wd:ID wd:type="Organization_Reference_ID">CC0001</wd:ID>
<wd:ID wd:type="Cost_Center_Reference_ID">CC0001</wd:ID>
</wd:Home_Cost_Center>
<wd:Home_Subarea wd:Descriptor="HS001 Test Home">
<wd:ID wd:type="WID">xxxxxxxxxxxxxxxxxxxxxxxxxx</wd:ID>
<wd:ID wd:type="Organization_Reference_ID">HS001</wd:ID>
<wd:ID wd:type="Custom_Organization_Reference_ID">HS001</wd:ID>
</wd:Home_Subarea>
<wd:Home_Job_Profile wd:Descriptor="Test Job Title">
<wd:ID wd:type="WID">6db2e1cea51601ac7fdd4d733e001646</wd:ID>
<wd:ID wd:type="Job_Profile_ID">100000001</wd:ID>
</wd:Home_Job_Profile>
<wd:Reported_Date>2022-04-11-07:00</wd:Reported_Date>
<wd:Reported_Quantity>8</wd:Reported_Quantity>
<wd:Time_Entry_Code wd:Descriptor="Worked Time (Hours Only)">
<wd:ID wd:type="WID">xxxxxxxxxxxxxxxxxxxxxxxxxx</wd:ID>
<wd:ID wd:type="Time_Code_Reference_ID">Worked_Time_Hours_Only_Time_Code</wd:ID>
</wd:Time_Entry_Code>
<wd:Calculation_Tags wd:Descriptor="Regular">
<wd:ID wd:type="WID">xxxxxxxxxxxxxxxxxxxxxxxxxx</wd:ID>
<wd:ID wd:type="Time_Calculation_Tag_ID">Regular</wd:ID>
</wd:Calculation_Tags>
</wd:Report_Entry>
I know how to get some of the data, but I can't figure out how to work with the multiple row groups.
I have this query that pulls the Employee_ID, Legal_Name_-First_Name, and Legal_Name-_Last_Name, but I can't figure out how to also pull the Employee_Type, Pay_Rate_Type, etc. with it:
DECLARE #XML XML
SET #XML = ( SELECT XMLData FROM #XML )
DECLARE #XML XML
SET #XML = ( SELECT XMLData FROM #XML )
;WITH XMLNAMESPACES( 'urn:com.test.report/Reported_Time_Blocks_for_a_Worker_s__-_Test_Worked_Hours_-_Copy' AS wd )
SELECT EmployeeId = Data.Col.value( 'wd:Employee_ID[1]', 'VARCHAR(12)' ),
FirstName = Data.Col.value( 'wd:Legal_Name_-_First_Name[1]', 'VARCHAR(100)' ),
LastName = Data.Col.value( 'wd:Legal_Name_-_Last_Name[1]', 'VARCHAR(100)' )
--INTO #EmployeeInformation
FROM #XML.nodes( 'wd:Report_Data/wd:Report_Entry/wd:Worker_group' ) Data( Col )
I am essentially trying to pull the following data from the XML above:
Employee_ID
Legal_Name_-_First_Name
Legal_Name_-_Last_Name
Employee_Type_ID
Pay_Rate_Type_ID
Pay_Rate
Cost_Center_Reference_ID
Reported_Date
Reported_Quantity
Home_Job_Profile_Descriptor
111111
TestFirstName
TestLastName
Regular
Salary
0
CC0001
2022-04-11-07:00
8
Test Job Title

Please try the following solution.
You may need to adjust data types.
SQL
DECLARE #xml XML =
N'<wd:Report_Data xmlns:wd="urn:com.test.report/Reported_Time_Blocks_for_a_Worker_s__-_Test_Worked_Hours_-_Copy">
<wd:Report_Entry>
<wd:Worker_group>
<wd:Employee_ID>111111</wd:Employee_ID>
<wd:Legal_Name_-_First_Name>TestFirstName</wd:Legal_Name_-_First_Name>
<wd:Legal_Name_-_Last_Name>TestLastName</wd:Legal_Name_-_Last_Name>
</wd:Worker_group>
<wd:Employee_Type wd:Descriptor="Property">
<wd:ID wd:type="WID">xxxxxxxxxxxxxxxxxxxxxxxxxxx</wd:ID>
<wd:ID wd:type="Employee_Type_ID">Regular</wd:ID>
</wd:Employee_Type>
<wd:Pay_Rate_Type wd:Descriptor="Salary">
<wd:ID wd:type="WID">xxxxxxxxxxxxxxxxxxxxxxxxxxx</wd:ID>
<wd:ID wd:type="Pay_Rate_Type_ID">Salary</wd:ID>
</wd:Pay_Rate_Type>
<wd:Pay_Rate>0</wd:Pay_Rate>
<wd:Home_Cost_Center wd:Descriptor="CC0001 Test Property">
<wd:ID wd:type="WID">xxxxxxxxxxxxxxxxxxxxxxxxxx</wd:ID>
<wd:ID wd:type="Organization_Reference_ID">CC0001</wd:ID>
<wd:ID wd:type="Cost_Center_Reference_ID">CC0001</wd:ID>
</wd:Home_Cost_Center>
<wd:Home_Subarea wd:Descriptor="HS001 Test Home">
<wd:ID wd:type="WID">xxxxxxxxxxxxxxxxxxxxxxxxxx</wd:ID>
<wd:ID wd:type="Organization_Reference_ID">HS001</wd:ID>
<wd:ID wd:type="Custom_Organization_Reference_ID">HS001</wd:ID>
</wd:Home_Subarea>
<wd:Home_Job_Profile wd:Descriptor="Test Job Title">
<wd:ID wd:type="WID">6db2e1cea51601ac7fdd4d733e001646</wd:ID>
<wd:ID wd:type="Job_Profile_ID">100000001</wd:ID>
</wd:Home_Job_Profile>
<wd:Reported_Date>2022-04-11-07:00</wd:Reported_Date>
<wd:Reported_Quantity>8</wd:Reported_Quantity>
<wd:Time_Entry_Code wd:Descriptor="Worked Time (Hours Only)">
<wd:ID wd:type="WID">xxxxxxxxxxxxxxxxxxxxxxxxxx</wd:ID>
<wd:ID wd:type="Time_Code_Reference_ID">Worked_Time_Hours_Only_Time_Code</wd:ID>
</wd:Time_Entry_Code>
<wd:Calculation_Tags wd:Descriptor="Regular">
<wd:ID wd:type="WID">xxxxxxxxxxxxxxxxxxxxxxxxxx</wd:ID>
<wd:ID wd:type="Time_Calculation_Tag_ID">Regular</wd:ID>
</wd:Calculation_Tags>
</wd:Report_Entry>
</wd:Report_Data>';
;WITH XMLNAMESPACES(DEFAULT 'urn:com.test.report/Reported_Time_Blocks_for_a_Worker_s__-_Test_Worked_Hours_-_Copy')
SELECT EmployeeId = c.value('(Worker_group/Employee_ID/text())[1]', 'VARCHAR(12)' )
, FirstName = c.value('(Worker_group/Legal_Name_-_First_Name/text())[1]', 'VARCHAR(100)' )
, LastName = c.value('(Worker_group/Legal_Name_-_Last_Name/text())[1]', 'VARCHAR(100)' )
, Employee_Type_ID = c.value('(Employee_Type/ID[#*:type="Employee_Type_ID"]/text())[1]', 'VARCHAR(100)' )
, Pay_Rate_Type_ID = c.value('(Pay_Rate_Type/ID[#*:type="Pay_Rate_Type_ID"]/text())[1]', 'VARCHAR(100)' )
, Pay_Rate = c.value('(Pay_Rate/text())[1]', 'VARCHAR(100)' )
, Cost_Center_Reference_ID = c.value('(Home_Cost_Center/ID[#*:type="Cost_Center_Reference_ID"]/text())[1]', 'VARCHAR(100)' )
, Reported_Date = c.value('(Reported_Date/text())[1]', 'VARCHAR(100)' )
, Reported_Quantity = c.value('(Reported_Quantity/text())[1]', 'VARCHAR(100)' )
, Home_Job_Profile_Descriptor = c.value('(Home_Job_Profile/#*:Descriptor)[1]', 'VARCHAR(100)' )
FROM #XML.nodes('/Report_Data/Report_Entry') AS t(c);
Output
+------------+---------------+--------------+------------------+------------------+----------+--------------------------+------------------+-------------------+-----------------------------+
| EmployeeId | FirstName | LastName | Employee_Type_ID | Pay_Rate_Type_ID | Pay_Rate | Cost_Center_Reference_ID | Reported_Date | Reported_Quantity | Home_Job_Profile_Descriptor |
+------------+---------------+--------------+------------------+------------------+----------+--------------------------+------------------+-------------------+-----------------------------+
| 111111 | TestFirstName | TestLastName | Regular | Salary | 0 | CC0001 | 2022-04-11-07:00 | 8 | Test Job Title |
+------------+---------------+--------------+------------------+------------------+----------+--------------------------+------------------+-------------------+-----------------------------+

Related

get values from xml by sql query when several attributes

There is xml with several attributes "Num"
DECLARE #XML XML = '
<FileId global_id="1234">
<file id="12aa">
</file>
<file id="12bb">
<Number Num = "1"/>
<Number Num = "2"/>
</file>
</FileId>';
With this sql query only one attribute can be get
SELECT F.[File].value(N'../#global_id','varchar(100)') as id_payment,
F.[File].value('#id', 'varchar(4)') AS id,
F.[File].value('(Number/#Num)[1]', 'int') as [Num]
FROM (VALUES (#XML)) V (X)
CROSS APPLY V.X.nodes('/FileId/file') F([File])
How to get all attributes -- Num = 1 and Num = 2.
Can be a variable amount of attributes.
id_payment id Num
1234 12aa NULL
1234 12bb 1
1234 12bb 2
Much simpler version. (1) No need to use the VALUES clause. (2) The OUTER APPLY simulates LEFT OUTER JOIN. (3) Most efficient way to retrieve the global_id attribute. The credit goes to Shnugo.
SQL
DECLARE #XML XML = N'
<FileId global_id="1234">
<file id="12aa">
</file>
<file id="12bb">
<Number Num="1"/>
<Number Num="2"/>
</file>
</FileId>';
SELECT #xml.value('(/FileId/#global_id)[1]','INT') AS id_payment
, c.value('#id', 'VARCHAR(4)') AS id
, n.value('#Num', 'INT') AS [Num]
FROM #xml.nodes('/FileId/file') AS t(c)
OUTER APPLY t.c.nodes('Number') AS t2(n);
Output
+------------+------+------+
| id_payment | id | Num |
+------------+------+------+
| 1234 | 12aa | NULL |
| 1234 | 12bb | 1 |
| 1234 | 12bb | 2 |
+------------+------+------+
DECLARE #XML XML = '
<FileId global_id="1234">
<file id="12aa">
</file>
<file id="12bb">
<Number Num = "1"/>
<Number Num = "2"/>
<Number Num = "3"/>
<Number Num = "4"/>
<Number Num = "5"/>
<Number Num = "6"/>
</file>
</FileId>';
SELECT F.[File].value(N'../#global_id','varchar(100)') as id_payment,
F.[File].value('#id', 'varchar(4)') AS id,
F.[File].value('(Number/#Num)[1]', 'int') as [Num],
n.num.value('(#Num)[1]', 'int') as [Numxyz]
FROM (VALUES (#XML)) V (X)
CROSS APPLY V.X.nodes('/FileId/file') F([File])
outer apply F.[File].nodes('Number') as n(num)

Generate BOD OAGIS XML from SQL Server

I need to generate an XML document from SQL Server that conforms to the BOD OAGIS schema.
I have a partial solution but am not getting the exact format I need.
This is my query:
SELECT
(
SELECT Customer As 'SalesOrderHeader/CustomerParty/ID',
(
SELECT LinesNo As 'LineNumber'
FROM LinesInterfaccia
WHERE HeaderInterfaccia.OrderNo = LinesInterfaccia.OrderNo
FOR XML path('salesline'), TYPE
)
FROM HeaderInterfaccia
FOR XML path('salesorder'), type
).query('for $i in /salesorder return <DataArea>{$i}</DataArea>');
This is my result:
<DataArea>
<salesorder>
<SalesOrderHeader>
<CustomerParty>
<ID>BP00003184</ID>
</CustomerParty>
</SalesOrderHeader>
<salesline>
<LineNumber>10</LineNumber>
</salesline>
<salesline>
<LineNumber>20</LineNumber>
</salesline>
</salesorder>
</DataArea>
<DataArea>
<salesorder>
<SalesOrderHeader>
<CustomerParty>
<ID>BP00003184</ID>
</CustomerParty>
</SalesOrderHeader>
<salesline>
<LineNumber>10</LineNumber>
</salesline>
</salesorder>
</DataArea>
but I need this format:
<DataArea>
<Process>tenant</Process>
<salesorder>
<SalesOrderHeader>
<CustomerParty>
<ID>BP00003184</ID>
</CustomerParty>
</SalesOrderHeader>
<salesline>
<LineNumber>10</LineNumber>
</salesline>
<salesline>
<LineNumber>20</LineNumber>
</salesline>
</salesorder>
</DataArea>
<DataArea>
<Process>tenant</Process>
<salesorder>
<SalesOrderHeader>
<CustomerParty>
<ID>BP00003184</ID>
</CustomerParty>
</SalesOrderHeader>
<salesline>
<LineNumber>10</LineNumber>
</salesline>
</salesorder>
</DataArea>
I also tried:
SELECT
(
SELECT 'tenant' as 'Process',
Customer As 'SalesOrderHeader/CustomerParty/ID',
(select LinesNo as 'LineNumber/no'
with result:
<DataArea>
<salesorder>
<Process>tenant</Process>
<SalesOrderHeader>
My expected structure is:
<DataArea>
<Process>
</Process>
<SalesOrder>
<SalesLine>
</Salesline>
<SalesLine>
</Salesline>
</salesOrder>
</DataArea>
Try it like this:
DECLARE #tblOrder TABLE(OrderID INT IDENTITY, CustomerID VARCHAR(100));
INSERT INTO #tblOrder VALUES('BP00003184')
,('One More');
DECLARE #tblOrderLine TABLE(OrderLineID INT IDENTITY, OrderID INT, LineNumber INT);
INSERT INTO #tblOrderLine VALUES(1,10),(1,20)
,(2,22),(2,33);
SELECT 'tenant' AS Process
,o.CustomerID AS [salesorder/SalesOrderHeader/CustomerParty/ID]
,(
SELECT LineNumber
FROM #tblOrderLine AS ol
WHERE ol.OrderID=o.OrderID
FOR XML PATH('salesline'),TYPE
) AS salesorder
FROM #tblOrder AS o
FOR XML PATH('DataArea');
This structure is missing a root element, which is - by definition invalid XML. SQL Server has no problem with this, but other engines might have...
You can think of the process as such:
Go down the columns of the select
Oh, there's a new XPath (Process), let's open a new tag
Oh, the next one is new, close /Process
Uh, quite large (salesorder/SalesOrderHeader/CustomerParty/ID), open it!
Ah, this is an XML itself. It will be called salesorder. Nice! still open!
Good, I found the FROM, let's close the current XPath

Querying XML file in SQL Server

This is my xml file
<Detials xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Parents>
<Parent id="1234">
<name>
<firstname>ABC</firstname>
<lastname>XYSX</lastname>
</name>
</Parent>
<Parent id="1235">
<name>
<firstname>TFU</firstname>
<lastname>GHY</lastname>
</name>
</Parent>
</Parents>
<Children>
<Child id="457" Parentid="1234">
<name>
<cfirstname>JOHN</cfirstname>
<clastname>SMITH</clastname>
</name>
</Child>
<Child id="459" Parentid="1235">
<name>
<cfirstname>DAVID</cfirstname>
<clastname>SMITH</clastname>
</name>
</Child>
</Children>
</Detials>
I stored it in a table (using Bulk Insert).
When I query like this
SELECT
x.c.value('(./Parents/Parent/#id)[1]', 'nvarchar(4)' ) AS id
FROM
T1 s
CROSS APPLY
s.XMLData.nodes('Detials') AS x(c)
I get the result as
Id
----
1234
When I changed it a little bit
SELECT
x.c.value('#id', 'nvarchar(4)' ) AS id
FROM
T1 s
CROSS APPLY
s.XMLData.nodes('/Detials/Parents/Parent') AS x(c)
I get:
Id
----
1234
1235
I just want to query them to result like this
Parent_id | firstname | lastname | Child_id | Child_First_name | child_last_name
----------+-----------+----------+----------+------------------+-----------
1234 | ABC | XYSX | 457 | JOHN | SMITH
1235 | TFU | GHY | 459 | DAVID | SMITH
How could I do this in query ?
In my result I want total no. of rows which depends on the Parentid. If my xml file contains 6 <parent id> then my query should show all 6 rows for each parent id's along with comparing the Childid's to also be included if the Parentid matches in <child> tag.
Thanks, Jayendran
You were on the right track, but this would require a left join on the two result sets Parent/Children
Example
Declare #T1 table (ID int,XMLData xml)
Insert Into #T1 values
(1,'<Detials xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Parents><Parent id="1234"><name><firstname>ABC</firstname><lastname>XYSX</lastname></name></Parent><Parent id="1235"><name><firstname>TFU</firstname><lastname>GHY</lastname></name></Parent></Parents><Children><Child id="457" Parentid="1234"><name><cfirstname>JOHN</cfirstname><clastname>SMITH</clastname></name></Child><Child id="459" Parentid="1235"><name><cfirstname>DAVID</cfirstname><clastname>SMITH</clastname></name></Child></Children></Detials>')
Select A.ID
,B.*
From #T1 A
Cross Apply (
Select B1.Parent_id
,B1.First_Name
,B1.Last_Name
,B2.Child_id
,B2.Child_First_name
,B2.child_last_name
From (
Select
Parent_id = x.c.value('#id', 'nvarchar(4)' )
,First_Name = x.c.value('(name/firstname)[1]','varchar(50)')
,Last_Name = x.c.value('(name/lastname)[1]','varchar(50)')
From #T1 s
Cross Apply s.XMLData.nodes('/Detials/Parents/Parent') AS x(c)
) B1
Left Join (
Select
pt_id = x.c.value('#Parentid', 'nvarchar(4)' )
,Child_id = x.c.value('#id', 'nvarchar(4)' )
,Child_First_name = x.c.value('(name/cfirstname)[1]','varchar(50)')
,child_last_name = x.c.value('(name/clastname)[1]','varchar(50)')
From #T1 s
Cross Apply s.XMLData.nodes('/Detials/Children/Child ') AS x(c)
) B2
on B1.Parent_id = B2.pt_id
) B
Returns
I'd solve this in one single go:
SELECT Parent.ID
,p.value('(name/firstname/text())[1]','nvarchar(max)')
,p.value('(name/lastname/text())[1]','nvarchar(max)')
,c.value('#id','int')
,c.value('(name/cfirstname/text())[1]','nvarchar(max)')
,c.value('(name/clastname/text())[1]','nvarchar(max)')
FROM #xml.nodes('/Detials/Parents/Parent') AS A(p)
OUTER APPLY (SELECT p.value('#id','int')) AS Parent(ID)
OUTER APPLY #xml.nodes('/Detials/Children/Child[#Parentid=sql:column("Parent.ID")]') AS B(c);
The trick is, to read the Parentid into a named column via APPLY. This value can be used as predicate to fetch the children via sql:column().

Add count to the root elements using FOR XML PATH

I have a Sql statement that returns xml of products in which root element is products. How can i add count attribute to the root element.
My sql is:
SELECT
id AS 'product_id',
name AS 'product_name'
FROM product
WHERE status = 1 AND ......
ORDER BY productid
FOR XML PATH('product'), ROOT('products')
Result is
<products>
<product>
.
.
</product>
</products>
I want to change result to
<products count="100">
<product>
.
.
</product>
</products>
SELECT
COUNT(*) AS '#count',
(
SELECT *
FROM product c1 FOR XML PATH('product'), TYPE
)
FROM product ct FOR XML PATH('products')
The easiest way to filter is to add condition in both query and sub-query:
SELECT
COUNT(*) AS '#count',
(
SELECT *
FROM product c1
WHERE c1.status = 1 AND ......
FOR XML PATH('product'), TYPE
)
FROM product ct
WHERE ct.status = 1 AND ......
FOR XML PATH('products')
Or use temp-table:
SELECT *
INTO #Temp
FROM product c1
WHERE c1.status = 1 AND ......
SELECT
COUNT(*) AS '#count',
(
SELECT *
FROM #Temp c1
FOR XML PATH('product'), TYPE
)
FROM #Temp ct
FOR XML PATH('products')

How to get an element tag in SQL Server

I have a stored procedure that receives some XML like this:
<Root>
<pickh attribute1="897" attribute2="GGG" ....>
<pickd attribute1="123" attribute2="678" ..../>
</pickh>
</Root>
or
<Root>
<rcpth attribute1="ABC" attribute2 ="DEF" ....>
<rcptd attribute1="012" attribute2="345" ..../>
</rcpth>
</Root>
what I need to do is something like:
select Nodetype = (here is the part that I need help),
Attribute1 = nodes.c.value('#Attribute1','varchar(40)'),
Attribute2 = nodes.c.value('#Attribute2','varchar(40)')
from #Xml.nodes('/Root//*') as node(c)
This query should return as result this:
NodeType Attribute1 Attribute2
pickh 897 GGG
pickd 123 678
rcpth ABC DEF
rpctd 012 345
Is there a way to do this?
declare #Xml xml
set #Xml = '<Root>
<pickh attribute1="897" attribute2="GGG" >
<pickd attribute1="123" attribute2="678" />
</pickh>
</Root>'
select NodeType = node.c.value('local-name(.)', 'varchar(15)'),
Attribute1 = node.c.value('#attribute1','varchar(40)'),
Attribute2 = node.c.value('#attribute2','varchar(40)')
from #Xml.nodes('/Root//*') as node(c)
Yep, and it's incredibly easy:
select Nodetype = node.c.value('local-name(.)','varchar(40)'),
Attribute1 = node.c.value('#attribute1','varchar(40)'),
Attribute2 = node.c.value('#attribute2','varchar(40)')
from #Xml.nodes('/Root//*') as node(c)

Resources