Modify XML values identified through cross apply - sql

I've got a data issue with some values stored in an XML column in a database. I've reproduced the problem as the following example:
Setup Script:
create table XMLTest
(
[XML] xml
)
--A row with two duff entries
insert XMLTest values ('
<root>
<item>
<flag>false</flag>
<frac>0.5</frac>
</item>
<item>
<flag>false</flag>
<frac>0</frac>
</item>
<item>
<flag>false</flag>
<frac>0.5</frac>
</item>
<item>
<flag>true</flag>
<frac>0.5</frac>
</item>
</root>
')
In the XML portion the incorrect entries are those with <flag>false</flag> and <frac>0.5</frac> as the value of flag should be true for non-zero frac values.
The following SQL identifies the XML item nodes that require update:
select
i.query('.')
from
XMLTest
cross apply xml.nodes('root/item[flag="false" and frac > 0]') x(i)
I want to do an update to correct these nodes, but I don't see how to modify the item elements identified by a cross apply. I saw the update as looking something like this:
update t
set
x.i.modify('replace value of (flag/text())[1] with "true"')
from
XMLTest t
cross apply xml.nodes('root/item[flag="false" and frac > 0]') x(i)
However this isn't working: I get the error "Incorrect syntax near 'modify'".
Can this be done through this method?
I know an alternative would be to do a string replace on the xml column, but I don't like that as being a bit unsubtle (and I'm not confident it wouldn't break things in my real-word problem)

It is not possible to update the one XML instance in more than one place at a time so you have to do the updates in a loop until you are done.
From http://msdn.microsoft.com/en-us/library/ms190675.aspx "Expression1: Identifies a node whose value is to be updated. It must identify only a single node."
-- While there are rows that needs to be updated
while exists(select *
from XMLTest
where [XML].exist('root/item[flag="false" and frac > 0]') = 1)
begin
-- Update the first occurence in each XML instance
update XMLTest set
[XML].modify('replace value of (root/item[flag="false" and frac > 0]/flag/text())[1] with "true"')
where xml.exist('root/item[flag="false" and frac > 0]') = 1
end

Related

Fetch a value from a column which contains XML

How to fetch a value from a column which contains XML in SQL Server?
below is my sample XML column value and the id's can be swap anytime(101,100) or (201,100,101,321).
<Questions>
<item id="101">Yes</item>
<item id="100">No</item>
</Questions>
I want to fetch a value based on Id. Like fetching Yes from id=101.
Sample code much appreciated.
I tried with below sample, but Unable to retrieve value "Yes"
select Y.value('#item[1]','varchar[3]') as valT from tbl_storeXML s cross apply s.Questions.nodes('Questions/item') as X(Y) where e.empId=256 and Y.value('#id','int')=101
Please help on this.
Ps. It's not a home work, I am learning handling xml in sql server.
Use of the value is not done correct, you do:
Y.value('#id','int')
This should be: Y.value('(#id)[1]','int')
round braces around #id, see: docs: value() Method
and Y.value('item[1]','varchar[3]').
This should be: Y.value('(#item)[1]','varchar(3)').
The # is removed because item is not an attribute
varchar should have round braces, not square braces.
Your try, after changes will become:
select
Y.value('(item)[1]','varchar(3)') as valT
from tbl_storeXML s
cross apply s.Questions.nodes('Questions/item') as X(Y)
where e.empId=256 and Y.value('(#id)','int')=101
This is not tested, because I do not have those tables. (I do think Y.value('(item)[1]','varchar(3)') might need to be written as Y.value('(.)[1]','varchar(3)') )
But the same approach can be seen in this DBFIDDLE
DECLARE #xml XML = '<Questions>
<item id="101">Yes</item>
<item id="100">No</item>
</Questions>';
select
X.y.value('(#id)[1]','VARCHAR(20)') id,
X.y.value('(.)[1]','VARCHAR(20)') value
from #xml.nodes('Questions/item') as X(y);
output:
id
value
101
Yes
100
No

Return specific nested node within XML from a SQL column

I need to return the value (Node) above the rows where 'Item' appears. This will be the column value for anything that appears as Key and Values. The other values (other nodes, not shown in this example) return back with the correct headings. Returning other nodes not under Item are fine.
I have created a CTE to extract the XML values from a column (CustomFieldXML) against the Client record that I am looking at. The way certain values are nested in the XML, my code works fine. The issue arises where the sub heading appears under 'Item'. How do I return each heading above the <item> node, but incorporate that into my CTE?
;WITH Clients_CTE_CastToXML AS
(
SELECT
C.ID,
C.Name,
C.Environment AS [Environment],
C.[CustomFieldsXml] AS 'x'
FROM Clients AS C WITH(NOLOCK)
WHERE id=5806
),
XML_Data AS
(
SELECT
[Id],
[Name],
x.y.value('local-name(..)', 'VARCHAR(MAX)') 'parentElementName',
x.y.value('local-name(.)', 'VARCHAR(MAX)') AS 'attributeName',
x.y.value('.', 'VARCHAR(MAX)') AS 'attributeValue'
FROM Clients_CTE_CastToXML AS c
CROSS APPLY x.nodes('//*[text()], //#*') AS x(y)
)
SELECT
'' AS [Id],
'' AS 'Source',
'' AS [Name],
D.parentElementName,
D.attributeName,
D.attributeValue
FROM XML_Data AS D
OPTION(RECOMPILE)
Example of what is returned...I need to pull any level above the heading for <item> (so pull out the node that says <DNA> or <RiskLevel>). Not sure what the syntax should be for that node.
<values>
<AuthorisingPartner>C</AuthorisingPartner>
<DNA>
<item>
<Key>No</Key>
<Value>No</Value>
</item>
</DNA>
<RiskLevel>
<item>
<Key>Normal</Key>
<Value>Normal</Value>
</item>
</RiskLevel>
</values>
Image: See the output here

Find if XML contain then return something

I'm trying to find a way where I run a query to check if any xml file contain something, if true, return me a different tag within that xml.
Example:
<shop>
<item>
<Product>shirt</Product>
<color>red</color>
</item>
<item>
<Product>shirt</Product>
<color>yellow</color>
</item>
<item>
<Product>jeans</Product>
<color>blue</color>
</item>
</shop>
assume col name is XML, I can find all cols that has shirts on and I want to get the color of the shirt back
[XML].exist ('/shop/item/Product[contains(., "shirt")]') > 0);
I want to be able to get a table or an array of just the colors of the shirts back. Is that possible?
I'm using SQL 2012
Shred the XML on item elements since there can be more than one color we need to select from each XML :
SELECT P.I.value('color[1]', 'varchar(100)') AS shirt_color
FROM YourTable t
CROSS APPLY t.[XML].nodes('/shop/item[contains(Product[1], "shirt")]') as P(I)
rextester demo
Also notice that, by using CROSS APPLY and the XPath/XQuery, rows that don't have any "shirt" item would be filtered out, so we no longer need exist() in the above.

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.

