Extracting values from XML field with unusual XML using SQL - sql

Hoping someone can help -
The XML format was put together with a very simple syntax, but for some reason I'm struggling to parse it using a standard 'value' type query.
I'm experienced with SQL, but only have limited experience in XML, and after 2 hours of frustration and much Googling, I thought I'd ask for my own sanity!
The data is stored as a text string, so converting it to XML before parsing:
<!-- Data config file -->
<Config>
<!-- keys-->
<foo value="bar"/>
<foo1 value="bar1"/>
<big_foo value="bar/bar.com"/>
<other value="f00"/>
The query I'm using is:
SELECT
col.value('foo1[0]', 'nvarchar(max)') as VALUE
from
(
select
CAST((SELECT TOP 1 xml_text FROM dbo.xml_lookup)AS XML)
as Col)x
but this returns NULL rather than the expected "bar1".
Any idea where I'm going wrong?

proper XPath would be
col.value('(Config/foo1)[1]/#value', 'nvarchar(max)')
sql fiddle demo

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

Can't parse XML with outer apply

I have an XML column in a table which i am trying to parse out values from to flat table structure.
I am trying to input the XML here but stackoverflow ses it as code and when i try and format as code it still won't accept it.
I can't even get data from "Header" level.
<RequestMessage xmlns="http://iec.ch/TC57/2011/schema/message" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Message.xsd">
<Header>
<Verb>created</Verb>
<Noun>MeterReadings</Noun>
<Timestamp>2021-03-08T00:57:18+01:00</Timestamp>
<Source>Ipsum Lorum</Source>
<AsyncReplyFlag>true</AsyncReplyFlag>
<AckRequired>true</AckRequired>
<MessageID>Ipsum Lorum</MessageID>
<CorrelationID />
</Header>
<Payload>
<MeterReadings xmlns:MeterReadings="http://iec.ch/TC57/2011/MeterReadings#" xmlns="http://iec.ch/TC57/2011/MeterReadings#">
<MeterReading>
<IntervalBlocks>
<IntervalReadings>
<timeStamp>2021-03-07T01:00:00+01:00</timeStamp>
<value>480.196</value>
<ReadingQualities>
<ReadingQualityType ref="3.0.0" />
</ReadingQualities>
</IntervalReadings>
<IntervalReadings>
<ReadingType ref="11.0.7.3.1.2.12.1.1.0.0.0.0.101.0.3.72.0" />
</IntervalReadings>
</IntervalBlocks>
<Meter>
<mRID>0000000000000</mRID>
<status>
<remark>Ipsum Lorum</remark>
<value>ESP</value>
</status>
</Meter>
<UsagePoint>
<mRID>73599900000000</mRID>
</UsagePoint>
</MeterReading>
</MeterReadings>
</Payload>
</RequestMessage>
I am not able to parse it and i have tried using examples from other threads. I am trying to not use OPENXML solution because requires DECLARE and executing the built in procedure for clearing the XML from memmory periodically. I am trying to use the OUTER APPLY solution.
Like Shugos solution in How to parse XML data in SQL server table or Query XML with nested nodes on Cross Apply.
It doesn't work.
It returns null for the timestamp column.
select
t.file_created_time
,c.value('(Timestamp)[1]','varchar(max)') as timestamp
from load.t t
OUTER APPLY t.xml_data.nodes('RequestMessage/Header') as m(c)
Please try the following solution.
Starting from SQL Server 2005 onwards, it is better to use XQuery language, based on the w3c standards, while dealing with the XML data type.
Microsoft proprietary OPENXML and its companions sp_xml_preparedocument and sp_xml_removedocument are kept just for backward compatibility with the obsolete SQL Server 2000. Their use is diminished just to very few fringe cases.
I had to comment out the following tag <!--<IntervalReadings>--> to make your XML well-formed.
XML Header fragment has a default namespace:
xmlns="http://iec.ch/TC57/2011/schema/message"
XML Payload fragment has its own two additional namespaces:
xmlns:MeterReadings="http://iec.ch/TC57/2011/MeterReadings#"
xmlns="http://iec.ch/TC57/2011/MeterReadings#"
Namespaces should be taken into account.
Check it out below.
SQL
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xml_data XML);
INSERT INTO #tbl (xml_data) VALUES
(N'<RequestMessage xmlns="http://iec.ch/TC57/2011/schema/message"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="Message.xsd">
<Header>
<Verb>created</Verb>
<Noun>MeterReadings</Noun>
<Timestamp>2021-03-08T00:57:18+01:00</Timestamp>
<Source>Ipsum Lorum</Source>
<AsyncReplyFlag>true</AsyncReplyFlag>
<AckRequired>true</AckRequired>
<MessageID>Ipsum Lorum</MessageID>
<CorrelationID/>
</Header>
<Payload>
<MeterReadings xmlns:MeterReadings="http://iec.ch/TC57/2011/MeterReadings#"
xmlns="http://iec.ch/TC57/2011/MeterReadings#">
<MeterReading>
<IntervalBlocks>
<IntervalReadings>
<timeStamp>2021-03-07T01:00:00+01:00</timeStamp>
<value>480.196</value>
<ReadingQualities>
<ReadingQualityType ref="3.0.0"/>
</ReadingQualities>
</IntervalReadings>
<!--<IntervalReadings>-->
<ReadingType ref="11.0.7.3.1.2.12.1.1.0.0.0.0.101.0.3.72.0"/>
</IntervalBlocks>
<Meter>
<mRID>0000000000000</mRID>
<status>
<remark>Ipsum Lorum</remark>
<value>ESP</value>
</status>
</Meter>
<UsagePoint>
<mRID>73599900000000</mRID>
</UsagePoint>
</MeterReading>
</MeterReadings>
</Payload>
</RequestMessage>');
-- DDL and sample data population, end
WITH XMLNAMESPACES(DEFAULT 'http://iec.ch/TC57/2011/schema/message')
SELECT id
, c.value('(Noun/text())[1]','VARCHAR(30)') AS Noun
, c.value('(Timestamp/text())[1]','DATETIMEOFFSET(0)') AS [timestamp]
FROM #tbl
CROSS APPLY xml_data.nodes('/RequestMessage/Header') AS t(c);
Output
+----+---------------+----------------------------+
| id | Noun | timestamp |
+----+---------------+----------------------------+
| 1 | MeterReadings | 2021-03-08 00:57:18 +01:00 |
+----+---------------+----------------------------+
You need to respect and include the XML namespace in your XML document in your XQuery!
<RequestMessage xmlns="http://iec.ch/TC57/2011/schema/message"
**********************************************
Try something like this:
WITH XMLNAMESPACES(DEFAULT N'http://iec.ch/TC57/2011/schema/message')
SELECT
t.id,
c.value('(Timestamp)[1]','varchar(max)') as timestamp
FROM
load.t t
CROSS APPLY
t.xml_data.nodes('RequestMessage/Header') AS m(c)
Also when trying to run this on my SQL Server, I get an error that the XML as shown is malformed.....
UPDATE:
If you need to also access bits in the Payload section - you need to also respect that XML namespace there:
<MeterReadings xmlns:MeterReadings="http://iec.ch/TC57/2011/MeterReadings#"
xmlns="http://iec.ch/TC57/2011/MeterReadings#">
***********************************************
Try this:
WITH XMLNAMESPACES(N'http://iec.ch/TC57/2011/schema/message' as hdr,
N'http://iec.ch/TC57/2011/MeterReadings#' as mr)
SELECT
t.id,
c.value('(hdr:Timestamp)[1]', 'varchar(50)') AS timestamp,
col.value('(mr:MeterReading/mr:IntervalBlocks/mr:IntervalReadings/mr:timeStamp)[1]', 'varchar(50)') AS MeterReadingsTimestamp
FROM
load.t t
CROSS APPLY
t.xml_data.nodes('/hdr:RequestMessage/hdr:Header') AS m(c)
CROSS APPLY
t.xml_data.nodes('/hdr:RequestMessage/hdr:Payload/mr:MeterReadings') AS mr(col)

SQL Query to Pars Out a Value from NTEXT (XML format) field

I have XML data in an NTEXT field (ou.ORDMODE), that I need to parse out a value (description) from. Column may contain null values. The data looks like this:
<?xml version="1.0" encoding="utf-16"?>
<UDFValidatedValue xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Accellos.Platform.UDF">
<Description>Export</Description>
<Value>EXP</Value>
<ValueType>String</ValueType>
</UDFValidatedValue>
The line I have in my query is this:
CAST(REPLACE(CAST(ou.ORDMODE as NVARCHAR(MAX)),' xmlns="http://schemas.datacontract.org/2004/07/Accellos.Platform.UDF"','') as XML).value ('(/UDFValidatedValue/Description/text())[0]', 'nvarchar(100)') as Mode3,
but Mode3 column is returned as blank.
What am I doing wrong?
You could use this:
DEclare #xml xml = N'<?xml version="1.0" encoding="utf-16"?>
<UDFValidatedValue xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Accellos.Platform.UDF">
<Description>Export</Description>
<Value>EXP</Value>
<ValueType>String</ValueType>
</UDFValidatedValue>';
;WITH XMLNAMESPACES(DEFAULT 'http://schemas.datacontract.org/2004/07/Accellos.Platform.UDF')
select #xml.value ('(/UDFValidatedValue/Description)[1]', 'nvarchar(100)') as Mode3
or
select #xml.value ('declare namespace
udf="http://schemas.datacontract.org/2004/07/Accellos.Platform.UDF";
(/udf:UDFValidatedValue/udf:Description)[1]', 'nvarchar(100)') as Mode3
Demo link: Rextester
As ZLK pointed out in his comment, your own approach seems to be quite okay, just the index must be 1 instead of 0. Try it here:
ad "quite okay": Actually this is absolutely far away from "quite okay", read my hint below...
DECLARE #tbl TABLE(SomeXmlInText TEXT);
INSERT INTO #tbl VALUES
('<?xml version="1.0" encoding="utf-16"?>
<UDFValidatedValue xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Accellos.Platform.UDF">
<Description>Export</Description>
<Value>EXP</Value>
<ValueType>String</ValueType>
</UDFValidatedValue>');
SELECT SomeXmlInText
,CAST(REPLACE(CAST(SomeXmlInText as NVARCHAR(MAX)),' xmlns="http://schemas.datacontract.org/2004/07/Accellos.Platform.UDF"','') as XML).value ('(/UDFValidatedValue/Description/text())[1]', 'nvarchar(100)') as Mode3
FROM #tbl
From your answer I take, that there is still no result...
Some possible issues:
XML is not really as you expect it
more / other namespaces
some weird no, not for you issue :-D
Hint
As you probably know TEXT, NTEXT and IMAGE are deprecated for centuries and should really not be used any more... If you have the slightest chance to change this, then you should store your XML in the appropritate XML-type...
You should not need any casts or manipulations on string level. This can be working, but is really out-dated...

Flattening xml data in sql

I'm trying to flatten XML data in a SQL query but I always seem to get nulls.
I tried the cross/outer apply method described here.
The column with XML data is called Data.
I'm guessing that the xml data with these links need to be somehow also added?
Could you please help to get a proper SQL query?
Query I tried:
SELECT
v.name
,pref.value('(LocalId/text())[1]', 'nvarchar(10)') as localid
FROM [database].[requests] v
outer apply v.Data.nodes('/DataForm') x(pref)
GO
example of xml data in that column:
<Dataform xmlns="http://somelongasslink.org/hasalsosomestuffhere" xmlns:i="http://somexlmschemalink/">
<DeleteDate xmlns="http://somelongasslink.org/hasalsosomestuffhere" i:nil="true" />
<LocalId xmlns="http://somelongasslink.org/hasalsosomestuffhere">5325325</LocalId>
...
You can use this code to get the result you're looking for:
;WITH XMLNAMESPACES(DEFAULT 'http://somelongasslink.org/hasalsosomestuffhere')
SELECT
rq.Name,
LocalID = TC.value('(LocalId)[1]', 'nvarchar(10)')
FROM
[database].[requests] rq
CROSS APPLY
rq.Data.nodes('/Dataform') AS TX(TC)
GO
There were two problems with your code:
you're not respecting / including the XML namespace that's defined on the XML document
<Dataform xmlns="http://somelongasslink.org/hasalsosomestuffhere"
*******************************************************
you didn't pay attention to the case-sensitivity of XML in your call to .nodes() - you need to use .nodes('/Dataform') (not /DataForm - the F is not capitalized in your XML)

How to get specific XML namespace in XQuery in SQL Server

I have a XML that I need one specific namespace according to node like temprature with hls i need namespace of that "http://www.schema.hls.com/extension" I have tried with these
DECLARE #EventXML AS XML
SET #EventXML='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns:test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ns="urn:global:test:xsd:1"
xmlns:hls="http://schema.hls.com/extension" creationDate="2007-01-25T00:00:00Z"
schemaVersion="1.0">
<TestBody>
<TestList>
<TestEvent>
<hls:temperature>20</hls:temperature>
</TestEvent>
</TestList>
</TestBody>
</ns:test>'
SELECT
OE.value('#ns','varchar(50)') + '#' + OE.value('fn:local-name(.)[1]','varchar(50)'),
OE.value('#id','varchar(50)'),
CONVERT(VARCHAR(4000),CASE WHEN OE.exist('./*') =1 THEN OE.query('./*') ELSE
OE.value('./text()[1]','varchar(100)') END)
FROM #EventXML.nodes('//TestEvent/*') TestEvent(OE)
WHERE OE.value('fn:local-name(.)[1]','varchar(50)') IN --(#tag)
(SELECT Split.a.value('.', 'VARCHAR(100)') AS extag
FROM (SELECT CONVERT(XML,'<M>' + REPLACE(ISNULL('temperature','0'), ',', '</M><M>') + '</M>') AS String
) AS A CROSS APPLY String.nodes ('/M') AS Split(a))
I am using these in SQL query window but getting only third column value 20 not get namespace by #ns
Please suggest how to get the namespace
OE.value('#ns','varchar(50)')
by these.
thanks in advanced.
Your code and XML somehow just don't quite match up - and the query is really quite confusing....
If you want to fetch the data, you must respect the XML namespaces in play. You need to declare them with a WITH XMLNAMESPACES() construct, and you need to use them in your XPath.
But also: the node you're selecting (<hls:temperature>) doesn't really have any id and ns attributes..... so of course you're not getting any values!
I tried to use a trimmed down version and I added the two attributes - just to show how to use the XML namespaces stuff in your code.
Here it comes:
DECLARE #EventXML AS XML
SET #EventXML =
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns:test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ns="urn:global:test:xsd:1"
xmlns:hls="http://schema.hls.com/extension"
creationDate="2007-01-25T00:00:00Z" schemaVersion="1.0">
<TestBody>
<TestList>
<TestEvent>
<hls:temperature ns="test" id="42">20</hls:temperature>
</TestEvent>
</TestList>
</TestBody>
</ns:test>'
-- define your XML namespaces that are in play.
-- You *MUST* match the namespace definition, but the *prefixes* that you define
-- can be something else entirely than in the XML document!
-- Of course, inside your XPath, you *MUST* use the defined prefixes!
;WITH XMLNAMESPACES('urn:global:test:xsd:1' AS x1,
'http://schema.hls.com/extension' AS x2)
SELECT
OE.value('#ns', 'varchar(50)'),
OE.value('#id', 'varchar(50)')
FROM
#EventXML.nodes('/x1:test/TestBody/TestList/TestEvent/x2:*') TestEvent(OE)
This code - using the XML namespaces defined and used in your XML - produces this output:
(No column name) (No column name)
test 42
So this shows how you can access the attributes - if they are present! - on your XML nodes, even with the presence of XML namespaces.