Count non-empty nodes in XML in SQL Server - sql

I need to count all b nodes which are not empty (so result should be 2).
<a>
<b>1</b>
<b/>
<b>g</b>
</a>
I'm using code below but this returns count off all nodes (empty included).
select top 1 rc.XmlContent.value('count(//a/b)', 'int') from Table rc

If you use //a/b/text() rather than just //a/b, then you get a count of 2
DECLARE #x XML= '<a><b>1</b><b/><b>g</b></a>';
SELECT #x.value('count(//a/b/text())', 'int');

Sorry, this is not the answer! I misread this completely and thought your are looking for the empty nodes. There is an appropriate answer given by GarethD already (same idea, just the other way round).
I don't delete it, because it might help others...
The empty element <b/> (same as <b></b>) is existing but has no text().
DECLARE #xml XML =
N'<a>
<b>1</b>
<b/>
<b></b>
<b>g</b>
</a>';
select #xml.value('count(/a/b[empty(text())])', 'int')
This returns 2, because there is <b/> and <b></b>.
Just for completeness, you might negate the predicate, which is your needed result actually:
select #xml.value('count(/a/b[not(empty(text()))])', 'int')

Use this XPath expression
count(/a/b[normalize-space(text())=''])
Incorporated in your code it would look like this:
select top 1 rc.XmlContent.value('count(/a/b[normalize-space(text())=""])', 'int') from Table rc

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

Extract field from XML Data in SQL

I have an issue as I never done that before.
I have an SQL table with the following :
ID int;
xml_record xml;
The xml record is looking like that :
<root xml:space="preserve" id="XXX">
<c1>Data1</c1>
<c2>Data2</c2>
<c3>Data3</c3>
...
<cn>DataN</cn>
</root>
However I tried to use the following query with no success (return null) :
SELECT xml_record.value('c1[1]','varchar(50)') as value_c1
FROM myTable
The problem might come from the "space" but not sure.
You just need to fix the expression:
SELECT xml_record.value('(/root/c1)[1]','varchar(50)') AS value_c1
FROM ...
SELECT
xml_record.value
( '(/root/c1/text())[1])',
'varchar(50)') as value_c1
FROM myTable
else remove the first xml line

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...

T-SQL/XML Query to get for each row in the query a subfield as well as the whole lement

I have an XML string like this:
<Root>
<Elem>
<RecTime>2016-08-17 12:30PM</RecTime>
<Otherfield>blah blah</Otherfield>
.. other fields ..
</Elem>
<Elem>
<RecTime>2016-08-17 15:30PM</RecTime>
<Otherfield>more blah</Otherfield>
.. other fields ..
</Elem>
</Root>
Obviously this describes a list of elements. I want to extract the record time as well as the entire element for every element in the XML - this is because I want to insert in a table the record time as well as the entire element: the table could be declared as
DECLARE Table myTable(SampleTime datetime, Data xml)
I tried the query
declare #xml xml
set #xml='<Root>
<Elem>
<RecordTime>2016-08-17 12:30:00PM</RecordTime>
<Otherfield>2</Otherfield>
<field2/>
</Elem>
<Elem>
<RecordTime>2016-08-17 15:30:00PM</RecordTime>
<Otherfield>3</Otherfield>
<field2>hello there</field2>
</Elem>
</Root>'
--INSERT INTO myTable
SELECT SampleTime = T.Item.value('RecordTime[1]', 'dateTime'),
Data = #xml.query('//Root/Elem[1]')
FROM #xml.nodes('//Root/Elem') T(item)
but it gives me rows that contain the proper time for each row but only the first element in the list for the 'whole element' part of the query:
I circled in red the proof that I select the wrong element
How should I shape the query to get in response the sample time for each element as well as the corresponding element?
Thanks for the help!
The issue is in Data = #xml.query('//Root/Elem[1]'). To achieve what you want use something like this.
--INSERT INTO myTable
SELECT SampleTime = T.Item.value('RecordTime[1]', 'dateTime'),
Data = T.item.query('.')
FROM #xml.nodes('Root/Elem') T(item) --No // before Root. Thanks to Shnugo
This is result.
2016-08-17 12:30:00.000 <Elem><RecordTime>2016-08-17 12:30:00PM</RecordTime><Otherfield>2</Otherfield><field2 /></Elem>
2016-08-17 15:30:00.000 <Elem><RecordTime>2016-08-17 15:30:00PM</RecordTime><Otherfield>3</Otherfield><field2>hello there</field2></Elem>