Safely retrieve values from element that can contain different values - sql

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)

Related

Importing XML to SQL Server

I am wondering how I can insert an XML file into a SQL Server DB. Below is the XML I have but I am unsure how to do this in a way that will scale. My thought is a Insert Into Select statement but I do not know if that is going to work as the data increases. Thank you in advance!
<Records>
<Record>
<ID SpecNum="5069580" IssueNum="001" SpecStatus="Pre-Approved">
<NutritionDetails>
<NutrientFacts>
<NutrientNameId>ENERC_KCAL</NutrientNameId>
<NutrientName>ENERC_KCAL</NutrientName>
<NutrientPer100gUnrounded>1.91</NutrientPer100gUnrounded>
<NutrientPer100gRounded>191</NutrientPer100gRounded>
</NutrientFacts>
</NutritionDetails>
</ID>
</Record>
</Records>
Once you've successfully created a proper, valid XML - you should be able to use this T-SQL code to grab the details:
SELECT
-- get the attributes from the <ID> node
IDSpecNum = XC.value('(ID/#SpecNum)[1]', 'int'),
IDIsseNum = XC.value('(ID/#IssueNum)[1]', 'int'),
IDSpecStatus = XC.value('(ID/#SpecStatus)[1]', 'varchar(100)'),
-- get the element values from the children of the <NutrientFacts> node
NutrientNameId = NUT.value('(NutrientNameId)[1]', 'varchar(100)'),
NutrientName = NUT.value('(NutrientName)[1]', 'varchar(100)'),
NutrientPer100gUnrounded = NUT.value('(NutrientPer100gUnrounded)[1]', 'decimal(20,4)'),
NutrientPer100gRounded = NUT.value('(NutrientPer100gRounded)[1]', 'decimal(20,4)')
FROM
dbo.YourTable
CROSS APPLY
-- get one XML fragment per <Record>
XmlData.nodes('/Records/Record') AS XT(XC)
CROSS APPLY
-- get one XML fragment per <NutrientFacts> inside
XC.nodes('ID/NutritionDetails/NutrientFacts') AS XT2(NUT)
The first CROSS APPLY basically get an "inline pseudo table" with one XML fragment for each <Record> node in your XML in the XmlData column of your table (this is just an assumption on my part - adapt to your reality!). These XML fragments are referenced as "pseudo-table" XT with a single column XC.
With that XC column's XML fragment, you can "reach in" and grab the attribute values from the <ID> node in the <Record> - that's the first three values.
Then, based on the XT pseudo table, I apply another CROSS APPLY to get all the <NutrientFacts> nodes inside <ID> / <NutritionDetails> - those are referenced as pseudo-table XT2 with column NUT, which again holds an XML fragment for each <NutrientFacts> node; I reach into that XML node and extract the values from the sub-elements of that node - those are the four additional values that are shown in the select.
Now that you have a SELECT that returns all the values - you can easily get those bits you need and use them in a INSERT INTO dbo.MyTable(list-of-columns) SELECT list-of-columns :...... scenario. Enjoy!
UPDATE: to import an XML file from disk (local disk on your SQL Server machine's file system) into your table - use something like this:
INSERT INTO dbo.YourTable(XmlData)
SELECT
CONVERT(XML, BulkColumn) AS BulkColumn
FROM
OPENROWSET(BULK 'C:\temp\records.xml', SINGLE_BLOB) AS x;
Again: adapt to your needs - I don't know if you want to insert additional information into dbo.YourTable - and I don't even know your table name; you can load one XML at a time from disk

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.

Parsing Dynamic XML to SQL Server tables with Parent and child relation

I have a XML in Source Table. I need to parse this XML to 3 different tables which has Parent Child relationship. I can do this in C# but currently for this i need to implement it at SQL server side.
The sample xml looks like:
<ROWSET>
<ROW>
<HEADER_ID>5001507</HEADER_ID>
<ORDER_NUMBER>42678548</ORDER_NUMBER>
<CUST_PO_NUMBER>LSWQWE1</CUST_PO_NUMBER>
<CUSTOMER_NUMBER>38087</CUSTOMER_NUMBER>
<CUSTOMER_NAME>UNIVERSE SELLER</CUSTOMER_NAME>
<LINE>
<LINE_ROW>
<HEADER_ID>5001507</HEADER_ID>
<LINE_ID>12532839</LINE_ID>
<LINE_NUMBER>1</LINE_NUMBER>
<ITEM_NUMBER>STAGEPAS 600I-CA</ITEM_NUMBER>
<ORDER_QUANTITY>5</ORDER_QUANTITY>
</LINE_ROW>
<LINE_ROW>
<HEADER_ID>5001507</HEADER_ID>
<LINE_ID>12532901</LINE_ID>
<LINE_NUMBER>3</LINE_NUMBER>
<ITEM_NUMBER>CD-C600 RK</ITEM_NUMBER>
<ORDER_QUANTITY>6</ORDER_QUANTITY>
</LINE_ROW>
<LINE_ROW>
<HEADER_ID>5001507</HEADER_ID>
<LINE_ID>12532902</LINE_ID>
<LINE_NUMBER>4</LINE_NUMBER>
<ITEM_NUMBER>CD-S300 RK</ITEM_NUMBER>
<ORDER_QUANTITY>8</ORDER_QUANTITY>
</LINE_ROW>
</LINE>
<PRCADJ>
<PRCADJ_ROW>
<PRICE_ADJUSTMENT_ID>43095064</PRICE_ADJUSTMENT_ID>
<HEADER_ID>5001507</HEADER_ID>
<LINE_ID>12532839</LINE_ID>
<ADJUSTED_AMOUNT>-126</ADJUSTED_AMOUNT>
</PRCADJ_ROW>
<PRCADJ_ROW>
<PRICE_ADJUSTMENT_ID>43095068</PRICE_ADJUSTMENT_ID>
<HEADER_ID>5001507</HEADER_ID>
<LINE_ID>12532840</LINE_ID>
<ADJUSTED_AMOUNT>-96.6</ADJUSTED_AMOUNT>
</PRCADJ_ROW>
</PRCADJ>
</ROW>
</ROWSET>
The issue is the Parent can have multiple child and each child can multiple sub child. How can i write query to transfer this into Sql Server 2005
You need to use three CROSS APPLY operators to break up the "list of XML elements" into separate pseudo tables of XML rows, so you can access their properties - something like this:
SELECT
HeaderID = XCRow.value('(HEADER_ID)[1]', 'int'),
OrderNumber = XCRow.value('(ORDER_NUMBER)[1]', 'int'),
LineHeaderID = XCLine.value('(HEADER_ID)[1]', 'int'),
LineID = XCLine.value('(LINE_ID)[1]', 'int'),
LineNumber = XCLine.value('(LINE_NUMBER)[1]', 'int'),
PriceAdjustmentID = XCPrc.value('(PRICE_ADJUSTMENT_ID)[1]', 'int'),
AdjustedAmount = XCPrc.value('(ADJUSTED_AMOUNT)[1]', 'decimal(20,4)')
FROM
dbo.YourTableNameHere
CROSS APPLY
Data.nodes('/ROWSET/ROW') AS XTRow(XCRow)
CROSS APPLY
XCRow.nodes('LINE/LINE_ROW') AS XTLine(XCLine)
CROSS APPLY
XCRow.nodes('PRCADJ/PRCADJ_ROW') AS XTPrc(XCPrc)
With this, the first CROSS APPLY will handle all the elements that are found directly under <ROWSET> / <ROW> (the header information), the second one will enumerate all instances of <LINE> / <LINE_ROW> below that header element, and the third CROSS APPLY handles the <PRCADJ> / <PRCADJ_ROW> elements, also below the header.
You might need to tweak the outputs a bit - and I only picked two or three of the possible values - extend and adapt to your own needs! But this should show you the basic mechanism - the .nodes() method returns a "pseudo table" of XML fragments, one for each match of the XPath expression you define.
you can do some thing like this. using cross apply you will get node elements and then extract the value using value clause. you need to specify the column type i.e int or varchar etc.
The result can then be inserted using insert into select query.
insert into Table1 values ( header_id, order_number, cust_po_number)
select R.value('(HEADER_ID)[1]', 'int') As header_id,
R.value('(ORDER_NUMBER)[1]', 'int') as order_number,
R.value('(CUST_PO_NUMBER)[1]', 'varchar(256)') as cust_po_number
from table
cross apply XMLdata.nodes('/ROWSET/ROW') AS P(R)
insert into Table2 values ( header_id, line_id, line_number)
select R.value('(HEADER_ID)[1]', 'int') As header_id,
R.value('(LINE_ID)[1]', 'int') as line_id,
R.value('(LINE_NUMBER)[1]', 'int') as line_number
from table
cross apply XMLdata.nodes('/ROWSET/ROW/LINE/LINE_ROW') AS P(R)

Read XML child node attributes using SQL query

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

Does nodes() or openxml returns rows in same order as it finds in xml?

I have an xml which i need to parse using openxml or nodes(). The xml contains few child tags that repeat with different values, as below.
<root>
<value>10</value>
<value>12</value>
<value>11</value>
<value>1</value>
<value>15</value>
<root>
For my code it is very important that i get all these rows returned in same order as in xml. I googled and gogled but nothing tells me if the #mp:id is always returned in same order as in xml. Or if nodes() return values in same order as it encounters them.
All I want to know if I can trust any of those two methods and be happy with proper order of rows.
P.S. excuse any errors or mistakes in above text, I dont enjoy typing codes in an android window either.
You can use row_number on the shredded XML like this.
declare #XML xml=
'<root>
<value>10</value>
<value>12</value>
<value>11</value>
<value>1</value>
<value>15</value>
</root>'
select value
from
(
select T.N.value('.', 'int') as value,
row_number() over(order by T.N) as rn
from #xml.nodes('/root/value') as T(N)
) as T
order by T.rn
Uniquely Identifying XML Nodes with DENSE_RANK
Update:
You can also use a numbers table like this;
declare #XML xml=
'<root>
<value>10</value>
<value>12</value>
<value>11</value>
<value>1</value>
<value>15</value>
</root>';
with N(Number) as
(
select Number
from master..spt_values
where type = 'P'
)
select #XML.value('(/root/value[sql:column("N.Number")])[1]', 'int')
from N
where N.Number between 1 and #XML.value('count(/root/value)', 'int')
order by N.Number
XPath allows you to select nodes explicitly by ordinal: '/root[1]/value[1]' is the first element, '/root[1]/value[2]' is the second etc. Also could use '(/root/value)[1]' and '(/root/value[2])'. This way you can select exactly the element you want, and selecting element 1 then element 2 then element 3 etc will give you controlled order. Slow, but controlled.
Updated P.S. Wouldn't this be nice to be true?
declare #x xml = '<root>
<value>10</value>
<value>12</value>
<value>11</value>
<value>1</value>
<value>15</value>
<root>';
select x.value(N'position()', N'int') as position,
x.value(N'.', 'int') as value
from #x.nodes(N'//root/value') t(x)
Unfortunately, is not...
Msg 2371, Level 16, State 1, Line 9
XQuery [value()]: 'position()' can only be used within a predicate or XPath selector
And the existence of this error makes me worry that order may be broken sometimes...