Loop and modify xml nodes SQL Server - sql

I want to loop through xml nodes in SQL Server, and create a copy of each node containing ','.
For example, for the following xml :
declare #answerXML xml = '<answers><answer part="1">answer,test0</answer><answer part="1">answer,test1</answer></answers>'
I want to be modified to become the following :
declare #answerXML xml = '<answers><answer part="1">answer,test0</answer><answer part="1">answer, test0</answer><answer part="1">answer,test1</answer><answer part="1">answer, test1</answer></answers>'
(Each node is duplicated, and in the added node, a space is added after the comma).
I was planing in using something like this :
SELECT
T.ref.value('.', 'varchar(256)') AS Answer
FROM
(SELECT
[Xml] = #answerXML.query('for $i in data(/answers/answer)
return element temp { $i }')
) A
CROSS APPLY
A.Xml.nodes('/temp') T(ref)
But no use, it seems it's too complicated.
Can anyone help in how to loop and update XML in T-SQL?
Thank you in advance,

You can try to shred the XML and rebuild it from scratch:
declare #answerXML xml=
'<answers>
<answer part="1">answer,test0</answer>
<answer part="1">answer,test1</answer>
</answers>';
SELECT a.value(N'#part',N'int') AS [answer/#part]
,a.value(N'text()[1]',N'nvarchar(max)') AS [answer]
,''
,a.value(N'#part',N'int') AS [answer/#part]
,REPLACE(a.value(N'text()[1]',N'nvarchar(max)'),',',', ') AS [answer]
FROM #answerXML.nodes(N'/answers/answer') AS A(a)
FOR XML PATH(''),ROOT('answers')
The result
<answers>
<answer part="1">answer,test0</answer>
<answer part="1">answer, test0</answer>
<answer part="1">answer,test1</answer>
<answer part="1">answer, test1</answer>
</answers>

Related

SQL query for XML data

I have a SQL Server database table with a column called XML that contains XML data which is structured like this:
<Item xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://test/data">
<Roots>
<Root>
<Name>Field Name</Name>
<Value>Field Value</Value>
</Root>
<Root>
<Name>Field Name</Name>
<Value>Field Value</Value>
</Root>
</Roots>
I want to use T-SQL to get the Value where Name = Total. I have tried the following but it isn't returning any data:
SELECT [XML]
FROM [BusinessAccount]
WHERE [XML].value('(/Root/Name)[13]', 'VARCHAR(MAX)') LIKE '%Total%'
If anyone could tell me where I've gone wrong?
You are missing the required WITH XMLNAMESPACES for your XML and the path is incorrect.
If you want to bring back rows where the 13th element consists of the text Total you can use the below.
WITH XMLNAMESPACES (DEFAULT 'http://test/data')
SELECT [XML]
FROM [BusinessAccount]
WHERE 1 = [XML].exist('(/Item/Roots/Root/Name)[13][text() = "Total"]')
Otherwise you can add the WITH XMLNAMESPACES to your original query and fix the path there too.
You need to specify namespaces. You can then match <Name> and <Value> pairs and extract the contents of <Value> like so:
SELECT NameNode.value('declare namespace x="http://test/data"; (../x:Value)[1]', 'varchar(100)')
FROM [BusinessAccount]
CROSS APPLY [XML].nodes('declare namespace x="http://test/data"; //x:Root/x:Name') AS n(NameNode)
WHERE NameNode.value('.', 'varchar(100)') = 'Total'
Demo on db<>fiddle

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.

Remove Varying levels of empty XML tags in SQL Server

I am using FOR XML EXPLICIT to union to convert some SQL table info into an XML file. It's all working, but I have loads of empty tags in the results, at all sorts of different levels. I would like to remove all the empty tags, but keep the top level for each group. Here's an example of the sort of thing I mean:
Using this example bit of nonsense XML, I can remove the bottom level empty nodes with a .modify xquery:
DECLARE #XML XML =
'<Whatever>
<GlassesTypes>
<GlassesType />
</GlassesTypes>
<ExpressionOfJoy>
<FellOver>Y</FellOver>
</ExpressionOfJoy>
<Flights>
<Flight>
<Bookings>
<Booking>
<Segments>
<Segment />
</Segments>
</Booking>
</Bookings>
</Flight>
</Flights>
</Whatever>'
SELECT #XML as Before
SET #Xml.modify('delete //*[not(node())]');
SELECT #XML AS After
This has done exactly what I want with 'GlassesTypes', but there are still levels of empty nodes in 'Flights'. I could repeat the same modify command over and over for each level to get up to the point where only 'Flights' is displayed, but doing so will delete the 'GlassesTypes' empty placeholder:
DECLARE #XML XML =
'<Whatever>
<GlassesTypes>
<GlassesType />
</GlassesTypes>
<ExpressionOfJoy>
<FellOver>Y</FellOver>
</ExpressionOfJoy>
<Flights>
<Flight>
<Bookings>
<Booking>
<Segments>
<Segment />
</Segments>
</Booking>
</Bookings>
</Flight>
</Flights>
</Whatever>'
SELECT #XML as Before
SET #Xml.modify('delete //*[not(node())]');
SET #Xml.modify('delete //*[not(node())]');
SET #Xml.modify('delete //*[not(node())]');
SET #Xml.modify('delete //*[not(node())]');
SET #Xml.modify('delete //*[not(node())]');
SELECT #XML AS After
Is there any way in which I can delete all empty nodes, until the penultimate empty node, regardless of how many levels a group contains? The ideal result would be this:
<Whatever>
<GlassesTypes />
<ExpressionOfJoy>
<FellOver>Y</FellOver>
</ExpressionOfJoy>
<Flights />
</Whatever>
In this example, there is only 'Whatever' tag, but in the real data, there might be several repeated, all with different levels of information, encompassed by a root tag or equivalent.
Any help much appreciated!
Thanks,
Mark
You can define your version of "empty" as not containing any descendant attribute or text nodes, instead of as not containing any descendant nodes. Then retain children of <Whatever> by only selecting descendants of its children:
/Whatever/*//*[empty(.//text() | .//attribute())]

Exporting a xml document from sql 2008

All
I have very little experience of xml, so please be gentle.
The situation is we use an application called dataswitch to take xml and process it into our ERP system via business objects.
The output I am trying to recreate is as follows from a sample xml document.
**<?xml version="1.0" encoding="Windows-1252" ?>
- <IssueDetails Language="05" CssStyle="" DecFormat="1" DateFormat="01" Role="01" Version="6.0.000">**
- <Item>
<Job>00000121</Job>
<NonStocked>N</NonStocked>
<Warehouse>RM</Warehouse>
<StockCode>BBMB100</StockCode>
<Line>00</Line>
<QuantityToIssue>1.000</QuantityToIssue>
</Item>
</IssueDetails>
So far I have worked on the For xml Path method to build my query and used the sp from Phil Factor to write the output
So far I have been successful creating an xml document which opens in the browser (many tries required!)
This is the output I have managed to create, please note the top lines are different
**<IssueDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">**
- <Item>
<Job>00044816</Job>
<NonStocked>N</NonStocked>
<Warehouse>C2</Warehouse>
<StockCode>FG00707</StockCode>
<Line>00</Line>
<QuantityToIssue>400.000000000</QuantityToIssue>
</Item>
- <Item>
<Job>00044816</Job>
<NonStocked>N</NonStocked>
<Warehouse>C2</Warehouse>
<StockCode>FG00707</StockCode>
<Line>00</Line>
<QuantityToIssue>10.000000000</QuantityToIssue>
</Item>
</IssueDetails>
So my question is how do I get the top line in my output to be the same as the sample output?
My procedure is listed below
Declare
#XML_1 varchar (max),
#myXML varchar(max),
#myFile varchar (30),
#Path varchar (100)
set #XML_1=''
set #myFile ='MaterialIssuesTest.xml'
set #Path='D:\Temp\'
--Query database for materials
Set #XML_1 =#XML_1 + ( SELECT WJM.[Job]
,Case when WJM.[Warehouse]='**' then 'Y' else 'N' end as NonStocked
,WJM.[Warehouse]
,rtrim(LP.StockCode) as StockCode
,WJM.[Line]
,[UnitQtyReqd]*LP.LQtyComplete as QuantityToIssue
FROM [SysproCompanyR].[dbo].[WipJobAllMat] WJM
inner join CHC_Production.LabPostsAtMileOps LP On WJM.Job=LP.Job
Where WJM.Job in ('00044816')and (WJM.OperationOffset > LP.PrevOp
and WJM.OperationOffset<LP.LOperation)
FOR XML Path ('Item'),ROOT ('IssueDetails'), ELEMENTS XSINIL
)
set #myXML = LTRIM(rtrim(#XML_1))
--Debug print xml
Print #myXML
--Output xml to document
execute dbo.spWriteStringToFile #myXML, #Path, #myFile
GO
To get you first line, which is a child element of the main body you need to do a sub query like what I have done below. To get the attributes you need to name them with the # symbol, again in my example (formatting changes to aid me setting it out!):
SET #XML_1 =#XML_1 +
(
SELECT WJM.[Job],
(
SELECT '' AS '#Language',
'' AS '#CssStyle',
'' AS '#DecFormat',
'' AS '#DateFormat',
'' AS '#Role',
'' AS '#Version'
FROM YOURTABLENAME YTN
WHERE YTN.[SOMEFIELD] = WJM.[SOMEFIELD]
FOR XML PATH(''), TYPE
) AS 'IssueDetails'
,Case when WJM.[Warehouse]='**' then 'Y' else 'N' end as NonStocked
,WJM.[Warehouse]
,rtrim(LP.StockCode) as StockCode
,WJM.[Line]
,[UnitQtyReqd]*LP.LQtyComplete as QuantityToIssue
FROM [SysproCompanyR].[dbo].[WipJobAllMat] WJM
INNER JOIN CHC_Production.LabPostsAtMileOps LP ON WJM.Job = LP.Job
WHERE WJM.Job IN ('00044816') AND (WJM.OperationOffset > LP.PrevOp
AND WJM.OperationOffset<LP.LOperation)
FOR XML Path ('Item'), ROOT ('IssueDetails'), ELEMENTS XSINIL
)
I don't know where the data is coming from, so you will have to fill in the table name and the WHERE clause to join it to the data that you already have. That is assuming the data is a child of the data that you have already provided.
Bare in mind the syntax might not be quite correct as I am doing this from memory and there are several different variations to get this result.

How to get value from XML attribute using Sql:Variable in xquery

I want to get attribute value from XML using Xquery.
MY XML is
<Answers>
<AnswerSet>
<Answer questionId="NodeID">155</Answer>
<Answer questionId="ParentNode" selectedValue="12">Product</Answer>
</AnswerSet>
</Answers>
Below is my query.
DECLARE #Field Varchar(100)
DECLARE #Attribute VARCHAR(100)
SET #Field='ParentNode'
SET #Attribute = 'selectedValue'
SELECT ISNULL(PropertyXML.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")])[1]','varchar(max)'),'') ,
ISNULL(PropertyXML.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")]/sql:variable(#Attribute) )[1]','varchar(max)'),'')
FROM node
WHERE id=155
below line is working fine with sql:variable
ISNULL(PropertyXML.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")])[1]','varchar(max)'),'')
but I am getting error in below line..
ISNULL(PropertyXML.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")]/sql:variable(#Attribute) )[1]','varchar(max)'),'')
Any ideas on how to get provided attribute(#Attribute) value in result?
Try something like
ISNULL(#Xml.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")]/#*[local-name() = sql:variable("#Attribute")])[1]','varchar(max)'),'')