SQL query to upload XML file into a single SQL table

I have a sample xml file that I need to upload into a SQL table.
Below the xml file is the SQL query to select the XML values to insert into the table
Problem: I am unable to obtain the <Actual>,<ExpectedValue> values as they occur more than once
Would appreciate if someone could kindly help correct/direct me to get the right values
Thanks.
<?xml version="1.0" encoding="utf-8" ?>
<Results>
<General>
<Version>1.01</Version>
<InputFilename>TestFile.xml</InputFilename>
<Filename>xyz.XML</Filename>
</General>
<Tests>
<Test Name="Test 1">
<Number>0</Number>
<MinimumSampleCount>1</MinimumSampleCount>
<ExpectedValues>
<ExpectedValue>8</ExpectedValue>
<ExpectedValue>5</ExpectedValue>
</ExpectedValues>
<Actual>
<seq>1</seq>
<ValueFound>8</ValueFound>
<seqTestResult>Passed</seqTestResult>
</Actual>
<Actual>
<seq>3</seq>
<ValueFound>8</ValueFound>
<seqTestResult>Passed</seqTestResult>
</Actual>
<Result>Last sequence matches test cases.</Result>
<TestResult>Passed</TestResult>
</Test>
<Test Name="Test 3">
<Number>25</Number>
<ExpectedValues><ExpectedValue>3.50</ExpectedValue>
<ExpectedValue>3.56</ExpectedValue>
<ExpectedValue>3.60</ExpectedValue>
</ExpectedValues>
<Result>not applicable</Result>
<TestResult>Skipped</TestResult>
</Test>
<OverallTestResult>Passed</OverallTestResult>
</Tests>
</Results>
SQL Code:
INSERT INTO dbo.Results_XML ([Version],[InputFilename],[Filename],
[OverallTestResult],Name,Number,MinimumSampleCount,ActualNumberOfSamples,ExpectedValue,
ActualSeq, ActualValueFound,ActualseqTestResult,CompareValue,Result,TestResult
SELECT [Version] = x.data.value('Version[1]','numeric(3,2)'),
[InputFilename] = x.data.value('InputFilename[1]','varchar(80)'),
[Filename] = x.data.value('Filename[1]','varchar(80)'),
[OverallTestResult]=T2.N.value('OverallTestResult[1]','varchar(15)'),
[Test Name]= y.data.value('#Name','varchar(255)')
,[Number]= y.data.value('Number[1]','int'),
[MinimumSampleCount]=y.data.value('Number[1]','int'),
[ActualNumberOfSamples]=y.data.value('ActualNumberOfSamples[1]','int')
,[ExpectedValue]=z.data.value('ExpectedValue[1]','varchar(255)')
,[ActualSeq]=v.data.value('Seq[1]','int'),
,ActualValueFound=v.data.value('ValueFound[1]','int'),
,ActualseqTestResult=v.data.value('seqTestResult[1]','varchar(255)'),
,[CompareValue]=y.data.value('CompareValue[1]','bigint')
,[Result]=y.data.value('Result[1]','varchar(40)'),
[TestResult]=y.data.value('TestResult[1]','varchar(10)')
FROM CD t
CROSS APPLY t.XMLData.nodes('/Results/General') x(data)
cross apply t.XMLData.nodes('/Results/Tests') as T2(N)
CROSS APPLY t.XMLData.nodes('/Results/Tests/Test') as y(data)
CROSS APPLY t.XMLData.nodes('Test/ExpectedValues/Expected') z(data)
CROSS APPLY t.XMLData.nodes('Test/ExpectedValues/Actual/') v(data)
I'm assuming (based on your previous question) that this is for SQL Server - correct?
(you should always explicitly and clearly specify what database system you're using - get used to it!)
You need to use something like this:
get a XML node for the <Results>/<General> node to get some basic data
then get a list of all <Test> nodes - get some data from there
based on each <Test> node - get a list of it's <ExpectedValues>/<ExpectedValue> nodes as well as the <Actual> nodes inside the <Test> and pick out the data that you need
In my experience, it's a good idea to use descriptive names - use GeneralData, ExpectedData etc. instead of just x, y and z - that makes it a lot easier to understand what you're dealing with!
Here's the T-SQL / XQuery statement that I used:
SELECT
-- General values
[Version] = GeneralData.value('Version[1]','numeric(3,2)'),
[InputFilename] = GeneralData.value('InputFilename[1]','varchar(80)'),
[Filename] = GeneralData.value('Filename[1]','varchar(80)'),
-- Test-specific values
[Test Name]= TestData.value('#Name','varchar(255)'),
[Test Number]= TestData.value('Number[1]','int'),
-- Test-specific expected values
[ExpectedValue] = ExpectedData.value('.', 'varchar(255)'),
-- Test-specific actual values
[ActualSeq] = ActualData.value('(seq)[1]', 'int'),
ActualValueFound = ActualData.value('(ValueFound)[1]', 'int'),
ActualseqTestResult = ActualData.value('(seqTestResult)[1]', 'varchar(255)')
FROM
CD t
CROSS APPLY
-- fetch the <Results> / <General> node
XMLdata.nodes('/Results/General') AS XGD(GeneralData)
CROSS APPLY
-- fetch a list of <Results> / <Tests> / <Test> nodes
XMLDATA.nodes('/Results/Tests/Test') AS XTD(TestData)
CROSS APPLY
-- for each <Test> node - fetch its contained <ExpectedValues>/<ExpectedValue> nodes
TestData.nodes('ExpectedValues/ExpectedValue') AS XED(ExpectedData)
CROSS APPLY
-- for each <Test> node - fetch its contained <Actual> nodes
TestData.nodes('Actual') AS XAD(ActualData)