Unable to retrive XML elements value using XPATH when element has attributes - sql

The task i have to grab the elements of a given XML , generate xpath for each element and then retrieve the values of each element:
I am able to create step one and two but when an element has attributes the XPATH doesn't work:
so if i had the following XPATH:
/Envelope[1]/Body[1]/sVerify[1]/verifyPost[1]/Message[1]/Error[1]
/Envelope[1]/Body[1]/sVerify[1]/verifyPost[1]/scope[1]/machine[1]/space[1]
/Envelope[1]/Body[1]/sVerify[1]/verifyPost[1]/scope[1]/Date[1]
It works for Below XML and am able to get elements value correctly:
-- Works: I can retrieve the Elements values using XPATH
<Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<sVerify>
<verifyPost>
<scope>
<machine>
<name>test</name>
<space>test2</space>
</machine>
<Sys>internal</Sys>
<Date>2013-02-28</Date>
</scope>
<Message>
<Error>11111111111</Error>
<Descrip>222222222</Descrip>
</Message>
<Final>true</Final>
<Receipt>33333</Receipt>
</verifyPost>
</sVerify>
</Body>
</Envelope>
Notice that i had to manually remove all the attirbutes in order for XPATH to work. It doesn't work if the XML was as below:
-- Doesn't work: can't get the elements value
<Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<sVerify xmlns="http://www.myCompany.com/Location/2014">
<verifyPost>
<scope>
<machine xmlns:i="http://www.myCompany.com/Location/2014">
<name>test</name>
<space>test2</space>
</machine>
<Sys>internal</Sys>
<Date>2013-02-28</Date>
</scope>
<Message xmlns="http://www.myCompany.com/Location/2014">
<Error>11111111111</Error>
<Descrip>222222222</Descrip>
</Message>
<Final xmlns="http://www.myCompany.com/Location/2014">true</Final>
<Receipt>33333</Receipt>
</verifyPost>
</sVerify>
</Body>
</Envelope>
There can be any number of attributes so i never know in advance what the attirbutes are going to be. What is the correct way to ensure an XPATH will always find the value of the given element regardless of if it has attributes or not.
Below is how i execute it in TSQL:
DECLARE #generatedXPATH nvarchar(500),
#elementVal nvarchar(50),
#xml xml,
#query nvarchar(max)
-- it works with this payload
-- because attributes aren't there replacing it with xml
-- where element attributes are present fails the element value extraction using xpath
SET #xml = '<Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <Body> <sVerify> <verifyPost> <scope> <machine> <name>test</name> <space>test2</space> </machine> <Sys>internal</Sys> <Date>2013-02-28</Date> </scope> <Message> <Error>11111111111</Error> <Descrip>222222222</Descrip> </Message> <Final>true</Final> <Receipt>33333</Receipt> </verifyPost> </sVerify> </Body> </Envelope>'
SET #generatedXPATH = '/Envelope[1]/Body[1]/sVerify[1]/verifyPost[1]/scope[1]/machine[1]/space[1]'
SET #elementVal = ''
SET #query = N'SELECT #elementVal= Nodes.node.value(''(' + #generatedXPATH
+ ')[1]'', ''varchar(50)'') FROM #xml.nodes(''.'') AS Nodes(node)';
exec sp_ExecuteSql
#query,
N' #xml xml,#elementVal nvarchar(max) output',
#xml = #xml,
#elementVal = #elementVal output
select #elementVal
Update:
Seems like only the attributes where attribute is without prefix is causing the issue. for example if attribute is xlmns="..........." then i am not able to grab its value using XPATH, if attribute was xlmns:i=".........." then it seems to work. Not sure what's going on.

