Parse XML in T-SQL - sql

I know that there are already numerous topics about querying xml in MS T-SQL, however with all the samples, I wasn't able to get my query to work properly.
I have the following XML:
<group>
<items>
<groupitem>
<key>23137</key>
</groupitem>
<groupitem>
<key>23139</key>
</groupitem>
<groupitem>
<key>23151</key>
</groupitem>
<groupitem>
<key>23153</key>
</groupitem>
</items>
</group>
I want to get all the 'key' items, so that I can insert them into a table (so 4 rows)
I started off putting my xml into variable #xml and running this query:
SELECT doc.value('(key/text())[1]', 'nvarchar(255)') AS 'key'
FROM #xml.nodes('/group/items/groupitem/*') AS ref(doc)
That gave me 4 empty rows, and if I remove the [1], it gives me this error: "XQuery [value()]: '*value()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic '"
Then I tried this:
SELECT doc.value('(/group/items/groupitem/key)[1]', 'nvarchar(255)') AS 'key'
FROM #xml.nodes('/group/items/groupitem/*') AS ref(doc)
That actually gave me some data, but unfortunately, it's 4 times the same key 23137, probably caused by the [1] in the statement. Removing it however brings me back to the same error message as before.
I know how I should do it in Xpath (/group/items/groupitem/key), but can't get my head around how I should do it in T-Sql. Any ideas?

In the following query, context element of doc is already key element :
SELECT doc.value('(key/text())[1]', 'nvarchar(255)') AS 'key'
FROM #xml.nodes('/group/items/groupitem/*') AS ref(doc)
So you should not mention key again in the select clause. You can use . instead to reference current context element :
SELECT doc.value('.', 'nvarchar(255)') AS 'key'
FROM #xml.nodes('/group/items/groupitem/*') AS ref(doc)

Try this:
SELECT
doc.value('(key)[1]', 'int') AS 'key'
FROM
#xml.nodes('/group/items/groupitem') AS ref(doc)
In my case, this returns an output like:
key
-----
23137
23139
23151
23153
Is that what you're looking for?
The call to .nodes() basically gives you a "pseudo" table of XML fragments - one for each match of the XPath expression. So in your case, you get four rows of XML back, each representing the contents of the <groupitem> node. You reach into that, grab the value of the <key> element contained inside, cast it to an int - and presto, you're done!

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

How to filter data from xml content using xpath queries to create a temporary table

