How to add multiple namespaces in sqlserver query to create XML - sql-server

I try to create a XML with sqlserver that must contain multiple namespaces. The XML should look something like this:
<ns1:Message xmlns:ns1="http://Something/A"
xmlns:ns2="http://Something/B"
xmlns:ns3="http://Something/C">
<ns1:A>
<ns1:A1>201608111003201</ns1:A1>
<ns1:A2>Some text</ns1:A2>
<ns1:A3>More text</ns1:A3>
</ns1:A>
<ns2:B>
<ns2:B1>123456788</ns2:B1>
<ns2:B2>Even more text</B2>
</ns2:B>
<ns3:C>
<ns3:C1>E232323</ns3:C1>
<ns3:C2>P</ns3:C2>
</ns3:C>
</ns1:Message
My query does look something like this now with only one namespace.
WITH XMLNAMESPACES ('http://Something/A' as ns3,
'http://Something/B' as ns2,
'http://Something/C' as ns1)
SELECT COLUMN1 as 'ns1:A1',
COLUMN2 as 'ns1:A2',
COLUMN3 as 'ns1:A3'
FROM MYTABLE
FOR XML PATH ('ns1:A'), ROOT('ns1:Message'), ELEMENTS
This query works fine, but when I try to add the ns2 or ns3 namespace in the query nothing seems to work. How must this be done.
Thanks in advance!

I do not quite understand, what you try to achieve...
This works:
WITH XMLNAMESPACES ('http://Something/A' as ns3,
'http://Something/B' as ns2,
'http://Something/C' as ns1)
SELECT COLUMN1 as 'ns1:A1',
COLUMN2 as 'ns2:A1',
COLUMN3 as 'ns3:A1'
FROM MYTABLE
FOR XML PATH ('ns1:A'), ROOT('ns1:Message'), ELEMENTS
The element name "A1" will be there multiple times, but - due to the namespace - it is handled as different elements. That is the main purpose of a namespace.
In most cases there is a default namespace xmlns="SomeURL" and sub-namespaces like xmlns:sub1="SomeSubURL". Elements without a specific namespace belong to the default namespace, other elements would start with sub1:SomeName and therefore belong to the sub-namespace. But there's no need to define a default namespace.
I think you've got a misconception what a namespace is meant to be. Your example would not need a namespace... You are using nestings to group your data...
The following code would produce exactly the XML you want to reach, but this design seems over complicated... Maybe you have a good reason for this.
WITH XMLNAMESPACES ('http://Something/C' as ns1
,'http://Something/A' as ns2
,'http://Something/B' as ns3)
SELECT 201608111003201 AS [ns1:A/ns1:A1]
,'Some text' AS [ns1:A/ns1:A2]
,'More text' AS [ns1:A/ns1:A3]
,123456788 AS [ns2:B/ns2:B1]
,'Even more text' AS [ns2:B/ns2:B2]
,'E232323' AS [ns3:C/ns3:C1]
,'P' AS [ns3:C/ns3:C2]
FOR XML PATH (''), ROOT('ns1:Message'), ELEMENTS
The result
<ns1:Message xmlns:ns3="http://Something/B" xmlns:ns2="http://Something/A" xmlns:ns1="http://Something/C">
<ns1:A>
<ns1:A1>201608111003201</ns1:A1>
<ns1:A2>Some text</ns1:A2>
<ns1:A3>More text</ns1:A3>
</ns1:A>
<ns2:B>
<ns2:B1>123456788</ns2:B1>
<ns2:B2>Even more text</ns2:B2>
</ns2:B>
<ns3:C>
<ns3:C1>E232323</ns3:C1>
<ns3:C2>P</ns3:C2>
</ns3:C>
</ns1:Message>

Related

Limit xml-namespaces to only the main root