If you have a XML with a custom namespace, you need to define it and use it as an affix for every element that is defined under that namespace using WITH XMLNAMESPACES (http://technet.microsoft.com/en-us/library/ms177607.aspx) In your case, try this:
SET #generatedXPATH = '/Envelope[1]/Body[1]/ns:sVerify[1]/ns:verifyPost[1]/ns:scope[1]/ns:machine[1]/ns:space[1]'
SET #elementVal = ''
SET #query = N'WITH XMLNAMESPACES (''http://www.myCompany.com/Location/2014'' AS ns)
SELECT #elementVal= Nodes.node.value(''(' + #generatedXPATH
+ ')[1]'', ''varchar(50)'') FROM #xml.nodes(''.'') AS Nodes(node)';
Edit: If you do not know the namespace, using * instead of ns should work for getting the element regardless of namespace:
SET #generatedXPATH = '/Envelope[1]/Body[1]/*:sVerify[1]/*:verifyPost[1]/*:scope[1]/*:machine[1]/*:space[1]'
SET #elementVal = ''
SET #query = N'SELECT #elementVal= Nodes.node.value(''(' + #generatedXPATH
+ ')[1]'', ''varchar(50)'') FROM #xml.nodes(''.'') AS Nodes(node)';

Related

Is there a way to put a header and footer in XML output when using SQL

I’m writing a query that returns data in XML format and I need to add a header that contains some static data, and a footer that contains a count of all of the records in the file. is there a way to do that so that the header and footer only appear once in the file?
my example query is as follows:
SET QUOTED_IDENTIFIER on
SET NOCOUNT ON
DECLARE #XMLTEST XML
SELECT #XMLTEST = (
SELECT GOLFER_NAME AS [GENERAL/NAME],
GOLFER_ID AS [GENERAL/ID],
HANDICAP_INDEX AS [ATTRIBUTES/HANDICAP],
GOLFER_AGE AS [ATTRIBUTES/AGE]
FROM GOLFERS
FOR XML PATH ('row'), Root('root')
)
SET #XMLTEST.modify(
'insert
(
attribute xsi:noNamespaceSchemaLocation {"ARIS_Inbound-v4.0.xsd"}
)
into (/GolfData)[1]')
SELECT #XMLTEST
This returns data in the following format:
<root>
<row>
<general>
<name>woods,tiger</name>
<id>1</id>
</general>
<attributes>
<handicap>4</handicap>
<age>45</age>
</attributes>
</row>
<row>
<general>
<name>fowler,ricky</name>
<id>2</id>
</general>
<attributes>
<handicap>7</handicap>
<age>39</age>
</attributes>
</row>
</root>
Which is what i am looking for but I need to have a header in the file with a format of:
<header>
<version>1.2.113</version>
<author>lincoln,abraham</author>
</header>
and a footer that counts the number of entries (or rows in this case) like this:
<trailer>
<rowcount>2</rowcount>
</trailer>
is this possible?
perhaps I'm overlooking the obvious, but couldn't you concatenate your normal results with your header and footer? Something like:
declare #header varchar(max) = "your header"
declare #footerstart varchar(max) = "your footer start"
declare #footercount int;
declare #footerend varchar(max) = "your footer end"
declare #xmlMiddle varchar(max)
select #xmlMiddle = -- your code that generates the full XML.
--add a select count(*) from your dataset like
select #footercount = from.... where...
select #header + #xmlMiddle + #footerstart + #footercount + #footerend

SQL - Define/Modify XML with variables

I'm trying to define an XML variable with VARCHAR characters inside of it...
Example
DECLARE #number nvarchar(10);
SET #number = '10'
DECLARE #xml XML;
SET #xml =
'
<Root>
<Node> Hello {sql:variable("#number")} </Node>
</Root>
'
However, the problem is that you can only set xml variables to string literals. Is there any way of doing this? Thanks!
Not sure why you want to do this. Use + to concatenate the variable inside the string then assign it to xml varibale
DECLARE #number NVARCHAR(10);
SET #number = '10'
DECLARE #xml XML;
SET #xml = '
<Root>
<Node> Hello ' + Isnull(#number,'') + ' </Node>
</Root>
'
To view the xml
select #xml
Result :
<Root>
<Node> Hello 10 </Node>
</Root>
DECLARE #number nvarchar(10) = '10';
DECLARE #xml XML = N'';
SELECT #xml = #xml.query('
<Root>
<Node>Hello {sql:variable("#number")}</Node>
</Root>
');
SELECT #xml;
/*
<Root>
<Node>Hello 10</Node>
</Root>
*/
In my eyes the existing answers (upvoted and accepted) are no good paths to go. One should (almost) never create XML via string concatenation...
Why not the simple way? Let the XML-engine do the hard work!
DECLARE #number nvarchar(10);
SET #number = '10'
DECLARE #xml XML=(SELECT 'Hello ' + #number FOR XML PATH('Node'),ROOT('Root'),TYPE);
SELECT #xml;
Returns
<Root>
<Node>Hello 10</Node>
</Root>
Just imagine, your (string!) variable includes some text with forbidden characters (><& or qoutes or one of the many special / far east / whatever characters)... Using FOR XML ... will implicitly do all the escaping for you...
One side effect: If your variable is NULL you will not get a NULL for all, but a valid XML with an empty node:
<Root>
<Node />
</Root>

Getting node value in xml column

