Selecting columns as XML with namespace - sql

I need to select some columns from a table as XML with namespaces included in them along with other columns as is. For example, I have a following table layout:
ID C1 X1C1 X1C2 X2C3
1 A 1 2 3
What the query should return is:
ID C1 XmlData
1 A <xmldata1>
2 A <xmldata2>
Where <xmldata1> would be:
<Root xmlns:xsd="w3.org/2001/XMLSchema" xmlns:xsi="w3.org/2001/XMLSchema-instance" xmlns:mst="microsoft.com/wsdl/types/">
<Child attrib="C1">
<ChildValue xsi:type="xsd:integer">1</ChildNode>
</Child>
<Child attrib="C2">
<ChildNode xsi:type="xsd:integer">2</ChildNode>
</Child>
</Root>
and <xmldata2> would be:
<Rootxmlns:xsd="w3.org/2001/XMLSchema" xmlns:xsi="w3.org/2001/XMLSchema-instance" xmlns:mst="microsoft.com/wsdl/types/">
<Child attrib="C3">
<ChildNode xsi:type="xsd:integer">3</ChildNode>
</Child>
</Root>
I have a good reference how to build the xml from this SO question but I'm not able to put in the namespaces. If this is possible how to do it?
Edit:
I've used following query attempting to get the required result:
select 1 ID, 'A' C1, 1 X1C1, 2 X1C2, 3 X2C3
into #t
;with xmlnamespaces('w3.org/2001/XMLSchema' as xsd, 'w3.org/2001/XMLSchema-instance' as xsi, 'microsoft.com/wsdl/types/' as mst)
select ID, C1, (select (SELECT 'C1' "#attrib", 'xsd:integer' "ChildValue/#xsi:type",t.X1C1 as 'ChildValue' FOR XML PATH('Child'), type),(SELECT 'C2' "#name", 'xsd:integer' "ChildValue/#xsi:type", t.X1C2 as 'ChildValue' FOR XML PATH('Child'), type) FOR XML PATH('Root'), type) as property_data
FROM #t t
drop table #t
Here is the output of its xml part:
<Root xmlns:mst="microsoft.com/wsdl/types/" xmlns:xsi="w3.org/2001/XMLSchema-instance" xmlns:xsd="w3.org/2001/XMLSchema">
<Child xmlns:mst="microsoft.com/wsdl/types/" xmlns:xsi="w3.org/2001/XMLSchema-instance" xmlns:xsd="w3.org/2001/XMLSchema" attrib="C1">
<ChildValue xsi:type="xsd:integer">1</ChildValue>
</Child>
<Child xmlns:mst="microsoft.com/wsdl/types/" xmlns:xsi="w3.org/2001/XMLSchema-instance" xmlns:xsd="w3.org/2001/XMLSchema" name="C2">
<ChildValue xsi:type="xsd:integer">2</ChildValue>
</Child>
</Root>
I can't get rid of the namespaces in the Child node.

I used this solution: TSQL for xml add schema attribute to root node
Basically, I did not put the namespace in the beginning but after generating the required xml structure I casted the xml to nvarchar(max) and replaced the root node with the desired namespace.
I also needed to use namespace prefix in the attribute. For that I used a pseudo attribute name which I replaced with a proper xml namespace prefix.
Both operations were done using tsql REPLACE function. Hacky but couldn't find other proper ways to do it.

you need to include WITH xmlnamespaces , example:
;with xmlnamespaces('w3.org/2001/XMLSchema' as xsd, 'w3.org/2001/XMLSchema-instance' as xsi, 'microsoft.com/wsdl/types/' as mst)
select ID, C1,
(select
(SELECT 'C1' "#name",t.C1 as 'value'FOR XML PATH('Property'), type),
(SELECT 'C2' "#name",t.C2 as 'value'FOR XML PATH('property'), type)
FOR XML PATH('data'), type) as property_data
FROM TableName t

Have you tried like?
select XML_COL_NAME.value('(/rootNode//childNode/node())[1]', 'nvarchar(64)') from tableName

Related

SQL query for XML data

I have a SQL Server database table with a column called XML that contains XML data which is structured like this:
<Item xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://test/data">
<Roots>
<Root>
<Name>Field Name</Name>
<Value>Field Value</Value>
</Root>
<Root>
<Name>Field Name</Name>
<Value>Field Value</Value>
</Root>
</Roots>
I want to use T-SQL to get the Value where Name = Total. I have tried the following but it isn't returning any data:
SELECT [XML]
FROM [BusinessAccount]
WHERE [XML].value('(/Root/Name)[13]', 'VARCHAR(MAX)') LIKE '%Total%'
If anyone could tell me where I've gone wrong?
You are missing the required WITH XMLNAMESPACES for your XML and the path is incorrect.
If you want to bring back rows where the 13th element consists of the text Total you can use the below.
WITH XMLNAMESPACES (DEFAULT 'http://test/data')
SELECT [XML]
FROM [BusinessAccount]
WHERE 1 = [XML].exist('(/Item/Roots/Root/Name)[13][text() = "Total"]')
Otherwise you can add the WITH XMLNAMESPACES to your original query and fix the path there too.
You need to specify namespaces. You can then match <Name> and <Value> pairs and extract the contents of <Value> like so:
SELECT NameNode.value('declare namespace x="http://test/data"; (../x:Value)[1]', 'varchar(100)')
FROM [BusinessAccount]
CROSS APPLY [XML].nodes('declare namespace x="http://test/data"; //x:Root/x:Name') AS n(NameNode)
WHERE NameNode.value('.', 'varchar(100)') = 'Total'
Demo on db<>fiddle

