Parse XML value with Name Space in SQl - sql

I have following XML :
<ProductionSchedule xmlns:inp2="http://www.wbf.org/xml/B2MML-V0401" xmlns="http://www.wbf.org/xml/B2MML-V0401">
<inp2:ProductionRequest>
<inp2:ID>0916A</inp2:ID>
<inp2:Description>SUBH190916A</inp2:Description>
<inp2:Location>
<inp2:EquipmentID>MYEqupiment</inp2:EquipmentID>
</inp2:Location>
<inp2:SegmentRequirement>
<inp2:ID>000</inp2:ID>
<inp2:EarliestStartTime>2015-10-17T12:00:00</inp2:EarliestStartTime>
<inp2:LatestEndTime>2015-10-19T12:00:00</inp2:LatestEndTime>
<inp2:MaterialProducedRequirement>
<inp2:MaterialDefinitionID>GEEC3MA0025EMZI</inp2:MaterialDefinitionID>
<inp2:Quantity>
<inp2:QuantityString>2</inp2:QuantityString>
</inp2:Quantity>
<inp2:MaterialProducedRequirementProperty>
<inp2:ID>ERPWOStatus</inp2:ID>
<inp2:Value>
<inp2:ValueString>Released</inp2:ValueString>
</inp2:Value>
</inp2:MaterialProducedRequirementProperty>
<inp2:MaterialProducedRequirementProperty>
<inp2:ID>ROUTING</inp2:ID>
<inp2:Value>
<inp2:ValueString>SOmeMPRVaue</inp2:ValueString>
</inp2:Value>
</inp2:MaterialProducedRequirementProperty>
<inp2:MaterialProducedRequirementProperty>
<inp2:ID>MPValue2</inp2:ID>
<inp2:Value>
<inp2:ValueString>2016-01-21T12:00:00</inp2:ValueString>
</inp2:Value>
</inp2:MaterialProducedRequirementProperty>
</inp2:MaterialProducedRequirement>
</inp2:SegmentRequirement>
</inp2:ProductionRequest>
</ProductionSchedule>
I am trying to get the value MPValue2 , from the XML.
I tried with following:
Select `#xml.value('(/ProductionSchedule/inp2:ProductionRequest/inp2:SegmentRequirement/inp2:MaterialProducedRequirement/inp2:MaterialProducedRequirementProperty)[1]','nvarchar(255)')`

Your select is OK, but you must consider/declare the namespaces:
WITH XMLNAMESPACES(DEFAULT 'http://www.wbf.org/xml/B2MML-V0401'
,'http://www.wbf.org/xml/B2MML-V0401' AS inp2)
Select #xml.value('(/ProductionSchedule/inp2:ProductionRequest/inp2:SegmentRequirement/inp2:MaterialProducedRequirement/inp2:MaterialProducedRequirementProperty)[1]','nvarchar(255)')
This works too (wildcard) but it's better to be as specific as possible:
Select #xml.value('(/*:ProductionSchedule/*:ProductionRequest/*:SegmentRequirement/*:MaterialProducedRequirement/*:MaterialProducedRequirementProperty)[1]','nvarchar(255)')
The fast and lazy would work too :-) but not fast in terms of performance...
Select #xml.value('(//*:MaterialProducedRequirementProperty)[1]','nvarchar(255)')
UPDATE
This is the query to get all your Properties:
WITH XMLNAMESPACES(DEFAULT 'http://www.wbf.org/xml/B2MML-V0401'
,'http://www.wbf.org/xml/B2MML-V0401' AS inp2)
SELECT prop.value('(inp2:ID)[1]','nvarchar(100)') AS Property
FROM #xml.nodes('/ProductionSchedule/inp2:ProductionRequest/inp2:SegmentRequirement/inp2:MaterialProducedRequirement/inp2:MaterialProducedRequirementProperty') AS A(prop)
The result
Property
--------
ERPWOStatus
ROUTING
MPValue2
UPDATE 2: Use the ID as filter in XQuery
See how I added the filter at the end of the XPath in .nodes().
Nodes will return all sub-elements row-wise. The filter will reduce the resultset to one single row (if inp2:ID is unique!) and then read the Value/ValueString.
I let the namespace declaration for DEFAULT and inp2. But, as #Serf pointed out correctly, both URLs are equal. It would be enough to declare only the DEFAULT and query without any namespace-prefixes...
DECLARE #TheID NVARCHAR(100)='MPValue2';
WITH XMLNAMESPACES(DEFAULT 'http://www.wbf.org/xml/B2MML-V0401'
,'http://www.wbf.org/xml/B2MML-V0401' AS inp2)
SELECT prop.value('(inp2:Value/inp2:ValueString)[1]','nvarchar(100)') AS Property
FROM #xml.nodes('/ProductionSchedule/inp2:ProductionRequest/inp2:SegmentRequirement/inp2:MaterialProducedRequirement/inp2:MaterialProducedRequirementProperty[inp2:ID=sql:variable("#TheID")]') AS A(prop)

