I have a table called Content which has a varchar(max) column called data.
The data in this column is in XML format, and I want to query an attribute in the XML.
Below is the top couple of lines of the XML, it is the p3:manifestId I want to query so my output would basically be Some_Data (I've obviously desensitized this).
Is this possible?
<manifest xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p3="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.w3.org/2001/XMLSchema-instance"
p3:manifestDateTime="2016-02-17T17:34:29.5925096Z"
p3:manifestVersion="1.1" p3:manifestId="Some_Data">
<p3:productList>
<p3:product p3:releaseDateTime="2016-02-17T17:34:29.5925096Z" p3:installSeqId="2" p3:uninstallSeqId="2" p3:releaseNum="1" p3:productType="doc" p3:productId="WEDREZ020RRRP0GGG001" p3:mfgCode="GIRE">
The only thing which might be complicated here, are the namespaces. How is this XML generated? It is quite unusual to see the same namespace (http://www.w3.org/2001/XMLSchema-instance) as default and with two different aliases...
DECLARE #mockup TABLE(ID INT IDENTITY, YourXML VARCHAR(MAX));
INSERT INTO #mockup VALUES
('<manifest xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p3="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.w3.org/2001/XMLSchema-instance"
p3:manifestDateTime="2016-02-17T17:34:29.5925096Z"
p3:manifestVersion="1.1" p3:manifestId="Some_Data">
<!--Some more stuff-->
</manifest>');
--This approach does not look at namespaces at all. I use the wildcard *:
SELECT CAST(YourXML AS XML).value(N'(/*:manifest/#*:manifestId)[1]',N'nvarchar(max)')
FROM #mockup;
--This approach declares all needed namespaces in advance (best approach in most cases):
WITH XMLNAMESPACES(DEFAULT 'http://www.w3.org/2001/XMLSchema-instance'
,'http://www.w3.org/2001/XMLSchema-instance' AS p3)
SELECT CAST(YourXML AS XML).value(N'(/manifest/#p3:manifestId)[1]',N'nvarchar(max)')
FROM #mockup;
--This approach is usefull, if you really do not need more than one value. I use one alias ns1 for all different occurances of this namespace:
SELECT CAST(YourXML AS XML).value(N'declare namespace ns1="http://www.w3.org/2001/XMLSchema-instance";
(/ns1:manifest/#ns1:manifestId)[1]',N'nvarchar(max)')
FROM #mockup;
Related
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 column called Resume of type XML stored in a table in MS-SQL Server.
I want to retrieve all the candidates who are from the city Saginaw.
When I use the query
SELECT Resume.query('(: explicit namespace :)declare namespace ns="Namespace-Resume";
//ns:Address/ns:Addr.Location/ns:Location/ns:Loc.City')
FROM JobCandidate
I get all the values for the city which is fine but when I use the following query:
SELECT Resume.query('(: explicit namespace :)declare namespace ns="Namespace-Resume";
//ns:Location[#ns:Loc.City="Saginaw"]/ns:Name')
FROM JobCandidate
I get an error "There is not attribute named Loc.City".
The sample data is as below:
<ns:Resume xmlns:ns="Namespace-Resume">
<ns:Name>
<ns:Name.Prefix></ns:Name.Prefix>
<ns:Name.First>Shai</ns:Name.First>
<ns:Name.Middle></ns:Name.Middle>
<ns:Name.Last>Bassli</ns:Name.Last>
<ns:Name.Suffix></ns:Name.Suffix>
</ns:Name>
<ns:Address>
<ns:Addr.Type>Home</ns:Addr.Type>
<ns:Addr.Street>567 3rd Ave</ns:Addr.Street>
<ns:Addr.Location>
<ns:Location>
<ns:Loc.CountryRegion>US </ns:Loc.CountryRegion>
<ns:Loc.State>MI </ns:Loc.State>
<ns:Loc.City>Saginaw</ns:Loc.City>
</ns:Location>
</ns:Addr.Location>
</ns:Address>
</ns:Resume>
Your question is not all clear... I'll provide several approaches, one of them will hopefully point you the way (You can copy the whole lot into a query window and execute it stand-alone):
DECLARE #xml XML=
N'<ns:Resume xmlns:ns="Namespace-Resume">
<ns:Name>
<ns:Name.Prefix />
<ns:Name.First>Shai</ns:Name.First>
<ns:Name.Middle />
<ns:Name.Last>Bassli</ns:Name.Last>
<ns:Name.Suffix />
</ns:Name>
<ns:Address>
<ns:Addr.Type>Home</ns:Addr.Type>
<ns:Addr.Street>567 3rd Ave</ns:Addr.Street>
<ns:Addr.Location>
<ns:Location>
<ns:Loc.CountryRegion>US </ns:Loc.CountryRegion>
<ns:Loc.State>MI </ns:Loc.State>
<ns:Loc.City>Saginaw</ns:Loc.City>
</ns:Location>
</ns:Addr.Location>
</ns:Address>
</ns:Resume>';
--Read one element's text with namespaces wildcards
SELECT #xml.value(N'(/*:Resume/*:Name/*:Name.First/text())[1]',N'nvarchar(max)');
--Use a default namespace
WITH XMLNAMESPACES(DEFAULT N'Namespace-Resume')
SELECT #xml.value(N'(/Resume/Address/Addr.Location/Location/Loc.City/text())[1]',N'nvarchar(max)');
--Your sample XML includes one person only, but I assume there are more
--Use a predicate to get the name for a given location
DECLARE #location NVARCHAR(100)=N'Saginaw';--Change this for tests
WITH XMLNAMESPACES(DEFAULT N'Namespace-Resume')
SELECT #xml.value(N'(/Resume[(Address/Addr.Location/Location/Loc.City/text())[1]=sql:variable("#location")]/Name/Name.First/text())[1]',N'nvarchar(max)');
--Read several values of this node
--Use a predicate to get the name for a given location
WITH XMLNAMESPACES(DEFAULT N'Namespace-Resume')
SELECT r.value(N'(Name/Name.First/text())[1]',N'nvarchar(max)')
,r.value(N'(Name/Name.Last/text())[1]',N'nvarchar(max)')
FROM #xml.nodes(N'/Resume[(Address/Addr.Location/Location/Loc.City/text())[1]=sql:variable("#location")]') AS A(r);
UPDATE: The SELECT you provide in comment
The call to nodes() is missing? Try it like this:
DECLARE #location NVARCHAR(100)=N'Saginaw';
WITH XMLNAMESPACES(DEFAULT N'Namespace-Resume')
SELECT r.value(N'(Name/Name.First/text())[1]',N'nvarchar(max)')
,r.value(N'(Name/Name.Last/text())[1]',N'nvarchar(max)')
FROM JobCandidate
CROSS APPLY Resume.nodes(N'/Resume[(Address/Addr.Location/Location/Loc.City/text())[1]=sql:variable("#location")]') AS A(r);
UPDATE 2: Namespace according to your comment
DECLARE #xml XML=
N'<ns:Resume xmlns:ns="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume">
<ns:Name>
<ns:Name.Prefix />
<ns:Name.First>Shai</ns:Name.First>
<ns:Name.Middle />
<ns:Name.Last>Bassli</ns:Name.Last>
<ns:Name.Suffix />
</ns:Name>
<ns:Address>
<ns:Addr.Type>Home</ns:Addr.Type>
<ns:Addr.Street>567 3rd Ave</ns:Addr.Street>
<ns:Addr.Location>
<ns:Location>
<ns:Loc.CountryRegion>US </ns:Loc.CountryRegion>
<ns:Loc.State>MI </ns:Loc.State>
<ns:Loc.City>Saginaw</ns:Loc.City>
</ns:Location>
</ns:Addr.Location>
</ns:Address>
</ns:Resume>';
DECLARE #location NVARCHAR(100)=N'Saginaw';--Change this for tests
WITH XMLNAMESPACES(DEFAULT N'http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume')
SELECT r.value(N'(Name/Name.First/text())[1]',N'nvarchar(max)')
,r.value(N'(Name/Name.Last/text())[1]',N'nvarchar(max)')
FROM #xml.nodes(N'/Resume[(Address/Addr.Location/Location/Loc.City/text())[1]=sql:variable("#location")]') AS A(r);
I'm fairly new to querying XML datatypes. We receive XMLs from partners and one such partner sends us XMLs like this:
DECLARE #ResultData XML = '<outGoing xmlns="urn:testsystems-com:HH.2015.Services.Telephony.OutGoing">
<customer>
<ID>158</ID>
</customer>
</outGoing>'
In this example, I would like to pull only the ID out of the XML, but it seems the xmlns is preventing me from getting anything inside the XML:
SELECT cust.value('(ID)[1]', 'VARCHAR(40)') as 'CustomerID'
FROM #ResultData.nodes('/outGoing/customer') as t(cust)
returns NUll, but if I manually remove the XMLNS from the XML I get 158.
I've experimented with WITH XMLNAMESPACES to see if I could use that, but I'm obviously missing something. Since these XMLs will be coming in automatically, I would like to be able to parse the XML, but right now I'm stuck.
That should work:
DECLARE #ResultData XML = '<outGoing xmlns="urn:testsystems-com:HH.2015.Services.Telephony.OutGoing">
<customer>
<ID>158</ID>
</customer>
</outGoing>'
;WITH XMLNAMESPACES(DEFAULT 'urn:testsystems-com:HH.2015.Services.Telephony.OutGoing')
SELECT
#ResultData.value('(/outGoing/customer/ID)[1]', 'int')
or to use your approach:
;WITH XMLNAMESPACES(DEFAULT 'urn:testsystems-com:HH.2015.Services.Telephony.OutGoing')
SELECT
CustomerID = cust.value('(ID)[1]', 'INT')
FROM
#ResultData.nodes('/outGoing/customer') as t(cust)
This will return 158 as its value.
I've used WITH XMLNAMESPACES(DEFAULT .....) since this is the only XML namespace in play, and it's defined at the top-level node - so it applies to every node in the XML structure.
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)')