Select values from XML in SQL - sql

I need to select values from an XML document. These values are stored as childnodes as follows:
<customers>
<customer>
<kunnr>1</kunnr>
<kdgrp>2</kdgrp>
</customer>
<customer>
<kunnr>2</kunnr>
<kdgrp>2</kdgrp>
</customer>
</customers>
I need to select the values of kunnr and kdgrp for every customer node. I expect a result like this:
kunnr kdgrp
1 2
2 2
What I tried so far:
SELECT #xml.query('/customers/customer/kunnr') AS KUNNR,
#xml.query('/customers/customer/kdgrp') AS KDGRP
This results in one row with two colums containing XML:
KUNNR KDGRP
<kunnr>1</kunnr><kunnr>2</kunnr> <kdgrp>2</kdgrp><kdgrp>2</kdgrp>
Another try:
SELECT C.value('/kunnr/text()','nvarchar(10)') as KUNNR,
C.value('/kdgrp/text()','nvarchar(10)') as KDGRP
from #xml.nodes('/customers/customer') AS T(C);
This resulted in the following error message:
XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'

Maybe something like this:
DECLARE #xml XML
SET #xml='<customers>
<customer>
<kunnr>1</kunnr>
<kdgrp>2</kdgrp>
</customer>
<customer>
<kunnr>2</kunnr>
<kdgrp>2</kdgrp>
</customer>
</customers>'
And then a query like this:
SELECT
c.value('kunnr[1]', 'nvarchar(10)') AS kunnr,
c.value('kdgrp[1]', 'nvarchar(10)') AS kdgrp
FROM
#xml.nodes('//customers/customer') as t(c)
This will give you this result:
kunnr kdgrp
1 2
2 2

I had a problem related to extracting values from T-SQL XML and found an issue that may help others. When retrieving data with: .value('(/root/subnode)[1]', 'varchar(max)') that call would not retrieve data but the following call did: .value('(//subnode)[1]', 'varchar(max)'). Note that the working version replaced the root node with a /. The problem with the first call seemed to be that the root node came with a specification of an xml namespace like &< root xmlns="http://www..." &> and to get the .value call to return data I needed to get past the specification of the namespace which was causing things to fail for some reason.

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

Extract Value from XML having same tag name in SQL Server

I have XML variable defined below and its value.
I want to fetch the text defined between tag <TextNodeChild> in single query.
Kindly help.
Declare #XMLVariable =
'<?xml version="1.0"?>
<root>
<TextNodeParent>
<TextNodeChild>12345</TextNodeChild>
<TextNodeChild>67890</TextNodeChild>
<TextNodeChild>12389</TextNodeChild>
</TextNodeParent>
</root>'
I need output like this:
12345
67890
12389
You could use the XQuery (i.e. XML query) .nodes() method
SELECT
TextNodeParent = n.value('.[1]', 'NVARCHAR(max)')
FROM
#XMLVariable.nodes('root/TextNodeParent/*') as p(n)
EDIT : If you want to just the select the TextNodeChild node data then little change in xml path as follow
#XMLVariable.nodes('root/TextNodeParent/TextNodeChild') as p(n)
Result
TextNodeParent
12345
67890
12389
#YogeshSharma's solution works - here - because you have nothing but <TextNodeChild> elements under your <TextNodeParent> node.
However, if you had various node, and you wanted to extract only the <TextNodeChild> ones and get their values (and ignore all others), you'd have to use something like this instead:
SELECT
TextNodeParent = XC.value('.', 'INT')
FROM
#XMLVariable.nodes('root/TextNodeParent/TextNodeChild') as XT(XC)

Retrieve all XML elements with the same prefix in SQL Server

I have an XML file in a format similar to:
<XML>
<Field1>100</Field1>
<Field2>200</Field2>
<Field3>300</Field3>
<Test>400</Test>
</XML>
I need to write a query that will get all of the element values that start with Field. So given the XML above the result should be
FieldVal
--------
100
200
300
I've tried the following but it does not work:
Select
xc.value('text()', 'int')
From
#XMLData.nodes('/XML/[starts-with(name(), ''Field'')]') As xt(xc)
NOTE: I am well aware that this task could be easily done if I reformatted my XML but unfortunately I have no control over the format of the XML.
One way is
declare #XMLData xml ='<XML>
<Field1>100</Field1>
<Field2>200</Field2>
<Field3>300</Field3>
<Test>400</Test>
</XML>'
Select
xc.value('.', 'int')
From #XMLData.nodes('/XML/*') As xt(xc)
WHERE xc.value('local-name(.)', 'varchar(50)') LIKE 'Field%'
Prefix name with special character and check contains instead.
declare #x xml ='<XML>
<Field1>100</Field1>
<Field2>200</Field2>
<Field3>300</Field3>
<Test>400</Test>
</XML>';
select t.n.value('.','varchar(100)')
from #x.nodes ('XML/*[contains(concat("$",local-name()),"$Field")]') t(n);
I think it's this what you are looking for:
DECLARE #xml XML=
'<XML>
<Field1>100</Field1>
<Field2>200</Field2>
<Field3>300</Field3>
<Test>400</Test>
</XML>';
SELECT Fld.value('.','int') AS FieldOnly
FROM #xml.nodes('/XML/*[substring(local-name(.),1,5)="Field"]') AS A(Fld)
Just because of the discussion in comments:
DECLARE #fldName VARCHAR(100)='Field';
SELECT Fld.value('.','int') AS FieldOnly
FROM #xml.nodes('/XML/*[substring(local-name(.),1,string-length(sql:variable("#fldName")))=sql:variable("#fldName")]') AS A(Fld)
Change the first line to "Test" (case sensitive!), and you'd get just the one row with 400...

Modify xml element name in SQL Server

How to change element name from Cust to Customer?
<Cust id="1">
<Name>aaaaaaaaaa</Name>
<Desc>bbbbbbbbbb</Desc>
</Cust>
When I'm using following statement
select #myXml.query('/node()[1]/node()') for xml raw('Customer')
sql removes attributes
<Customer>
<Name>aaaaaaaaaa</Name>
<Desc>bbbbbbbbbb</Desc>
</Customer>
Try this:
SELECT
#myXml.value('(/Cust/#id)[1]', 'int') AS '#id',
#myXml.query('/node()[1]/node()')
FOR XML PATH('Customer')
Gives me an output of:
<Customer id="1">
<Name>aaaaaaaaaa</Name>
<Desc>bbbbbbbbbb</Desc>
</Customer>
With the FOR XML PATH, you can fairly easily "restore" that attribute that gets lost in the conversion.
You could use replace:
replace(replace(#YourXml, '<Cust id', '<Customer id)', '</Cust>', '</Customer>')
This is fairly safe, as < is not valid as data in XML, it would appear as < or an ASCII or UNICODE sequence.

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