I have this query
WITH XMLNAMESPACES(DEFAULT 'https://tribunet.hacienda.go.cr /docs/esquemas/2017/v4.2/facturaElectronica'
,'http://www.w3.org/2001/XMLSchema' AS xsd
,'http://www.w3.org/2001/XMLSchema-instance' AS xsi)
SELECT 1 AS [id]
,0 AS [pass]
(
/*Others*/
SELECT
OT.OTH_MESSAGE as Others
FROM [crdx_COREDev1].[dbo].[OTH_OTHERS] as OT
where
OT.OTH_ID=E.OTH_ID
fOR XML PATH ('Others'), type
)
,0 AS [CONSECUTIVE]
FOR XML PATH('FE');
This generates this XML
<FE xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2 /facturaElectronica"> <- CHANGE 2
<id>1</id>
<pass>0</pass>
<CONSECUTIVE>0</CONSECUTIVE>
<Others xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2 /facturaElectronica">
<MESSAGE>MESSAGE</MESSAGE>
</Others>
</FE>
Now my question: I would like only <FE> to show the namespaces, but - as you see in the xml - that declarations appear also in <Others>. How can I limit this to <FE>?
This is an annoying and well known issue and occurs whenever you use namespaces in connection with nested sub-queries in FOR XML queries...
There has been a connect issue for more than 10 years - until it disappaered recently.
It is important to mention, that these repeated namespace declarations are not wrong, just bloating your XML. And it can collide with (to) strict schema validations.
No good solution, just workarounds:
Create the inner XML without the namespace and add the wrapping node on string base, or
Create the namespaces as normal attributes (but not named xmlns) and use REPLACE to change the names.
Both workarounds need a conversion to NVARCHAR(MAX) and back to XML.
I really have no idea, why this was implemented this way...
Find some related examples
here
and here
and here
and here
Attention:
xmlns="https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2 /facturaElectronica">
You are using namespace URLs with blanks. This is not allowed...

Select XML multiple only a few nodes with the same name