Related

Oracle 10.2.0.4.0 query on partial xpath

I need to change the below query to be able to query any kind of tender item.
/Basket/CardTenderItem/Description
/Basket/CashTenderItem/Description
So
/Basket/WildcardTenderItem/Description
I have looked at various examples on but cannot them to bring back any results when running (happily admit to user error if can get working!)
SELECT
RETURN_ID
,SALE_ID,
,extractValue(xmltype(RETURNxml),'/Basket/CashTenderItem/NetValue')
,extractValue(xmltype(RETURNxml),'/Basket/CashTenderItem/Description')
FROM SPR361
WHERE return_id = '9999.0303|20170327224954|2063'
If you only want to match anything the ends with TenderItem, but doesn't have anything after that, you could be specific with substring checks:
SELECT
RETURN_ID
,SALE_ID
,extractValue(xmltype(RETURNxml),
'/Basket/*[substring(name(), string-length(name()) - 9) = "TenderItem"]/NetValue')
,extractValue(xmltype(RETURNxml),
'/Basket/*[substring(name(), string-length(name()) - 9) = "TenderItem"]/Description')
FROM SPR361
WHERE return_id = '9999.0303|20170327224954|2063'
If you never have any nodes with anything after that fixed string then #Shnugo's contains approach is easier, and in Oracle would be very similar:
...
,extractValue(xmltype(RETURNxml),
'/Basket/*[contains(name(), "TenderItem")]/NetValue')
,extractValue(xmltype(RETURNxml),
'/Basket/*[contains(name(), "TenderItem")]/Description')
I'm not sure there's any real difference between name() and local-name() here.
If a basket can have multiple child nodes (card and cash, or more than one of each) you could also switch to XMLTable syntax:
SELECT
s.RETURN_ID
,s.SALE_ID
,x.netvalue
,x.description
FROM SPR361 s
CROSS JOIN XMLTable(
'/Basket/*[contains(name(), "TenderItem")]'
PASSING XMLType(s.RETURNxml)
COLUMNS netvalue NUMBER PATH './NetValue'
, description VARCHAR(80) PATh './Description'
) x
WHERE s.return_id = '9999.0303|20170327224954|2063'
And it's overkill here maybe, but for more complicated tests you can use other XPath syntax, like:
CROSS JOIN XMLTable(
'for $i in /Basket/*
where contains($i/name(), "TenderItem") return $i'
PASSING XMLType(s.RETURNxml)
...
This is SQL-Server syntax and I cannot test, if this works with Oracle too, but I think it will. You can use XQuery function contains():
DECLARE #xml XML=
N'<root>
<abcTenderItem>test1</abcTenderItem>
<SomeOther>should not show up</SomeOther>
<xyzTenderItem>test2</xyzTenderItem>
</root>';
SELECT #xml.query(N'/root/*[contains(local-name(),"TenderItem")]')
only the elements with "TenderItem" in their names show up:
<abcTenderItem>test1</abcTenderItem>
<xyzTenderItem>test2</xyzTenderItem>

Using Fields[0].Value to get XML from FOR XML RAW, ELEMENTS query is messed up

I have a query that uses FOR XML RAW, ELEMENTS to return a SELECT query as a structured XML document. However, when I get the result using a TSQLDataSet by using Fields[0].Value, the result is different from what I see when I run the query in SQL Server Management Studio.
What I see in the result from the TSQLDataSet:
੄customerIdфname၄governmentNumberไdebtorAddress1ไdebtorAddress2ไdebtorAddress3ไdebtorAddress4ࡄpostCodeୄcontactNameՄphonë́faxൄcustomerSinceՄtermsلactiveไcurrentBalanceلDebtorခŁ䄁ഃӤ
What I see in the result in SSMS:
<Debtor>
<customerId>C0E449E5B2C </customerId>
<name>New Customer 2 </name>
<governmentNumber> </governmentNumber>
<debtorAddress1>Address Line 1 </debtorAddress1>
<debtorAddress4>Address Line 4 </debtorAddress4>
<postCode>1234 </postCode>
<phone>1234567890 </phone>
<fax>1234567890 </fax>
<customerSince>2013-06-10T18:16:06.213</customerSince>
<terms>M </terms>
<active>true</active>
<currentBalance>0.0000</currentBalance>
</Debtor>
Is there a particular way it should be executed to get the right result?
AFAIK this is a DbExpress limitation. I know how overcome this, but using ADO (the returned data must be requested using a special parametrized object and a set of ADO streams). However you can use a workaround converting the XML data to a string in the server side sorrounding the sentence with a select (subquery) or just using a simple CAST statement.
For example if you sentence is like so
SELECT Foo, Bar FROM FooTable FOR XML RAW, ELEMENTS
you can rewrite to
SELECT (SELECT Foo, Bar FROM FooTable FOR XML RAW, ELEMENTS)
or you can rewrite to (use a CAST VARCHAR or NVARCHAR)
SELECT CAST( (SELECT Foo, Bar FROM FooTable FOR XML RAW, ELEMENTS) AS VARCHAR(MAX))
and finally
Retrieve the result like this
SQLDataSet1.Fields[0].AsString

Trying to get all xml nodes with SQL Server, what am I doing wrong?

I have the following xml in a table cell, in column MyColumn:
<BSDL xmlns="..." xmlns:i="...">
<dateTime>2012-12-30T00:00:00Z</dateTime>
<dateTime>2013-01-07T00:00:00Z</dateTime>
<dateTime>2013-01-14T00:00:00Z</dateTime>
<dateTime>2013-01-21T00:00:00Z</dateTime>
<dateTime>2013-01-29T00:00:00Z</dateTime>
<dateTime>2013-02-05T00:00:00Z</dateTime>
<dateTime>2013-02-12T00:00:00Z</dateTime>
<dateTime>2013-02-19T00:00:00Z</dateTime>
<dateTime>2013-03-22T00:00:00Z</dateTime>
<dateTime>2013-03-29T00:00:00Z</dateTime>
<dateTime>2013-04-19T00:00:00Z</dateTime>
</BSDL>
I'm just trying to query it (get all xml nodes) using:
SELECT BSDL.item.value('(dateTime)[1]', 'datetime')
from [MyTable]
CROSS APPLY [MyColumn].nodes ('//BSDL') BSDL(item)
it yields no result, although my MyColumn in MyTable has the same number of entries as above, for each row.
Since there's only one <BSDL> node - your call to //BSDL selects that single node and then item.value('(dateTime)[1]', 'datetime') returns the first <dateTime> child.
You need to change your XQuery to (use that xmlns=.... namespace you mention in your sample here):
;WITH XMLNAMESPACES('.....' as ns)
SELECT
item.value('.', 'datetime')
from
dbo.MyTable
CROSS APPLY
MyColumn.nodes ('/ns:BSDL/ns:dateTime') BSDL(item)
You need to get a list of all <dateTime> nodes under <BSDL> - then you'll get all of them and you can inspect them one by one.

How to check that the XML element has the attribute with an empty string value

Is there a way to check that value of an element in the XML field has an empty size using SQLXML? Consider I have the following data in the column Conf of the table Test:
<Conf>
<UserData>
<data type="str" value="" />
</UserData>
</Conf>
I can check that data exists by using the following SQL request:
SELECT Test.Conf.exist('/Conf/UserData/data') FROM Test;
But how can I check that data has an empty value? It could be something like the following, but it doesn't work:
SELECT Test.Conf.value('(/Conf/UserData/data/#value)[1]', 'nvarchar(max)')='' FROM Test;
My final solution is to use the following SQL statement:
SELECT Test.Conf.value('string-length(/Conf[1]/UserData[1]/data[1]/#value)', 'int') FROM Test;
Using XPath 1.0. string(#someattribute) test should return false if empty. I know nothing about SQLXML, but it will work if you can use a control sequence.
Possibly this will work.
SELECT Test.Conf.exist('data(/Conf/UserData/data[#value=''''])') FROM Test;
This checks to see if a data element with #value = '' exists.
The problem is not with XPath but with TSQL syntax.
XML.Exist returns a BIT datatype, indicating whether it exists. SQL Server has no native BOOLEAN datatype (a real true/false).
So, your query should have been
SELECT CASE WHEN Test.Conf.value('(/Conf/UserData/data/#value)[1]', 'nvarchar(max)')=''
THEN 1 ELSE 0 END
FROM Test;

Better way in TSQL to search xml for a node that doesn't exist

We have a source XML file that has an address node, and each node is supposed to have a zip_code node beneath in order to validate. We received a file that failed the schema validation because at least one node was missing it's zip_code (there were several thousand addresses in the file).
We need to find the elements that do not have a zip code, so we can repair the file and send an audit report to the source.
--declare #x xml = bulkcolumn from openrowset(bulk 'x:\file.xml',single_blob) as s
declare #x xml = N'<addresses>
<address><external_address_id>1</external_address_id><zip_code>53207</zip_code></address>
<address><external_address_id>2</external_address_id></address>
</addresses>'
declare #t xml = (
select #x.query('for $a in .//address
return
if ($a/zip_code)
then <external_address_id />
else $a/external_address_id')
)
select x.AddressID.value('.', 'int') AddressID
from #t.nodes('./external_address_id') x(AddressID)
where x.AddressID.value('.', 'int') > 0
GO
Really, it's the where clause that bugs me. I feel like I'm depending on a cast for a null value to 0, and it works, but I'm not really sure that it should. I tried a few variations with the .exist function, but I couldn't get the correct result.
If you just want to ensure that you are selecting address elements that have a zip_code element, then adjust your XPATH to include that criteria in a predicate filter:
/addresses/address[zip_code]
If you also want to ensure that the zip_code element also has a value, use a predicate filter for the zip_node to select those that have text() nodes:
/addresses/address[zip_code[text()]]
EDIT:
Actually, I'm looking for the
opposite. I need to identify the nodes
that don't have a zip, so we can
manually correct the source data.
So, if you want to identify all of the address elements that do not have a zip_code, you can specify it in the XPATH like this:
/addresses/address[not(zip_code)]
If you just want to locate those nodes that are missing their <zip_code> element, you could use something like this:
SELECT
ADRS.ADR.value('(external_address_id)[1]', 'int') as 'ExtAdrID'
FROM
#x.nodes('/addresses/address') as ADRS(ADR)
WHERE
ADRS.ADR.exist('zip_code') = 0
It uses the built-in .exist() method in XQuery to check the existence of a subnode inside an XML node.