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)')
Related
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...
I'm querying a table T which has a string column StrXML that has XML text stored in it. Here's an example of the XML stored:
<Sequence mc:Ignorable="sap sads" DisplayName="Post Processing"
sap:VirtualizedContainerService.HintSize="424,318"
mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces"
xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006
xmlns:mee="clr-namespace:MatX.eRP.Entities;assembly=eRP.Entities"
xmlns:mepa="clr-namespace:MatX.eRP.PostProcessing.Activities;assembly=PostProcessing.Activities"
xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities"
xmlns:sads="http://schemas.microsoft.com/netfx/2010/xaml/activities/debugger"
xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation"
xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<sap:WorkflowViewStateService.ViewState>
<scg:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<mepa:BasicOperation Description="Traitement Thermique" DisplayName="HeatTreatment" Guid="82800b59-e181-4a93-b483-7e2cd9b14827" sap:VirtualizedContainerService.HintSize="402,154" Scope="Build">
<mepa:BasicOperation.MeasurementDescriptions>
<scg:List x:TypeArguments="mee:MeasurementDescription" Capacity="0" />
</mepa:BasicOperation.MeasurementDescriptions>
</mepa:BasicOperation>
<mepa:BasicOperation Description="Finition manuelle" DisplayName="Manual Finishing" Guid="cd64be75-6968-47fe-8aac-93a4fdf37892">
<mepa:BasicOperation.MeasurementDescriptions>
<scg:List x:TypeArguments="mee:MeasurementDescription" Capacity="4">
<mee:MeasurementDescription Max="{x:Null}" Min="{x:Null}" Guid="7c1a37f1-f39d-4ed3-8048-6b0a266c70b9" IsRequired="False" Name="MesureMF1" Type="Double" />
<mee:MeasurementDescription Max="{x:Null}" Min="{x:Null}" Guid="a21b0c0d-dfff-4237-9975-4179bcefe7c2" IsRequired="False" Name="MesureMF2" Type="Double" />
</scg:List>
</mepa:BasicOperation.MeasurementDescriptions>
</mepa:BasicOperation>
</Sequence>
In my select request on table T, I want to only show the Description value for which the Guid="82800b59-e181-4a93-b483-7e2cd9b14827".
How can I do that?
In a comment I mentioned already, that one of your namespaces is missing the final ". This is a big problem, if it's not just a copy-and-paste issue... (not well formed)
XML should not be stored in a string column (slow and dangerous!). If you database does not support XML natively the XML should at least be checked.
You did not mention the actual RDBMS, but the XQuery-principles should be the same (however your RDBMS deals with XQuery actually).
The simple approach is this XQuery (fetch any <BasicOperation>, wherever it is placed, and filter for the given GUID)
//*:BasicOperation[#Guid="82800b59-e181-4a93-b483-7e2cd9b14827"]/#Description
With SQL-Server you can try this
SELECT CAST(T.StrXML AS XML).value(N'(//*:BasicOperation[#Guid="82800b59-e181-4a93-b483-7e2cd9b14827"]/#Description)[1]',N'nvarchar(max)')
The more specific (and recommended) approach is this:
declare namespace dflt="http://schemas.microsoft.com/netfx/2009/xaml/activities";
declare namespace mepa="clr-namespace:MatX.eRP.PostProcessing.Activities;assembly=PostProcessing.Activities";
dflt:Sequence/mepa:BasicOperation[#Guid="82800b59-e181-4a93-b483-7e2cd9b14827"]/#Description
Again - with SQL-Server - you might try this:
SELECT CAST(T.StrXML AS XML).value(N'declare namespace dflt="http://schemas.microsoft.com/netfx/2009/xaml/activities";
declare namespace mepa="clr-namespace:MatX.eRP.PostProcessing.Activities;assembly=PostProcessing.Activities";
(dflt:Sequence/mepa:BasicOperation[#Guid="82800b59-e181-4a93-b483-7e2cd9b14827"]/#Description)[1]',N'nvarchar(max)')
If the GUID-value is variable SQL-Server would allow you to pass the value in from a variable declared outside. Read about sql:variable() and sql:column().
UPDATE
You can use lower-case() to get a secure comparison:
DECLARE #xml XML=
'<root>
<a guid="82800b59-e181-4a93-b483-7e2cd9b14827" />
<a guid="82800B59-E181-4A93-B483-7E2CD9B14827" />
</root>';
DECLARE #guid UNIQUEIDENTIFIER='82800B59-E181-4A93-B483-7E2CD9B14827';
SELECT #xml.query(N'/root/a[lower-case(#guid)=lower-case(sql:variable("#guid"))]')
Try something like this, assuming this is for SQL Server:
;WITH XMLNAMESPACES(DEFAULT 'http://schemas.microsoft.com/netfx/2009/xaml/activities',
'clr-namespace:MatX.eRP.PostProcessing.Activities;assembly=PostProcessing.Activities' AS mepa)
SELECT
T.X.value('#Description', 'varchar(100)') AS JobTitle
FROM
#XTable
CROSS APPLY
XmlData.nodes('/Sequence/mepa:BasicOperation') AS T(X)
WHERE
T.X.value('#Guid','varchar(50)') = '82800b59-e181-4a93-b483-7e2cd9b14827'
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...
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!
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: