SQL query to get most recent date from XML document - sql

I am doing a SQL query against a column with an XML document located.
The XML document looks like the following.
<root>
<date>2016-10-12</date>
<date>2016-12-01</date>
<date>2016-11-13</date>
</root>
As you can see the dates are out of order.
I am looking for a SQL query that will get the most recent date from the XML document (in this case: 2016-12-01).

One way is to read all data and find the maximum externally (external ORDER BY with TOP 1, like in Prdp's answer, or MAX(), eventually with GROUP BY).
Another way is a FLWOR-XQuery:
DECLARE #xml XML=
'<root>
<date>2016-10-12</date>
<date>2016-12-01</date>
<date>2016-11-13</date>
</root>';
SELECT #xml.value('max(for $d in /root/date return xs:date($d))','date')
This means:
Take each value in /root/date, return it as date and find the highest!
Both approaches will need to read the whole list, but it should be a bit faster only to look for the maximum value, rather than return a full list and do some external sorting, picking again...

Try this
DECLARE #xml XML
SET #xml = '<root>
<date>2016-10-12</date>
<date>2016-12-01</date>
<date>2016-11-13</date>
</root>'
SELECT Top 1 x.col.value('.', 'date') AS dates
FROM #xml.nodes('/root/date') x(col)
ORDER BY dates DESC

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

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

SQL Server: How to get the value of a XML element specifying an attribute?

For the usage in a SQL query I need to get the value of a specific XML element. The XML element is specified by its attribute.
My XML elements looks like this:
<translations>
<value lang="en-US">example</value>
<value lang="de-DE">Beispiel</value>
</translations>
and the value I am looking for would be "example" when I specify lang to be "en-US".
I found a way to get this value by using the query() function and afterwards the value() function.
declare #S varchar(max)
set #S =
'<translations>
<value lang="en-US">example</value>
<value lang="de-DE">Beispiel</value>
</translations>'
declare #X xml
set #X = CAST(#S as xml)
select #X.query('/translations/value[#lang="en-US"]').value('.','varchar(max)')
This select statement return the value "example" I am looking for by using the query() and value() function. But is there also a - more convenient - way to only use value() OR query()?
Thank you in advance!
Sure there is...
You also can shorten the declaration:
declare #X xml=
'<translations>
<value lang="en-US">example</value>
<value lang="de-DE">Beispiel</value>
</translations>';
select #X.value('(/translations/value[#lang="en-US"])[1]','varchar(max)');
The point is, that you need a singleton result when you are using .value(). You achieve this by putting the XPath in paranthesis and force the first element to be taken (in this case it's the only element).
Btw: If you need this (and sooner or later you will need this...), you might put the "en-US" as parameter into your query like this:
declare #prm VARCHAR(10)='en_US';
select #X.value('(/translations/value[#lang=sql:variable("#prm")])[1]','varchar(max)');
You can reach the similar, but with a value of the actual query by using sql:column().

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

Does nodes() or openxml returns rows in same order as it finds in xml?

I have an xml which i need to parse using openxml or nodes(). The xml contains few child tags that repeat with different values, as below.
<root>
<value>10</value>
<value>12</value>
<value>11</value>
<value>1</value>
<value>15</value>
<root>
For my code it is very important that i get all these rows returned in same order as in xml. I googled and gogled but nothing tells me if the #mp:id is always returned in same order as in xml. Or if nodes() return values in same order as it encounters them.
All I want to know if I can trust any of those two methods and be happy with proper order of rows.
P.S. excuse any errors or mistakes in above text, I dont enjoy typing codes in an android window either.
You can use row_number on the shredded XML like this.
declare #XML xml=
'<root>
<value>10</value>
<value>12</value>
<value>11</value>
<value>1</value>
<value>15</value>
</root>'
select value
from
(
select T.N.value('.', 'int') as value,
row_number() over(order by T.N) as rn
from #xml.nodes('/root/value') as T(N)
) as T
order by T.rn
Uniquely Identifying XML Nodes with DENSE_RANK
Update:
You can also use a numbers table like this;
declare #XML xml=
'<root>
<value>10</value>
<value>12</value>
<value>11</value>
<value>1</value>
<value>15</value>
</root>';
with N(Number) as
(
select Number
from master..spt_values
where type = 'P'
)
select #XML.value('(/root/value[sql:column("N.Number")])[1]', 'int')
from N
where N.Number between 1 and #XML.value('count(/root/value)', 'int')
order by N.Number
XPath allows you to select nodes explicitly by ordinal: '/root[1]/value[1]' is the first element, '/root[1]/value[2]' is the second etc. Also could use '(/root/value)[1]' and '(/root/value[2])'. This way you can select exactly the element you want, and selecting element 1 then element 2 then element 3 etc will give you controlled order. Slow, but controlled.
Updated P.S. Wouldn't this be nice to be true?
declare #x xml = '<root>
<value>10</value>
<value>12</value>
<value>11</value>
<value>1</value>
<value>15</value>
<root>';
select x.value(N'position()', N'int') as position,
x.value(N'.', 'int') as value
from #x.nodes(N'//root/value') t(x)
Unfortunately, is not...
Msg 2371, Level 16, State 1, Line 9
XQuery [value()]: 'position()' can only be used within a predicate or XPath selector
And the existence of this error makes me worry that order may be broken sometimes...