Getting Multiple Answers out of an XML String SQL - 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);

Related

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.

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.

Querying XML data in SQL Server 2012

I have a database that has a table Parameters_XML with columns.
id, application, parameter_nr flag, value
The parameter_nr is for example 1 and the value for that parameter is the following:
<Root>
<Row>
<Item id="341" flags="1">
<Str>2</Str>
</Item>
<Item id="342" flags="1">
<Str>10</Str>
</Item>
<Item id="2196" flags="1">
<Str>7REPJ1</Str>
</Item>
</Row>
</Root>
I need to retrieve the values for all the applications where the item is 341, 342 and 2196.
Eg: for the application 1 the value for the item 341 is 2 and so on.
I have written the following query:
SELECT cast (value as XML).value('data(/Root/Row/Item[#id="431"],')
FROM Parameters_Xml x
WHERE parameter_nr = 1
I get the following error:
Msg 174, Level 15, State 1, Line 1
The value function requires 2 argument(s).
Why my query is not valid?
Try someting like this:
SELECT
CAST(x.Value AS XML).value('(/Root/Row/Item[#id="341"]/Str)[1]', 'nvarchar(100)')
FROM dbo.Parameters_Xml x
WHERE parameter_nr = 1
You're telling SQL Server to go find the <Item> node (under <Root> / <Row>) with and id=341 (that what I'm assuming - your value in the question doesn't even exist) and then get the first <Str> node under <Item> and return that value
Also: why do you need CAST(x.Value as XML) - if that column contains only XML - why isn't it defined with datatype XML to begin with? If you have this, you don't need any CAST ...
DECLARE #str XML;
SET #str = '<Root>
<Row>
<Item id="341" flags="1">
<Str>2</Str>
</Item>
<Item id="342" flags="1">
<Str>10</Str>
</Item>
<Item id="2196" flags="1">
<Str>7REPJ1</Str>
</Item>
</Row>
</Root>'
-- if you want specific values then
SELECT
xmlData.Col.value('#id','varchar(max)') Item
,xmlData.Col.value('(Str/text())[1]','varchar(max)') Value
FROM #str.nodes('//Root/Row/Item') xmlData(Col)
where xmlData.Col.value('#id','varchar(max)') = 342
--if you want all values then
SELECT
xmlData.Col.value('#id','varchar(max)') Item
,xmlData.Col.value('(Str/text())[1]','varchar(max)') Value
FROM #str.nodes('//Root/Row/Item') xmlData(Col)
--where xmlData.Col.value('#id','varchar(max)') = 342
Edit After CommentIf i query my db: select * from parameters_xml where parameter_nr = 1 i will receive over 10000 rows, each row is like the following: Id app param value 1 1 1 11 I need for all the 10000 apps to retrieve the item id and the value from the XML value - like you did for my eg.
-- declare temp table
declare #temp table
(val xml)
insert into #temp values ('<Root>
<Row>
<Item id="341" flags="1">
<Str>2</Str>
</Item>
<Item id="342" flags="1">
<Str>10</Str>
</Item>
<Item id="2196" flags="1">
<Str>7REPJ1</Str>
</Item>
</Row>
</Root>')
insert into #temp values ('<Root>
<Row>
<Item id="3411" flags="1">
<Str>21</Str>
</Item>
<Item id="3421" flags="1">
<Str>101</Str>
</Item>
<Item id="21961" flags="1">
<Str>7REPJ11</Str>
</Item>
</Row>
</Root>')
-- QUERY
SELECT
xmlData.Col.value('#id','varchar(max)') Item
,xmlData.Col.value('(Str/text())[1]','varchar(max)') Value
FROM #temp AS T
outer apply T.val.nodes('/Root/Row/Item') as xmlData(Col)
Try this:
select cast (value as
XML).value('data(/Root/Row/Item[#id="431"]','nvarchar(max)')
data type is 2nd parameter.

Select multiple values in XML file and concatenation them with xQuery

I have the following XML:
<items>
<item value="1"/>
<item value="2"/>
<item value="4"/>
</items>
and I would like to select all item value and concatenate them like this - see below - with XQuery :
1.2.4
Any ideas?
Thanks in advance.
There are two parts to your question:
getting values out of xml
concatenating multiple values together
The second part of your question has been covered off very nicely in the answers to this question.
In terms of getting the values out of your xml, try the below:
-- Your xml variable
DECLARE #xml AS XML = '<items>
<item value="1"/>
<item value="2"/>
<item value="4"/>
</items>'
-- Example of selecting the values into rows
SELECT
item.value('.', 'int')
FROM
#xml.nodes('/items/item/#value') as T1(item)
-- Use your favourite/'best for your circumstance' method of
-- concatenating the rows into one string
-- see https://stackoverflow.com/q/194852/1208914 for other ways
SELECT
item.value('.', 'varchar(50)') + ' ' as 'data()'
FROM
#xml.nodes('/items/item/#value') as T1(item)
for xml path('')
I've put the code above into a sql fiddle that you can access and play with here: http://sqlfiddle.com/#!6/d41d8/18144/0
Using XQuery you can simply select the values using XPath and join them together using a dot.
string-join(/items/item/#value, '.')

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