for xml explicit dealing with namespace

I'm trying to query explicit xml. For that purposes I have to use namespace. Here is a simplified version:
SELECT 1 as Tag,
NULL as Parent,
i.SSN as [SSN!1]
FROM IRS_Table as i
for xml explicit
But I want xml to look like this:
<irs:SSN>999-99-9999</irs:SSN>
Here is declaration of namespace in the beginning tags:
xmlns:irs="urn:us:gov:treasury:irs:common"
How to revise script so that to see in output namespace?
EDIT
<?xml version="1.0" encoding="utf-8"?>
<n1:Form109495CTransmittalUpstream xmlns:irs="urn:us:gov:treasury:irs:common">
<irs:SSN>999-99-9999</irs:SSN>
</n1:Form109495CTransmittalUpstream>
You can use following query to include namespace into for xml explicitoutput.
SELECT 1 as Tag,
NULL as Parent,
i.SSN as [irs:SSN!1],
'urn:us:gov:treasury:irs:common' as [irs:SSN!1!xmlns:irs] --namespace is here
FROM IRS_Table as i
for xml explicit
Update
Probably easier is to use for xml path construction.
;with xmlnamespaces('urn:us:gov:treasury:irs:common' as irs,
'blahblah' as n1,
default 'blahblah:blah' )
SELECT
null as [n1:Form109495CTransmittalUpstream],
ssn as [n1:Form109495CTransmittalUpstream/irs:SSN]
FROM IRS_Table as i
for xml path(''), elements, root

SQL Parse an xml string

I have a bunch of XMLs that I need to parse with SQL.
The XML can take on multiple forms:
<Grandparent>
<parent>
<child1>something</child1>
<child2>something</child2>
</parent>
</Grandparent>
or
<Grandparent>
<child1>something</child1>
<child2>something</child2>
</Grandparent>
Additionally, the number of "child" nodes is variable and there is no way of knowing before hand how many children there are.
What i have done so far is:
#xml.nodes('/Grandparent')
which returns either the <parent> node and children or simply the child nodes depending on the format of the xml.
The version of SQL and the fact that i'm writing it as an SQL function seems to mean that trying to get valueas shown in this anwser does not work.
Therefore, I decided to parse the string. Essentially, I look for < and take the substring from there until > for the node name. Then I take anything between > and </ for the value. I do so in a while loop until the xml string is finished. It works perfectly unless the xml has that parent node.
I don't know how to determine whether that parent node is there and how to ignore it if it is. This is where I am stuck.
What I want to get in either case is:
Node | Value
child1 | something
child2 | something
etc for as many child nodes that there is.
You can use descendant axis // to get child nodes at any level depth within a parent node.
Another useful xpath syntax for this task is local-name() which return current context node/attribute's name without namespace :
select c.value('local-name(.)', 'varchar(max)') as 'node'
, c.value('.', 'varchar(max)') as 'value'
from #xml.nodes('/Grandparent//*[not(*)]') as T(c)
This xpath bit //*[not(*)] means select descendant nodes that doesn't have child node, in other words select the inner most descendant.
SQL Fiddle
Going out on a limb here with two assumptions; your question isn't clear about the following:
I'm assuming that your child nodes have the same name (e.g., child, not child1 and child2), and
You want a SQL statement that returns 1 child per row.
If either of those assumptions is incorrect, this answer won't help :)
DECLARE #xml XML = '<Grandparent>
<parent>
<child>something</child>
<child>something</child>
</parent>
</Grandparent>'
SELECT x.value('.[1]', 'varchar(100)')
FROM #xml.nodes('/Grandparent//child') t(x)
SET #xml= '<Grandparent>
<child>something</child>
<child>something</child>
</Grandparent>'
SELECT x.value('.[1]', 'varchar(100)')
FROM #xml.nodes('/Grandparent//child') t(x)
Try:
DECLARE #xml xml = N'
<Grandparent>
<parent>
<child1>something</child1>
<child2>something</child2>
</parent>
</Grandparent>';
SELECT
child.value('fn:local-name(.)', 'varchar(100)') AS Node
,child.value('.', 'varchar(100)') AS value
FROM #xml.nodes('//*[self::child1 or self::child2]') AS ansestor(child);
SET #xml = N'
<Grandparent>
<child1>something</child1>
<child2>something</child2>
</Grandparent>';
SELECT
child.value('fn:local-name(.)', 'varchar(100)') AS Node
,child.value('.', 'varchar(100)') AS value
FROM #xml.nodes('//*[self::child1 or self::child2]') AS ansestor(child);