I am trying to create a sql query using xpath where i am looking to filter the data i need and put that in a temporary table.
Example:
<superStarsDoc>
<names>
<starname>
<preferredname>pref</preferredname>
<firstNm>Bradd</firstNm>
<lastNm>Pitt</lastNm>
</starname>
</names>
</superStarsDoc>
and i am trying to get something like this but not working
with data(firstName,lastName) as
(
unnest(xpath('/superStarsDoc/names/starname/firstNm[#firstNm="Bradd"]/text()',
(select xmlparse(document superstar_doc))))::text as firstName
,unnest(xpath('/superStarsDoc/names/starname/lastNm[#lastNm="Pitt"]lastNm="/text()',
(select xmlparse(document superstar_doc))))::text as lastName
from dbname.superstartable
)
I tried searching for solution but i did not find anything specific for my requirement, i dont have any attribute to point to that record exactly.
I tried using the following solution but that is not working, i am getting syntax error.
XPath 1.0 to find if an element's value is in a list of values
Note: I typed the code here as i cannot copy paste my code exactly, so please excuse any typos
You should probably fix your XPath with :
/superStarsDoc/names/starname/firstNm[.="Bradd"]/text()
/superStarsDoc/names/starname/lastNm[.="Pitt"]/text()
Generic code :
with superstartable(superstar_doc) as (
values (
'<?xml version="1.0" encoding="UTF-8"?>
<superStarsDoc>
<names>
<starname>
<preferredname>pref</preferredname>
<firstNm>Bradd</firstNm>
<lastNm>Pitt</lastNm>
</starname>
</names>
</superStarsDoc>
'::xml)
)
SELECT
xpath('/superStarsDoc/names/starname/firstNm[.="Bradd"]/text()', superstar_doc)[1] as "first-name",
xpath('/superStarsDoc/names/starname/lastNm[.="Pitt"]/text()', superstar_doc)[1] as "last-name"
from superstartable

extracting all tags(duplicates also) with specified name from xmltype column in sql

i want to extract a tag from an xml and insert into another table.
this XML is having different name spaces hence i use local-name() to fetch the tag which i want.
but some times there are multiple tags with same name. hence its failing with EXACTFETCH RETURNS MULTIPLE NODES. when multiple tags are existed i want to consider both instead of ignoring the second occurence.
source_table(id, payload):
id : 10
payload :
<root>
<name>john</name>
<status>
<statuscode>1</statuscode>
<statusmessage>one</statusmessage>
<statuscode>2</statuscode>
<statusmessage>two</statusmessage>
</status>
</root>
i want to extract stauscode and message and insert into another table
destination_table(id,name,statuscode,message)
output
10,john,1,one
10,john,2,two
below is the query i used
select id,
extract(payload, '//*[local-name()="name"]'),
extract(payload, '//*[local-name()="statuscode"]'),
extract(payload, '//*[local-name()="statusmessage"]')
from source_table;
i can get first occurence or second occurence by specifying [1] or [2] but i need both the stauscodes to be displayed like below
10,john,1,one
10,john,2,two
any help here
Hope this is what you need: Just past this into an empty query window and execute. Adapt it for your needs:
This solution assumes, that the status codes are sorted (as in your example 1,2,...). If this could occur in random order, just ask again...
Short explanation: The CTE "NameAndCode" brings up the name and the statuscodes. The ROW_NUMBER-function give us the node's index. This index I use to fetch the right message.
One more hint: If you can change the XML's format, it would be better to make the message an attribut of statuscode or to have it as subnode...
DECLARE #xmlColumn XML='<root>
<name>john</name>
<status>
<statuscode>1</statuscode>
<statusmessage>one</statusmessage>
<statuscode>2</statuscode>
<statusmessage>two</statusmessage>
</status>
</root>';
WITH NameAndCode AS
(
SELECT #xmlColumn.value('(/root/name)[1]','varchar(max)') AS Name
,x.y.value('.','int') AS Statuscode
,x.y.query('..') XMLNode
,ROW_NUMBER() OVER(ORDER BY x.y.value('.','int')) AS StatusIndex
FROM #xmlColumn.nodes('/root/status/statuscode') AS x(y)
)
SELECT *
,XMLNode.value('(/status[1]/statusmessage[sql:column("StatusIndex")])[1]','varchar(max)')
FROM NameAndCode

Catch value from within XML string

Consider the following query:
SELECT [Details] FROM [History];
This is returning XML strings for [Details] like this:
<auditElement><field id="232131" type="4" name="Attachments" formatstring=""><oldValue>
<![CDATA[RB01B01_000001303; RB01B01_000001304]]></oldValue>
<newValue><![CDATA[ThisIsText]]></newValue></field></auditElement>
<auditElement><field id="232131" type="4" name="Attachments" formatstring=""><oldValue>
</oldValue>
<newValue><![CDATA[ThisIsText]]></newValue></field></auditElement>
So sometimes there is an <oldValue> and sometimes there isn't. Regardless, I want only return the CDATA within <oldValue> (or an empty row if there is no <oldValue>, so:
RB01B01_000001303; RB01B01_000001304
(NULL)
So I tried first
replace('<auditElement><field id="232131" type="4" name="Attachments" formatstring="">
<oldValue><![CDATA[', '', e.[Details]) as Details
but I get an error: String or binary data would be truncated.
How can I do this properly?
There are probably better ways, but try this query using the value method of the xml type:
select x.value('(auditElement/*/oldValue)[1]', 'nvarchar(max)') as result
from (select cast([Details] as xml) as x from [History]) a
I wasn't sure if your data already was typed as xml so I included a cast that might be unnecessary.
Sample SQL Fiddle

How do I select a top-level attribute of an XML column in SQL Server?

I have an XML column in SQL Server that is the equivalent of:
<Test foo="bar">
<Otherstuff baz="belch" />
</Test>
I want to get the value of the foo attribute of Test (the root element) as a varchar. My goal would be something along the lines of:
SELECT CAST('<Test foo="bar"><Otherstuff baz="belch" /></Test>' AS xml).value('#foo', 'varchar(20)') AS Foo
But when I run the above query, I get the following error:
Msg 2390, Level 16, State 1, Line 1
XQuery [value()]: Top-level attribute
nodes are not supported
John Saunders has it almost right :-)
declare #Data XML
set #Data = '<Test foo="bar"><Otherstuff baz="belch" /></Test>'
select #Data.value('(/Test/#foo)[1]','varchar(20)') as Foo
This works for me (SQL Server 2005 and 2008)
Marc
If you dont know the root element:
select #Data.value('(/*/#foo)[1]','varchar(20)') as Foo
Why does .value('#foo', 'varchar(20)') generate the error “Top-level attribute nodes are not supported”?
When you query the xml data type, the context is the document node, which is an implicit node that contains the root element(s) of your XML document. The document node has no name and no attributes.
How can I get the value of an attribute on the root element?
In your XQuery expression, include the path to the first root element:
DECLARE #Data xml = '<Customer ID="123"><Order ID="ABC" /></Customer>'
SELECT #Data.value('Customer[1]/#ID', 'varchar(20)')
-- Result: 123
If you don’t know (or don’t want to specify) the name of the root element, then just use * to match any element:
SELECT #Data.value('*[1]/#ID', 'varchar(20)')
-- Result: 123
Because the query context is the document node, you don’t need to prefix the XQuery expression with a forward slash (as the other answers unnecessarily do).
Why do I have to include [1]?
The XQuery expression you pass to value() must be guaranteed to return a singleton. The expression Customer/#ID doesn’t satisfy this requirement because it matches both ID="123" and ID="456" in the following example:
DECLARE #Data xml = '<Customer ID="123" /><Customer ID="456" />'
Remember that the xml data type represents an XML document fragment, not an XML document, so it can contain multiple root elements.
What’s the difference between Customer[1]/#ID and (Customer/#ID)[1]?
The expression Customer[1]/#ID retrieves the ID attribute of the first <Customer> element.
The expression (Customer/#ID)[1] retrieves the ID attribute of all <Customer> elements, and from this list of attributes, picks the first.
The following example demonstrates the difference:
DECLARE #Data xml = '<Customer /><Customer ID="123" /><Customer ID="456" />'
SELECT #Data.value('Customer[1]/#ID', 'varchar(20)')
-- Result: NULL (because the first Customer element doesn't have an ID attribute)
SELECT #Data.value('(Customer/#ID)[1]', 'varchar(20)')
-- Result: 123