Limit xml-namespaces to only the main root - sql-server

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...

Related

Is that valid XML and how to replicate with SQL Server

I do have to replicate an XML file with SQL Server and I am now stumbling over the following structure inside the XML file and I don't know how to replicate that.
The structure looks like this at the moment for certain tags:
<ART_TAG1>
<UNMLIMITED/>
</ART_TAG1>
<ART_TAG2>
<ART_TAG3>
<Data_Entry/>
</ART_TAG3>
</ART_TAG2>
I am wondering if this is proper XML that the data inside (unlimited and Data_Entry) is enclosed with a closing XML tag. The XML validator https://www.w3schools.com/xml/xml_validator.asp is telling me this is correct. But now I am struggling with replicating that with Transact-SQL.
If I try to replicate that I can only come up with the following TSQL script, which obviously does not fully look like the original.
SELECT 'UNLIMITED' as 'ART_TAG1'
, 'Data_Entry' as 'ART_TAG2/ART_TAG3'
FOR XML PATH(''), ROOT('root')
<root>
<ART_TAG1>UNLIMITED</ART_TAG1>
<ART_TAG2>
<ART_TAG3>Data_Entry</ART_TAG3>
</ART_TAG2>
</root>
If I get this correctly, your question is:
How can I put my query to create those <SomeElement /> tags?
Look at this:
--This will create filled nodes
SELECT 'outer' AS [OuterNode/#attr]
,'inner' AS [OuterNode/InnerNode]
FOR XML PATH('row');
--The empty string is some kind of content
SELECT 'outer' AS [OuterNode/#attr]
,'' AS [OuterNode/InnerNode]
FOR XML PATH('row');
--the missing value (NULL) is omited by default
SELECT 'outer' AS [OuterNode/#attr]
,NULL AS [OuterNode/InnerNode]
FOR XML PATH('row');
--Now check what happens here:
--First XML has an empty element, while the second uses the self-closing element
DECLARE #xml1 XML=
N'<row>
<OuterNode attr="outer">
<InnerNode></InnerNode>
</OuterNode>
</row>';
DECLARE #xml2 XML=
N'<row>
<OuterNode attr="outer">
<InnerNode/>
</OuterNode>
</row>';
SELECT #xml1,#xml2;
The result is the same for both...
Some background: Semantically the empty element <element></element> is exactly the same as the self-closing element <element />. It should not make any difference, whether you use the one or the other. If your consumer cannot deal with this, it is a problem in the reading part.
Yes, you can force any content into XML on string level, but - as the example shows above - this is just a (dangerous) hack.
XML within T-SQL returns - by default - a missing node as NULL and an empty element as empty (depending on the datatype, and beware of the difference between an element and its text() node).
In short: This is nothing you should have to think about...

Add Extra information to query output in SQL Server

I have this query in SQL Server that generates XML, and I would like to add a couple of details.
This is what I have:
<FE>
<id>1</id>
<pass>0</pass>
<CONSECUTIVE>0</CONSECUTIVE>
<DetailLine>
<Article>Book<Article/>
<Currency>USD</Currency>
<Price>10</Price>
<Total>10</Total>
</DetailLine>
....
</FE>
and I would like it to generate this output
<?xml version="1.0" encoding="utf-8"?> <- CHANGE 1
<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>
<DetailLine>
<Article>Book<Article/>
<Currency>USD</Currency>
<Price>10</Price>
<Total>10</Total>
</DetailLine>
</EndLine> <--add this CHANGE 3
....
</FE>
Your question is not all clear... Especially change 3 is wrong actually...
But one after the other:
change 1: This is not supported on XML level!
Any XML-declaration <?xml blah ?> will be omited as XML within SQL-Server is UTF-16 in any case (to be exact: UCS-2)
Real UTF-8 is not support by SQL-Server at all! Many people think, that the VARCHAR type is UTF-8, but this is wrong. It is extended ASCII.
You can concatenated strings. Thus you can convert the XML to a string and append any other string you like, but this is - probably - no longer well-formed XML...
change 2: This is easy, read about WITH XMLNAMESPACES
Try this
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]
,0 AS [CONSECUTIVE]
FOR XML PATH('FE');
change 3: No idea what you need. Your example is showing a closing </newLine>, but this does not help to understand your issue. Anyway, without the opening tag this is wrong...

How to add multiple namespaces in sqlserver query to create XML

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>

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