Please let me know why the following XML query is not fetching any result.
I am trying to get value EffectiveUserName tag.
DECLARE #MyXML XML
SET #MyXML = '<PropertyList xmlns="urn:schemas-microsoft-com:xml-analysis" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<Catalog>name</Catalog>
<Timeout>600</Timeout>
<Format>Native</Format>
<DbpropMsmdFlattened2>false</DbpropMsmdFlattened2>
<Cube>Model</Cube>
<DbpropMsmdOptimizeResponse>1</DbpropMsmdOptimizeResponse>
<DbpropMsmdActivityID>68A6900B-20F8-4A02-AEC3-7C56B2D3C5D5</DbpropMsmdActivityID>
<DbpropMsmdRequestID>A0D1E07F-AE29-4CCA-AEE4-3B79D97CA426</DbpropMsmdRequestID>
<DbpropMsmdCurrentActivityID>68A6900B-20F8-4A02-AEC3-7C56B2D3C5D5</DbpropMsmdCurrentActivityID>
<LocaleIdentifier>1033</LocaleIdentifier>
<EffectiveUserName>userid#domainname.com</EffectiveUserName>
<sspropinitappname>PowerBI</sspropinitappname>
</PropertyList>'
select #MyXML.value('(/PropertyList/EffectiveUserName)[1]','varchar(max)')
Your XML has a default namespace that you must respect and include in your query!
<PropertyList xmlns="urn:schemas-microsoft-com:xml-analysis"
***********************************************
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
Use this code to grab the value you're looking for by defining the default XML namespace for your query:
;WITH XMLNAMESPACES(DEFAULT 'urn:schemas-microsoft-com:xml-analysis')
SELECT
#MyXML.value('(/PropertyList/EffectiveUserName)[1]', 'varchar(50)')
You can ignore the namespace by using *: before the tag names:
select #MyXML.value('(/*:PropertyList/*:EffectiveUserName)[1]','varchar(max)')

How to get value from XML attribute using Sql:Variable in xquery

I want to get attribute value from XML using Xquery.
MY XML is
<Answers>
<AnswerSet>
<Answer questionId="NodeID">155</Answer>
<Answer questionId="ParentNode" selectedValue="12">Product</Answer>
</AnswerSet>
</Answers>
Below is my query.
DECLARE #Field Varchar(100)
DECLARE #Attribute VARCHAR(100)
SET #Field='ParentNode'
SET #Attribute = 'selectedValue'
SELECT ISNULL(PropertyXML.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")])[1]','varchar(max)'),'') ,
ISNULL(PropertyXML.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")]/sql:variable(#Attribute) )[1]','varchar(max)'),'')
FROM node
WHERE id=155
below line is working fine with sql:variable
ISNULL(PropertyXML.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")])[1]','varchar(max)'),'')
but I am getting error in below line..
ISNULL(PropertyXML.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")]/sql:variable(#Attribute) )[1]','varchar(max)'),'')
Any ideas on how to get provided attribute(#Attribute) value in result?
Try something like
ISNULL(#Xml.value('(/Answers/AnswerSet/Answer[#questionId=sql:variable("#Field")]/#*[local-name() = sql:variable("#Attribute")])[1]','varchar(max)'),'')

Updating XML attribute in SQL Server XML column

I am trying to update a node of in my XML which is stored in a SQL Server XML column, the line below works if my XML is in a XML element but now I somehow need to change it to XML attributes, apparently the line becomes invalid after the change.
Works for XMLElement:
UPDATE [Customers]
SET voucherXML.modify('replace value of (/ArrayOfCampaignVoucher/CampaignVoucher/Qty/text())[1] with "50"')
WHERE voucherXML.value('(/ArrayOfCampaignVoucher/CampaignVoucher/VouCode)[1]', 'nvarchar(50)') = #VoucherCode
I tried changing the statement like this but no luck, no errors but QTY values doesn't get change to the value of #NewQuantity:
UPDATE [Customers]
SET voucherXML='<ArrayOfCampaignVoucher xmlns:xsd="http://www.w3.org/2001/XMLSchema" Qty="' + CAST(#NewQuantity AS NVARCHAR(16)) + '" />'
WHERE voucherXML.value('(/CampaignVoucher/VouCode)[1]', 'nvarchar(50)') = #VoucherCode
This is how my XML looks in the SQL Server XML column:
<ArrayOfCampaignVoucher xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CampaignVoucher VouCode="Vouc001" Qty="16" />
<CampaignVoucher VouCode="Vouc002" Qty="18" />
<CampaignVoucher xsi:nil="true" />
</ArrayOfCampaignVoucher>
You should use the XQuery functions - not string together your XML like this....
Try this instead:
DECLARE #newquantity INT = 55
UPDATE dbo.Customers
SET voucherXML.modify('replace value of (/ArrayOfCampaignVoucher/CampaignVoucher[#VouCode="Vouc002"]/#Qty)[1] with sql:variable("#NewQuantity") ')
This works for me - the Qty attribute of the node with VouCode="Vouc002" gets updated to the value I defined before in a SQL variable
Update: to insert a new <CampaignVoucher> entry, use XQuery something like this:
UPDATE dbo.Customers
SET voucherXML.modify('insert <CampaignVoucher VouCode="Vouc003" Qty="42" />
as last into (/ArrayOfCampaignVoucher)[1]')