Read XML child node attributes using SQL query - sql

I have one XML column (Criteria) in table (Qualifications) which contains different XML:
<training ID="173"><badge ID="10027" /><badge ID="10028" /></training>
<book Category="Hobbies And Interests" PropertyName="C#" CategoryID="44" />
<sport Category="Hobbies And Interests" PropertyName="Cricket" CategoryID="46" />
<education ID="450" School="Jai ambe vidyalaya"></education>
I want to read the "badge" node "ID" attributes for all nodes under the "training" node.
Can anyone help?

IDs of badge elements inside training only
select t.c.value('.', 'int') ID
from Qualifications q
cross apply q.Criteria.nodes('//training[badge]/badge[#ID]/#ID') t(c)
IDs of badge elements anywhere (not only inside training)
select t.c.value('.', 'int') ID
from Qualifications q
cross apply q.Criteria.nodes('//badge[#ID]/#ID') t(c)
If Criteria column is nvarchar type, you can cast to xml as:
select t.c.value('.', 'int') ID
from Qualifications q
cross apply (select convert(xml, q.Criteria) xmlCriteria) a
cross apply a.xmlCriteria.nodes('//training[badge]/badge[#ID]/#ID') t(c)

Try this sample, it should help (just replace #xml with your table/column name)
DECLARE #xml XML
SET #xml ='
<training ID="173">
<badge ID="10027" />
<badge ID="10028" />
</training>
<book Category="Hobbies And Interests" PropertyName="C#" CategoryID="44" />
<sport Category="Hobbies And Interests" PropertyName="Cricket" CategoryID="46" />
<education ID="450" School="Jai ambe vidyalaya"></education>'
SELECT data.col.value('(#ID)[1]', 'int')
FROM #xml.nodes('(/training/badge)') AS data(col)
Output:
10027
10028

Related

How to get value from a node in XML via SQL Server

I've found several pieces of information online about this but I can't get it working for the life of me.
This is the XML I have:
I need to extract the ID & Name value for each node. There are a lot.
I tried to do this but it returns NULL:
select [xml].value('(/Alter/Object/ObjectDefinition/MeasureGroup/Partitions/Partition/ID)[1]', 'varchar(max)')
from test_xml
I understand the above would return only 1 record. My question is, how do I return all records?
Here's the XML text (stripped down version):
<Alter xmlns="http://schemas.microsoft.com/analysisservices/2003/engine" AllowCreate="true" ObjectExpansion="ExpandFull">
<ObjectDefinition>
<MeasureGroup xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ID>ts_homevideo_sum_20140430_76091ba1-3a51-45bf-a767-f9f3de7eeabe</ID>
<Name>table_1</Name>
<StorageMode valuens="ddl200_200">InMemory</StorageMode>
<ProcessingMode>Regular</ProcessingMode>
<Partitions>
<Partition>
<ID>123</ID>
<Name>2012</Name>
</Partition>
<Partition>
<ID>456</ID>
<Name>2013</Name>
</Partition>
</Partitions>
</MeasureGroup>
</ObjectDefinition>
</Alter>
You need something like this:
DECLARE #MyTable TABLE (ID INT NOT NULL, XmlData XML)
INSERT INTO #MyTable (ID, XmlData)
VALUES (1, '<Alter xmlns="http://schemas.microsoft.com/analysisservices/2003/engine" AllowCreate="true" ObjectExpansion="ExpandFull">
<ObjectDefinition>
<MeasureGroup xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ID>ts_homevideo_sum_20140430_76091ba1-3a51-45bf-a767-f9f3de7eeabe</ID>
<Name>table_1</Name>
<StorageMode valuens="ddl200_200">InMemory</StorageMode>
<ProcessingMode>Regular</ProcessingMode>
<Partitions>
<Partition>
<ID>123</ID>
<Name>2012</Name>
</Partition>
<Partition>
<ID>456</ID>
<Name>2013</Name>
</Partition>
</Partitions>
</MeasureGroup>
</ObjectDefinition>
</Alter>')
;WITH XMLNAMESPACES(DEFAULT 'http://schemas.microsoft.com/analysisservices/2003/engine')
SELECT
tbl.ID,
MeasureGroupID = xc.value('(ID)[1]', 'varchar(200)'),
MeasureGroupName = xc.value('(Name)[1]', 'varchar(200)'),
PartitionID = xp.value('(ID)[1]', 'varchar(200)'),
PartitionName = xp.value('(Name)[1]', 'varchar(200)')
FROM
#MyTable tbl
CROSS APPLY
tbl.XmlData.nodes('/Alter/ObjectDefinition/MeasureGroup') AS XT(XC)
CROSS APPLY
XC.nodes('Partitions/Partition') AS XT2(XP)
WHERE
ID = 1
First of all, you must respect and include the default XML namespace defined in the root of your XML document.
Next, you need to do a nested call to .nodes() to get all <MeasureGroup> and all contained <Partition> nodes, so that you can reach into those XML fragments and extract the ID and Name from them.
This should then result in something like this as output:

How to insert a XML file into a SQL table

How do I insert this into a SQL table?
<ITEM id="1"
name="Swimmer Head"
mesh_name="eq_head_swim"
totalpoint="0"
type="equip"
res_sex="m"
res_level="0"
slot="head"
weight="2"
bt_price="0"
hp="4"
ap="8"
maxwt="0"
sf="0"
fr="0"
cr="0"
pr="0"
lr="0"
color="#FFFFFFFF"
desc="Part of an everyday swimming outfit" />
Also, theres a lot of more lines in this XML file, so how can I do this with 1 .sql file?
Here is one method which will give you an EAV structure (Entity Attribute Value).
You may notice I only have to identify ONE key element ... id
I truncated a few elements and added a second item for demonstrative purposes only
Declare #XML xml = '
<ITEM id="1" name="Swimmer Head" mesh_name="eq_head_swim" totalpoint="0" type="equip" res_sex="m" res_level="0" slot="head" weight="2" bt_price="0" color="#FFFFFFFF" desc="Part of an everyday swimming outfit" />
<ITEM id="2" name="Boxer Feet" mesh_name="eq_feet_boxer" totalpoint="0" type="equip" res_sex="m" res_level="0" slot="head" weight="2" bt_price="25.00" color="#FFFFFFFF" desc="Somthing for the boxer" />
'
Select ID = r.value('#id','int')
,Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From #XML.nodes('/ITEM') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('id')
Returns (which can easily be Pivoted if necessary)
EDIT - To load XML from a FILE
Declare #XML xml
Select #XML = BulkColumn FROM OPENROWSET(BULK 'C:\Working\SomeXMLFile.xml', SINGLE_BLOB) x;
Select ID = r.value('#id','int')
,Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From #XML.nodes('/ITEM') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('id')
Re-asking Siyual's question, but more specific:
Is this one line of many that should go into a table?
And is it not nested?
In other words, would it continue with repetitions of <ITEM id= [...] desc="something" /> ? If the answer is yes, consider a perl script that picks everything after an equal sign and between double quotes and concatenates the obtained bits, separating them by, say, comma, creating one line per <ITEM [...] /> .
This way, you'd get a CSV file to load. Of course, you'd have to create the target table first.

How to apply .nodes() to a column's cell instead of XML variable?

Is there a way to apply a .node() method to a specific column instead of a XML variable? I'm working on transferring XML files into a SQL Table. Some of my XML files have child-nodes. So I'm using the Cross Apply function to connect the children with the parent.
For example, one of my columns in my table stores a child xml file.
One cell contains the following XML: (Note I have pulled out the xml and already stored it in the #input variable).
DECLARE #input XML
SET #INPUT = N'
<ParentElement ID="1">
<Title>parent1</Title>
<Description />
<ChildElement ID="6">
<Title>Child 4</Title>
<Description />
<StartDate>2010-01-25T00:00:00</StartDate>
<EndDate>2010-01-25T00:00:00</EndDate>
</ChildElement>
<ChildElement ID="0">
<Title>Child1</Title>
<Description />
<StartDate>2010-01-25T00:00:00</StartDate>
<EndDate>2010-01-25T00:00:00</EndDate>
</ChildElement>
<ChildElement ID="8">
<Title>Child6</Title>
<Description />
<StartDate>2010-01-25T00:00:00</StartDate>
<EndDate>2010-01-25T00:00:00</EndDate>
</ChildElement>
</ParentElement>'
This is what my Cross Apply looks like:
SELECT
Parent.Elm.value('(#ID)[1]', 'int') AS 'ID',
Parent.Elm.value('(Title)[1]', 'varchar(100)') AS 'Title',
Parent.Elm.value('(Description)[1]', 'varchar(100)') AS 'Description',
Child.Elm.value('(#ID)[1]', 'int') AS 'ChildID',
Child.Elm.value('(Title)[1]', 'varchar(100)') AS 'ChildTitle',
Child.Elm.value('(StartDate)[1]', 'DATETIME') AS 'StartDate',
Child.Elm.value('(EndDate)[1]', 'DATETIME') AS 'EndDate'
FROM
#input.nodes('/ParentElement') AS Parent(Elm)
CROSS APPLY
Parent.Elm.nodes('ChildElement') AS Child(Elm)
Currently, I'm having to pull out that child-xml-node data from the XML cell and store it in a variable (#input) so that I can apply the .nodes() method to it. I would like to be able to simply call the nodes method on each particular cell in that column. Is there a way to do that?
Thanks for your help!
Credit to Beta033 and marc_s from this a link post about how to get the cross apply to work!
select
parent.value('(#ID)[1]', 'int') AS 'ID',
parent.value('(Title)[1]', 'varchar(100)') AS 'Title',
parent.value('(Description)[1]', 'varchar(100)') AS 'Description',
child.value('(#ID)[1]', 'int') AS 'ChildID',
child.value('(Title)[1]', 'varchar(100)') AS 'ChildTitle',
child.value('(StartDate)[1]', 'DATETIME') AS 'StartDate',
child.value('(EndDate)[1]', 'DATETIME') AS 'EndDate'
from
yourtable
cross apply
xmldata.nodes('/ParentElement') xp(parent)
cross apply
xp.parent.nodes('ChildElement') xc(child)
where xmldata is the field that contains the xml data in yourtable

Safely retrieve values from element that can contain different values

We have some xml elements in a database that [for older data] can sometimes contain guids and sometimes contain integers.
is there a nice way of pulling out all the integrs only?
This will fail if the value element contains a guid!
select
ra.*,
t.c.value('.', 'int') as organisationId
from
Audit.EmployeeAudit ra
cross apply ra.EmployeeXml.nodes('//*:employee/*:property[*:name="ORG"]/*:value') t(c)
Sample Xml
<employee>
<property>
<name>ORG</name>
<value>39</value> <!-- Sometimes this will be a guid -->
<description>Leeds</description>
</property>
</employee>
You could add a predicate to only match entries of less than or equal to 10 characters.
;with EmployeeAudit as
(
SELECT CAST('<employee><property>
<name>ORG</name>
<value>39</value> <!-- Sometimes this will be a guid -->
<description>Leeds</description>
</property></employee>
' AS XML) AS EmployeeXml
UNION ALL
SELECT CAST('<employee><property>
<name>ORG</name>
<value>2FD29F11-59FC-47FD-BC30-DD330A53284E</value>
<description>Leeds</description>
</property></employee>
' AS XML)
)
select
ra.*,
t.c.value('.', 'int') as organisationId
from
EmployeeAudit ra
cross apply
ra.EmployeeXml.nodes
('//*:employee/*:property[*:name="ORG"]/*:value[string-length() <= 10]') t(c)
Or actually this might be a bit more robust
('//*:employee/*:property[*:name="ORG"]/*:value[ceiling(.) = .]') t(c)

SQL XML parsing different nodes

i would like to ask you for help with parsing XML in SQL, where my XML looks like this, where Load is parrent which can be repeated X-times. I need Column SerNr and for each row need to bound Order name
where final table will looks like this
Example of table:
<ImageHistory>
<Load targets="2" totalTime="417">
<Orders>
<Order name="20548976"/>
</Orders>
<Data>
<Disk SerNr="XXXXXX" Size_mb="228936" LoadSuccessfull="true" />
<Disk SerNr="ZZZZZ" Size_mb="228936" LoadSuccessfull="true" />
</Data>
</Load>
</ImageHistory>
sql is
with data as (SELECT CAST(MY_XML AS xml) as MY_XML FROM OPENROWSET(BULK 'addres to xml', SINGLE_BLOB) AS T(MY_XML)),
datainfo as (
SELECT
MY_XML.Blasting.value(' #name', 'BIGINT') as Size_mb,
MY_XML.Blasting.value('#SerNr', 'varchar(32)') as SerNr
FROM data CROSS APPLY MY_XML.nodes('ImageHistory/Load/Data/Disk') AS MY_XML (Blasting))
select * from datainfo
thank you for help
I saved the XML as a file on the file system: e:\Temp\DiskSerialNumbers.xml
The rest is below.
SQL
;WITH XmlFile (Contents) AS
(
SELECT CAST(BulkColumn AS XML)
FROM OPENROWSET(BULK 'e:\Temp\DiskSerialNumbers.xml', SINGLE_BLOB) AS XmlData
)
SELECT c.value('(../../Orders/Order/#name)[1]', 'INT') AS [Name]
, c.value('#SerNr', 'VARCHAR(20)') AS [SerNr]
FROM XmlFile CROSS APPLY Contents.nodes('/ImageHistory/Load/Data/Disk') AS t(c);
Output
+----------+--------+
| Name | SerNr |
+----------+--------+
| 20548976 | XXXXXX |
| 20548976 | ZZZZZ |
+----------+--------+
You've got your answer already, but I want to point to the fact, that backward navigation (using ../../) is horribly slow with bigger structures.
I'd suggest this:
A mockup to simulate your issue:
DECLARE #yourTable TABLE(ID INT IDENTITY,YourXml XML);
INSERT INTO #yourTable VALUES
(N'<ImageHistory>
<Load targets="2" totalTime="417">
<Orders>
<Order name="20548976" />
</Orders>
<Data>
<Disk SerNr="XXXXXX" Size_mb="228936" LoadSuccessfull="true" />
<Disk SerNr="ZZZZZ" Size_mb="228936" LoadSuccessfull="true" />
</Data>
</Load>
</ImageHistory>');
--The query
SELECT t.ID
,ld.value('#targets','int') AS Load_Targets
,ld.value('#totalTime','int') AS Load_TotalTime
,ld.value('(Orders/Order/#name)[1]','int') AS Order_Name
,dsk.value('#SerNr','nvarchar(100)') AS Disk_SerNr
,dsk.value('#Size_mb','nvarchar(100)') AS Disk_Size_mb
,dsk.value('#LoadSuccessfull','bit') AS Disk_LoadSuccessfull
FROM #yourTable AS t
CROSS APPLY t.YourXml.nodes('/ImageHistory/Load') A(ld)
CROSS APPLY A.ld.nodes('Data/Disk') B(dsk);
The idea in short:
The first APPLY will dive down to <Load> and will return all <Load> elements (if there are more...
The second APPLY will use the fragment returned by the first APPLY and dive deeper down to <Disk>.
We fetch the order's name (and other values) calling .value() against the first fragment (which is the <Load> element) and we fetch the values of <Disk> calling .value() against the fragment of the second APPLY.