Conditional xml in sql - sql

I only want to show the categoryid node and value if its not an empty guid. I've tried different formats but nothing worked. It keeps just printing the if statement as a string in the results. I read that you had to have an else condition as part of your code, can I just put an empty string there? I've looked through a few articles online about this and the msdn reference with no luck. For some reason it's evaluating my if statement.
declare #XML xml = N'
<books>
<book>
<title>Book 1</title>
<categoryid>00000000-0000-0000-0000-000000000000</categoryid>
<author>Chris</author>
<price>10</price>
</book>
<book>
<title>Book 2</title>
<categoryid>DF3D696D-B7A3-44A2-BC7D-1D45745C979B</categoryid>
<author>Spencer</author>
<price>20</price>
</book>
</books>';
select #XML.query(
'<books>
{
for $b in /books/book
return
<book>
{$b/title}
{$b/price}
if(not($b/categoryid = 00000000-0000-0000-0000-000000000000))
then {$b/categoryid}
else
</book>
}
</books>'
);

I think there were two issues.
1 - The conditional block needed to be enclosed in curly braces before the "if" and after the "else" ended.
2 - The empty guid value needed to be its own variable - I think within the condition it was treating it as a string.
I also had trouble getting the "not" operator to behave as expected, so I just reversed the condition to bypass that problem.
declare #XML xml = N'
<books>
<book>
<title>Book 1</title>
<categoryid>00000000-0000-0000-0000-000000000000</categoryid>
<author>Chris</author>
<price>10</price>
</book>
<book>
<title>Book 2</title>
<categoryid>DF3D696D-B7A3-44A2-BC7D-1D45745C979B</categoryid>
<author>Spencer</author>
<price>20</price>
</book>
</books>';
declare #emptyguid uniqueidentifier = '00000000-0000-0000-0000-000000000000'
select #XML.query('
<books>
{
for $b in /books/book
return
<book>
{$b/title}
{$b/price}
{
if($b/categoryid = sql:variable("#emptyguid")) then
()
else (
$b/categoryid
)
}
</book>
}
</books>
');

Related

XQuery wont return a document