How to add attributes to xml nodes in sql server 2005

If i wanted to add an attribute to the root element record, can i do this from the sql side?
SELECT top 1 'text' as nodeA
from test as z
FOR XML AUTO, ELEMENTS, root('record')
i would like to produce the xml like this:
<Root attribute="value">
<z>
<NodeA>text</NodeA>
</z>
</Root>
Use the new FOR XML PATH syntax:
SELECT TOP 1
'someValue' AS '#Attribute',
'text' as 'z/NodeA'
FROM dbo.Test
WHERE....
FOR XML PATH('YourElement'), ROOT('Root')
This would give something like
<Root>
<YourElement Attribute="someValue">
<z>
<NodeA>text</NodeA>
</z>
</YourElement>
</Root>
Read more about it here:
Simple Example of Creating XML File Using T-SQL
Using XML Serialization with SQL's FOR XML PATH
Your example is not doing what is requested.
request:
<Root attribute="someValue">
<YourElement>
<z>
<NodeA>text</NodeA>
</z>
</YourElement>
</Root>
your answer:
<Root>
<YourElement Attribute="someValue">
<z>
<NodeA>text</NodeA>
</z>
</YourElement>
</Root>
I'm doing something similar and using PowerShell to scrub the file before saving it:
scrub reason 1: https://connect.microsoft.com/SQLServer/feedback/details/265956/suppress-namespace-attributes-in-nested-select-for-xml-statements
scrub reason 2: THIS
SELECT
'someValue' AS '#Attribute',
(SELECT TOP 1
'text' as 'z/NodeA'
FROM dbo.Test
WHERE....
FOR XML PATH('YourElement')
)
FOR XML PATH('ROOT');
It should create a xml with ROOT containg attribute and list of ... inside.

How do I select a top-level attribute of an XML column in SQL Server?

I have an XML column in SQL Server that is the equivalent of:
<Test foo="bar">
<Otherstuff baz="belch" />
</Test>
I want to get the value of the foo attribute of Test (the root element) as a varchar. My goal would be something along the lines of:
SELECT CAST('<Test foo="bar"><Otherstuff baz="belch" /></Test>' AS xml).value('#foo', 'varchar(20)') AS Foo
But when I run the above query, I get the following error:
Msg 2390, Level 16, State 1, Line 1
XQuery [value()]: Top-level attribute
nodes are not supported
John Saunders has it almost right :-)
declare #Data XML
set #Data = '<Test foo="bar"><Otherstuff baz="belch" /></Test>'
select #Data.value('(/Test/#foo)[1]','varchar(20)') as Foo
This works for me (SQL Server 2005 and 2008)
Marc
If you dont know the root element:
select #Data.value('(/*/#foo)[1]','varchar(20)') as Foo
Why does .value('#foo', 'varchar(20)') generate the error “Top-level attribute nodes are not supported”?
When you query the xml data type, the context is the document node, which is an implicit node that contains the root element(s) of your XML document. The document node has no name and no attributes.
How can I get the value of an attribute on the root element?
In your XQuery expression, include the path to the first root element:
DECLARE #Data xml = '<Customer ID="123"><Order ID="ABC" /></Customer>'
SELECT #Data.value('Customer[1]/#ID', 'varchar(20)')
-- Result: 123
If you don’t know (or don’t want to specify) the name of the root element, then just use * to match any element:
SELECT #Data.value('*[1]/#ID', 'varchar(20)')
-- Result: 123
Because the query context is the document node, you don’t need to prefix the XQuery expression with a forward slash (as the other answers unnecessarily do).
Why do I have to include [1]?
The XQuery expression you pass to value() must be guaranteed to return a singleton. The expression Customer/#ID doesn’t satisfy this requirement because it matches both ID="123" and ID="456" in the following example:
DECLARE #Data xml = '<Customer ID="123" /><Customer ID="456" />'
Remember that the xml data type represents an XML document fragment, not an XML document, so it can contain multiple root elements.
What’s the difference between Customer[1]/#ID and (Customer/#ID)[1]?
The expression Customer[1]/#ID retrieves the ID attribute of the first <Customer> element.
The expression (Customer/#ID)[1] retrieves the ID attribute of all <Customer> elements, and from this list of attributes, picks the first.
The following example demonstrates the difference:
DECLARE #Data xml = '<Customer /><Customer ID="123" /><Customer ID="456" />'
SELECT #Data.value('Customer[1]/#ID', 'varchar(20)')
-- Result: NULL (because the first Customer element doesn't have an ID attribute)
SELECT #Data.value('(Customer/#ID)[1]', 'varchar(20)')
-- Result: 123