I'm trying to construct a soap message, and I was able to construct the entire message using a single select. Except the problem is, on only a few occasions the same node name is repeated twice.
So for example the required output result should be like so, with two separate id root nodes:
<SoapDocument>
<recordTarget>
<patientRole>
<id root="1.2.3.4" extension="1234567" />
<id root="1.2.3.5.6" extension="0123456789" />
</patientRole>
</recordTarget>
</SoapDocument>
I tried to use my sparse knowledge of xpath to construct the node names like so:
select
'1.2.3.4' AS 'recordTarget/patientRole/id[1]/#root',
'1234567' AS 'recordTarget/patientRole/id[1]/#extension',
'1.2.3.5.6' AS 'recordTarget/patientRole/id[2]/#root',
'0123456789' AS 'recordTarget/patientRole/id[2]/#extension'
FOR XML PATH('SoapDocument'),TYPE
Apparently xpath naming can't be applied to column names id[1] and id[2] like that? Am I missing something here or should the notation be different? What would be the easiest way to constuct the desired result?
From your question I assume, this is not tabular data, but fixed values and you are creating a medical document, assumably a CDA.
Try this:
SELECT
(
SELECT
'1.2.3.4' AS 'id/#root',
'1234567' AS 'id/#extension',
'',
'1.2.3.5.6' AS 'id/#root',
'0123456789' AS 'id/#extension'
FOR XML PATH('patientRole'),TYPE
) AS [SoapDocument/recordTarget]
FOR XML PATH('')
The result:
<SoapDocument>
<recordTarget>
<patientRole>
<id root="1.2.3.4" extension="1234567" />
<id root="1.2.3.5.6" extension="0123456789" />
</patientRole>
</recordTarget>
</SoapDocument>
Some explanation: The empty element in the middle allows you to place two elements with the same name in one query. There are various approaches how you get this into your surrounding tags. This is just one possibility.
UPDATE
I'd like to point to BdR's own answer! Great finding and worth an up-vote!
A little more elaboration on the answer from Shnugo, as it got me trying out some things using an "empty column".
If you do not give the emtpy column a name, it will reset to the XML root node. So the following columns will start from the XML root of the selection you are in at that point. However, if you explicitly name the empty separator column, then the following columns will continue in the hierarchy as set by that column name.
So the selection below will also result in the desired result. It's subtly different, but in my case it allows me to avoid using subselections.
select
'1.2.3.4' AS 'recordTarget/patientRole/id/#root',
'1234567' AS 'recordTarget/patientRole/id/#extension',
'' AS 'recordTarget/patientRole',
'1.2.3.5.6' AS 'recordTarget/patientRole/id/#root',
'0123456789' AS 'recordTarget/patientRole/id/#extension'
FOR XML PATH('SoapDocument'),TYPE
This should do the job:
WITH CTE AS (
SELECT *
FROM (VALUES('1.2.3.4','1234567'),
('1.2.3.5.6','0123456789')) V ([root], [extension]))
SELECT (SELECT (SELECT (SELECT [root] AS [#root],
[extension] AS [#extension]
FROM CTE
FOR XML PATH('id'), TYPE)
FOR XML PATH('patientRole'), TYPE)
FOR XML PATH ('recordTarget'), TYPE)
FOR XML PATH ('SoapDocument');

XML attribute not removing using XML.modify [duplicate]

I have a table called XML (in SQL Server 2008) and it has a field called XmlDocument of type XML. I am trying to to delete an attribute from an XML variable.
Here is how my xml looks like
<clue_personal_auto xmlns="http://cp.com/rules/client">
<admin>
<receipt_date>03/16/2011</receipt_date>
<date_request_ordered>03/16/2011</date_request_ordered>
<report_usage>Personal</report_usage>
</admin>
</clue_personal_auto>
My query
UPDATE XML
SET XmlDocument.modify('delete (/clue_personal_auto/#xmlns)[1]')
WHERE xmlid = 357
When I run this query in query analyzer I see the message "1 row(s) affected" but in reality the xmlns attribute of clue_personal_auto element is not being removed. Any idea what am I doing wrong.
Thanks
BB
You need to use WITH xmlnamespaces, otherwise "/clue_personal_auto" does not match the NAMESPACED clue_personal_auto xmlns="..." node.
Not only that, you cannot actually remove a namespace since it is not a normal attribute.
Example of removing a regular attribute
declare #xml table (xmlid int, xmldocument xml)
insert #xml select 357, '
<clue_personal_auto xmlns="http://cp.com/rules/client" otherattrib="x">
<admin>
<receipt_date>03/16/2011</receipt_date>
<date_request_ordered>03/16/2011</date_request_ordered>
<report_usage>Personal</report_usage>
</admin>
</clue_personal_auto>'
;WITH XMLNAMESPACES ('http://cp.com/rules/client' as ns)
UPDATE #XML
SET XmlDocument.modify('delete (/ns:clue_personal_auto/#otherattrib)[1]')
WHERE xmlid = 357
select * from #xml
UPDATE XML
SET CONVERT(XML, REPLACE(CONVERT(NVARCHAR(MAX), XmlDocument), N' xmlns=...'))
WHERE ID = 357
I can't seem to find an easy way to do this - but the real question remains: why do you want to remove the namespace?? Using the WITH XMLNAMESPACES ... construct, you can easily make use of the namespaces.
Instead of putting a lot of effort in getting rid of it - learn about XML namespaces and start using them!
You can quite easily use that XML namespace in your queries:
;WITH XMLNAMESPACES (DEFAULT 'http://cp.com/rules/client' )
SELECT
XmlDocument.value('(/clue_personal_auto/admin/report_usage)[1]', 'varchar(25)')
FROM XML
WHERE ID = 357
and be happy with it - no need to artificially remove xmlns= declarations anymore!

SQL Server Xml query with multiple namespaces

I have a table in SQL server that contains an Xml column and I am having trouble querying it. I don't know enough about XPath to determine if my query is wrong, or if it is because of what seems like conflicting namespaces. Here is an example xml:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<!-- snip -->
</s:Header>
<s:Body>
<FetchRequest xmlns="http://www.foobar.org/my/schema">
<Contract xmlns:a="http://www.foobar.org/2014/04/datacontracts"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:RequestedBy>John Doe</a:RequestedBy>
<a:TransactionId>ABC20140402000201</a:TransactionId>
</Contract>
</FetchRequest>
</s:Body>
</s:Envelope>
I want to retrieve TransactionId from the xml. The query I tried was this:
SELECT TOP 100
MessageXml,
MessageXml.value('
declare namespace s="http://www.w3.org/2003/05/soap-envelope";
declare namespace a="http://www.w3.org/2005/08/addressing";
(/s:Envelope/s:Body/FetchRequest/Contract/a:TransactionId)[1]', 'varchar(max)')
FROM dbo.Message
I am getting back NULL for my MessageXml.value. If I remove everything after s:Body I seem to get a bunch of text that is concatenated, but as soon as I add FetchRequest I get NULL back in my results.
I did notice that the Contract element defines a namespace of a, and the Envelope also defines a namespace of a, but I wasn't sure if that is a problem or not.
How can I retrieve TransactionId using an XPath query given the above xml example?
I know that answer is accepted, but there is actually simplier way of doing it, if the only thing you need to do is select node value. Just use * as namespace name:
SELECT MessageXml
, MessageXml.value('(/*:Envelope/*:Body/*:FetchRequest/*:Contract/*:TransactionId)[1]'
, 'varchar(max)')
FROM dbo.Message
You have two problems :
you're not respecting the implicit default XML namespace on the <FetchRequest> node
the XML namespace with the a: prefix is first defined on the <s:Envelope> node, and is being re-declared on the <Contract> node (really really bad practice in my opinion) and you need to use the second declaration for anything below the <Contract> node.
So you need something like this (I prefer to define the XML namespaces upfront, in a WITH XMLNAMESPACES() statement):
;WITH XMLNAMESPACES('http://www.w3.org/2003/05/soap-envelope' AS s,
'http://www.foobar.org/2014/04/datacontracts' AS a,
'http://www.foobar.org/my/schema' AS fb)
SELECT
MessageXml,
MessageXml.value('(/s:Envelope/s:Body/fb:FetchRequest/fb:Contract/a:TransactionId)[1]', 'varchar(max)')
FROM
dbo.Message
This will output the whole query and the value ABC20140402000201 for your second column.
FetchRequest and Contract are in namespace http://www.foobar.org/my/schema and alias a is redefined in the document. You need:
SELECT TOP 100
MessageXml,
MessageXml.value('declare namespace s="http://www.w3.org/2003/05/soap-envelope";
declare namespace a="http://www.w3.org/2005/08/addressing";
declare namespace f="http://www.foobar.org/my/schema";
declare namespace x="http://www.foobar.org/2014/04/datacontracts";
(/s:Envelope/s:Body/f:FetchRequest/f:Contract/x:TransactionId)[1]', 'varchar(max)')
FROM dbo.Message;
Fiddle here:

SQL Server 2005 Xquery namespaces

I'm trying to get some values out of an Xml Datatype. The data looks like:
<Individual xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FirstName xmlns="http://nswcc.org.au/BusinessEntities.Crm">Lirria</FirstName>
<LastName xmlns="http://nswcc.org.au/BusinessEntities.Crm">Latimore</LastName>
</Indvidual>
Note the presence of the xmlns in the elements FirstName and LastName - this is added when we create the xml by serializing a c# business object. Anyway it seems that the presence of this namespace in the elements is causing XQuery expressions to fail, such as:
SELECT MyTable.value('(//Individual/LastName)[1]','nvarchar(100)') AS FirstName
This returns null. But when I strip out the namespace from the elements in the xml (e.g. using a Replace T-SQL statement), the above returns a value. However there must be a better way - is there a way of making this query work i.e. without updating the xml first?
Thanks
John Davies
You need to properly name the element you want to select. See Adding Namespaces Using WITH XMLNAMESPACES. Here is an example using your XML:
declare #x xml;
set #x = N'<Individual
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FirstName xmlns="http://nswcc.org.au/BusinessEntities.Crm">Lirria</FirstName>
<LastName xmlns="http://nswcc.org.au/BusinessEntities.Crm">Latimore</LastName>
</Individual>';
with xmlnamespaces (N'http://nswcc.org.au/BusinessEntities.Crm' as crm)
select #x.value(N'(//Individual/crm:LastName)[1]',N'nvarchar(100)') AS FirstName
The * wildcard will also allow you to select the element without enforcing the explicit namespace. Remus' answer is the way to go, but this may assist others having namespace issues:
select #x.value(N'(//Individual/*:LastName)[1]',N'nvarchar(100)')

Resources