I just started learning about XQuery and am trying to play with it a little.
Now, iv'e built what i think is a simple query and a correct one (a much as i can see) but SQL Manger
prints me the error:
XQuery [query()]: "=" was expected.
I looked back at the code but i cant understand how to fix it..
here is my code:
DECLARE #x AS XML = N'
<bookstore>
<book category="cooking">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="children">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="web">
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<author>Per Bothner</author>
<author>Kurt Cagle</author>
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2003</year>
<price>49.99</price>
</book>
<book category="web" cover="paperback">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>';
SELECT #x.query('for $i in bookstore/book
let $a := $i/author
let $t := $i/title
let $p := $i/price
where $i/#price < 40
return
<This is a test>
<author>$a</author>
<title>$t</title>
<price>$p</price>
</This is a test>')
AS [I hope this works]
Thanks in advance for your help :)
This is not a valid xml element:
<This is a test>
changing this to
<ThisIsATest>
will fix your query but after the fix, as-is, your query doesn't return anything. My T-SQL XQuery skills are rusty but you can get what you're trying to do using nodes and the values method.
This:
SELECT
author = bs.book.value('(author/text())[1]', 'varchar(8000)'),
title = bs.book.value('(title/text())[1]', 'varchar(8000)'),
price = bs.book.value('(price/text())[1]', 'varchar(8000)')
FROM (VALUES(#x)) AS ml(x)
CROSS APPLY ml.x.nodes('bookstore/book') AS bs(book);
Returns:
author title price
--------------------- --------------------- ----------
Giada De Laurentiis Everyday Italian 30.00
J K. Rowling Harry Potter 29.99
James McGovern XQuery Kick Start 49.99
Erik T. Ray Learning XML 39.95
This (which includes your sample XML):
DECLARE #x AS XML = N'
<bookstore>
<book category="cooking">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="children">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="web">
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<author>Per Bothner</author>
<author>Kurt Cagle</author>
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2003</year>
<price>49.99</price>
</book>
<book category="web" cover="paperback">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>';
SELECT
author = bs.book.value('(author/text())[1]', 'varchar(8000)'),
title = bs.book.value('(title/text())[1]', 'varchar(8000)'),
price = bs.book.value('(price/text())[1]', 'varchar(8000)')
FROM (VALUES(#x)) AS ml(x)
CROSS APPLY ml.x.nodes('bookstore/book') AS bs(book)
FOR XML PATH('Book');
Returns:
<Book>
<author>Giada De Laurentiis</author>
<title>Everyday Italian</title>
<price>30.00</price>
</Book>
<Book>
<author>J K. Rowling</author>
<title>Harry Potter</title>
<price>29.99</price>
</Book>
<Book>
<author>James McGovern</author>
<title>XQuery Kick Start</title>
<price>49.99</price>
</Book>
<Book>
<author>Erik T. Ray</author>
<title>Learning XML</title>
<price>39.95</price>
</Book>
You were close, but there are three things to mention:
As Alan Burstein has told you, the tag <This is a test> is not valid. The message you get, is because the engine thinks of an element <This> and expects the is to be the first attribute, which should be followed by = like in <This is="some value">.
Your where tries to query a #price attribute
In order to resolve the values you need {} around your variables.
Try this:
SELECT #x.query('for $i in bookstore/book
let $a := $i/author
let $t := $i/title
let $p := $i/price
where $p < 40
return
<This-is-a-test>
<author>{$a}</author>
<title>{$t}</title>
<price>{$p}</price>
</This-is-a-test>')
AS [I hope this works]

dc:creator from XML into SQL table

I am trying to store an XML file (Code below) but the the dc:creator is causing an error. I have found from other related questions on here stating that I should use ;WITH XMLNAMESPACES(''http://purl.org/dc/elements/1.1/'' AS dc) but this has not worked either any ideas on what might be the problem/solution ? .
XML file:
<?xml version="1.0" encoding="UTF-8"?>
-<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="http://talksport.com/rss/sports-news/football/feed" version="2.0">
-<channel>
<title>Football</title>
<link>http://talksport.com/rss/sports-news/football/feed</link>
<description/>
<language>en</language>
<atom:link type="application/rss+xml" rel="self" href="http://talksport.com/rss/sports-news/football/feed"/>
-<item>
<title>Hillsborough families 'back introduction of rail seating' as bereaved family says 'standing did not kill our 96'</title>
<link>http://talksport.com/football/hillsborough-families-back-introduction-rail-seating-bereaved-family-says-standing-did-not</link>
<description/>
<pubDate>Wed, 19 Jul 2017 08:18:37 +0000</pubDate>
<dc:creator>talkSPORT</dc:creator>
<guid isPermaLink="false">247276 at http://talksport.com</guid>
</item>
</rss>
This is the stored procedure:
CREATE PROCEDURE feed.usp_importXML(#file VARCHAR(8000))
AS
BEGIN
DECLARE #Query VARCHAR(8000)
SET #Query ='
DECLARE #xmlFile as XML
SET #xmlFile = ( cast
SELECT CONVERT(XML,BulkColumn) as BulkColumn
FROM OPENROWSET (BULK '''+#file+''', SINGLE_BLOB) AS t)
INSERT INTO feed.tempXML (title,link,source)
SELECT
title = t.value (''title[1]'', ''NVARCHAR(300)''),
link = t.value (''link[1]'', ''NVARCHAR(300)''),
source = t.value(''(dc:creator)[1]'',''NVARCHAR(30)'')
FROM #xmlFile.nodes(''/rss/channel/item'') AS xTable(t);'
EXEC(#Query)
END
GO
In your case it might be enough to replace the dc: with a wildcard: *:. Assuming your XML is written into an XML file already, you might try as such:
DECLARE #xmlFile XML=
N'<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="http://talksport.com/rss/sports-news/football/feed" version="2.0">
<channel>
<title>Football</title>
<link>http://talksport.com/rss/sports-news/football/feed</link>
<description />
<language>en</language>
<atom:link type="application/rss+xml" rel="self" href="http://talksport.com/rss/sports-news/football/feed" />
<item>
<title>Hillsborough families ''back introduction of rail seating'' as bereaved family says ''standing did not kill our 96''</title>
<link>http://talksport.com/football/hillsborough-families-back-introduction-rail-seating-bereaved-family-says-standing-did-not</link>
<description />
<pubDate>Wed, 19 Jul 2017 08:18:37 +0000</pubDate>
<dc:creator>talkSPORT</dc:creator>
<guid isPermaLink="false">247276 at http://talksport.com</guid>
</item>
</channel>
</rss>';
SELECT
title = t.value ('title[1]', 'NVARCHAR(300)'),
link = t.value ('link[1]', 'NVARCHAR(300)'),
source = t.value('(*:creator)[1]','NVARCHAR(30)')
FROM #XmlFile.nodes('/rss/channel/item') AS xTable(t);
This will work too:
WITH XMLNAMESPACES('http://purl.org/dc/elements/1.1/' AS dc)
SELECT
title = t.value ('title[1]', 'NVARCHAR(300)'),
link = t.value ('link[1]', 'NVARCHAR(300)'),
source = t.value('(dc:creator)[1]','NVARCHAR(30)')
FROM #XmlFile.nodes('/rss/channel/item') AS xTable(t);
And you can declare the namespace within .value too:
SELECT
title = t.value ('title[1]', 'NVARCHAR(300)'),
link = t.value ('link[1]', 'NVARCHAR(300)'),
source = t.value('declare namespace dc="http://purl.org/dc/elements/1.1/";(dc:creator)[1]','NVARCHAR(30)')
FROM #XmlFile.nodes('/rss/channel/item') AS xTable(t);

Converting XML node values to comma separated values in SQL

I am trying to convert XML node values to comma separated values but, getting a
Incorrect syntax near the keyword 'SELECT'.
error message
declare #dataCodes XML = '<Root>
<List Value="120" />
<List Value="110" />
</Root>';
DECLARE #ConcatString VARCHAR(MAX)
SELECT #ConcatString = COALESCE(#ConcatString + ', ', '') + Code FROM (SELECT T.Item.value('#Value[1]','VARCHAR(MAX)') as Code FROM #dataCodes.nodes('/Root/List') AS T(Item))
SELECT #ConcatString AS Result
GO
I tried to follow an article but not sure how to proceed further. Any suggestion is appreciated.
Expectation:
Comma separated values ('120,110') stored in a variable.
Try this;
DECLARE #dataCodes XML = '<Root>
<List Value="120" />
<List Value="110" />
</Root>';
DECLARE #ConcatString VARCHAR(MAX)
SELECT #ConcatString = COALESCE(#ConcatString + ', ', '') + Code
FROM (
SELECT T.Item.value('#Value[1]', 'VARCHAR(MAX)') AS Code
FROM #dataCodes.nodes('/Root/List') AS T(Item)
) as TBL
SELECT #ConcatString AS Result
GO
You just need to add an alias to your sub SQL query.
For future readers, XML data can be extracted into arrays, lists, vectors, and variables for output in comma separated values more fluidly using general purpose languages. Below are open-source solutions using OP's needs taking advantage of XPath.
Python
import lxml.etree as ET
xml = '<Root>\
<List Value="120" />\
<List Value="110" />\
</Root>'
dom = ET.fromstring(xml)
nodes = dom.xpath('//List/#Value')
data = [] # LIST
for elem in nodes:
data.append(elem)
print((", ").join(data))
120, 110
PHP
$xml = '<Root>
<List Value="120" />
<List Value="110" />
</Root>';
$dom = simplexml_load_string($xml);
$node = $dom->xpath('//List/#Value');
$data = []; # Array
foreach ($node as $n){
$data[] = $n;
}
echo implode(", ", $data);
120, 110
R
library(XML)
xml = '<Root>
<List Value="120" />
<List Value="110" />
</Root>'
doc<-xmlInternalTreeParse(xml)
data <- xpathSApply(doc, "//List", xmlGetAttr, 'Value') # LIST
print(paste(data, collapse = ', '))
120, 110
To do this without a variable, you can use the nodes method to convert the xml nodes into a table format with leading commas, then use FOR XML PATH('') to collapse it into a single line of XML, then wrap that in STUFF to convert it to varchar and strip off the initial leading comma:
DECLARE #dataCodes XML = '<Root>
<List Value="120" />
<List Value="110" />
</Root>';
SELECT STUFF(
(
SELECT ', ' + T.Item.value('#Value[1]', 'VARCHAR(MAX)')
FROM #dataCodes.nodes('/Root/List') AS T(Item)
FOR XML PATH('')
), 1, 2, '')

VTD XML navigating to child nodes which are siblings to first child

My XML
<catalog>
<item Id="1">
<book id="b1">
</book>
<cd id="c1">
</cd>
</item>
<item Id="11">
<book id="b11">
</book>
<cd id="c11">
</cd>
</item>
</catalog>
what I have ..
ap.selectXPath("/catalog/item");
while ((result = ap.evalXPath()) != -1) {
if (vn.toElement(VTDNav.FIRST_CHILD, "item")) {
do {
//do something with the contnets in the item node
} while (vn.toElement(VTDNav.NEXT_SIBLING, "book"));
}
// move to the parent node to traverse the rest of the items
}
What I want is to get to the "cd" node.
In some of the examples I saw VTDNav.NEXT_CHILD but seems like is not available . Can anyone suggest how to get to the node I need to . For now I am managing to do it by getting to the FIRST_CHILD and then moving to the next sibling
if (vn.toElement(VTDNav.FIRST_CHILD, "book")) {
// Move to the next sibling
vn.toElement(VTDNav.NEXT_SIBLING, "cd");
}
Appreciate all your help
Regards
You should not need to have next child. What you look for is probably NEXT_SIBLING, after you call FIRST_CHILD...

Format / Indent XML code while sending email using sp_send_dbmail [formatting xml code in mail body]

I am trying to send a dbmail with XML code as a body, see my code below showing the same,
declare #xml xml = '<?xml version="1.0"?>
<Order>
<Date>2003/07/04</Date>
<CustomerId>123</CustomerId>
<CustomerName>Acme Alpha</CustomerName>
<Item>
<ItemId> 987</ItemId>
<ItemName>Coupler</ItemName>
<Quantity>5</Quantity>
</Item>
<Item>
<ItemId>654</ItemId>
<ItemName>Connector</ItemName>
<Quantity unit="12">3</Quantity>
</Item>
<Item>
<ItemId>579</ItemId>
<ItemName>Clasp</ItemName>
<Quantity>1</Quantity>
</Item>
</Order>'
--SELECT #xml
declare #bodyprep varchar(max)
select #bodyprep = cast(#xml as varchar(max))
EXEC msdb. dbo.sp_send_dbmail
#profile_name='Profile-A' ,
#recipients ='.com',
#from_address = '.com' ,
#subject = 'test',
#body = #bodyprep
but the mail i receive is unaligned like shown below,
<Order><Date>2003/07/04</Date><CustomerId>123</CustomerId><CustomerName>Acme Alpha</CustomerName><Item><ItemId> 987</ItemId><ItemName>Coupler</ItemName><Quantity>5</Quantity></Item><Item><ItemId>654</ItemId><ItemName>Connector</ItemName><Quantity unit="12">3</Quantity></Item><Item><ItemId>579</ItemId><ItemName>Clasp</ItemName><Quantity>1</Quantity></Item></Order>
Is there a way to format or indent the XML code above ?
I would like to send a formatted code as shown below,
<Order>
<Date>2003/07/04</Date>
<CustomerId>123</CustomerId>
<CustomerName>Acme Alpha</CustomerName>
<Item>
<ItemId> 987</ItemId>
<ItemName>Coupler</ItemName>
<Quantity>5</Quantity>
</Item>
<Item>
<ItemId>654</ItemId>
<ItemName>Connector</ItemName>
<Quantity unit="12">3</Quantity>
</Item>
<Item>
<ItemId>579</ItemId>
<ItemName>Clasp</ItemName>
<Quantity>1</Quantity>
</Item>
</Order>
Thanks
msdb.dbo.sp_send_dbmail has an additional parameter, #body_format = 'HTML', which would allow you to display the XML as you like, once you'd decorated it with the proper HTML markup. That's the only way you can control the appearance of the email body to the level you've specified, i.e. with proper indentation and color-coding.
If you can do without the color-coding, #body_format = 'TEXT' (the default) is fine, but either way you have some work on your hands to get it indented as you indicate.