Find if XML contain then return something - sql

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.

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

need to query XML for value in Oracle 11g

I have a table called parts with an XMLType column called RunList_XML
The XML has several Unit tags example <Item> <Unit>420</Unit> </Item> <Item> <Unit>10</Unit> </Item> <Item> <Unit>0</Unit> </Item>
I want to query to get back a list of all parts
that have a Unit of 420
I cant seem to figure out how to get this using = 420 or = '420'
the below gives back false positives
select * from Parts P
WHERE P.RunList_XML.extract('ArrayOfItem// /Unit/text()').getStringVal() like '%420'
Try this using existsnode:
select * from Parts P
WHERE existsnode(RunList_XML, 'ArrayOfItem//Item[Unit="420"]') = 1
existsnode returns 1 if node is found and 0 if not.

Getting Multiple Answers out of an XML String SQL

I have an XML document saved in a Column as varchar(max). The text I want is surrounded by <Text> Words I Want</Text>but these Text tags repeat sometimes 4 or 5 times.
How do I loop through the same document x number of times dependent on the number of text tags?
Currently I'm using this to pull out the first bit of text
DECLARE #first_char nvarchar(10)
DECLARE #second_char nvarchar(10)
SET #first_char = 'xt>';
SET #second_char = '</text>';
SELECT[TestId]
,[SectionId],
SUBSTRING
(
-- column
settings
-- start position
,CHARINDEX(#first_char, Settings , 1) + 3
-- length
,CASE
WHEN (CHARINDEX(#second_char, Settings , 0) - CHARINDEX(#first_char, Settings, 0)) > 0
THEN CHARINDEX(#second_char, Settings, 0) - CHARINDEX(#first_char, Settings, 0) - 3
ELSE 0
END
) AS Response
FROM [B2IK-TestBuilder].[dbo].[Questions]
group by [TestId]
,[SectionId], settings
and I know how many times the Text tag appears.
This is an example of the xml document saved a varchar(max):
<Settings>
<ShowNotes>true</ShowNotes>
<ShowComment>false</ShowComment>
<TextBefore>From the six safety essentials, a </TextBefore>
<TextAfter>is essential before any work is carried out?</TextAfter>
<Items>
<Item>
<Text>Answer 1</Text>
</Item>
<Item>
<Text>Answer 2</Text>
</Item>
<Item>
<Text>Answer 3</Text>
</Item>
<Item>
<Text>Answer 4</Text>
</Item>
<Item>
<Text>Answer 5</Text>
</Item>
<Item>
<Text>Answer 6</Text>
</Item>
Thank you in advance.
OK since SQL 2005 you can use XPath to query the data. I would recommend using the XML column type for the Settings column. Then you can use CROSS APPLY to get the Item nodes.
SELECT q.TestId,q.SectionId,x.XmlCol.value('(Text)[1]','VARCHAR(MAX)')
FROM Questions q
CROSS APPLY q.settings.nodes('/Settings/Items/Item') x(XmlCol);
If you for some reason cannot change the type of you settings column you could cast it in your statement.
SELECT q.TestId,q.SectionId,x.XmlCol.value('(Text)[1]','VARCHAR(MAX)')
FROM (SELECT TestId,SectionId, cast([settings] as xml) as Data FROM Questions) q
CROSS APPLY q.settings.nodes('/Settings/Items/Item') x(XmlCol);

Modify XML values identified through cross apply

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