XQuery To Select Related Node - xquery

Given the below XML, what would be the proper SQL XQuery to retrieve the SubscriberStatus where the SubscriberID is empty? Given the XML is stored in a column with the XML datatype.
<ObjectEntry>
<Key>Key1</Key>
<DicValue>
<ObjectEntry>
<Key>SubscriberStatus</Key>
<Value xsi:type="xsd:string">Active</Value>
<DicValue />
</ObjectEntry>
<ObjectEntry>
<Key>SubscriberID</Key>
<Value xsi:type="xsd:string" />
<DicValue />
</ObjectEntry>
</DicValue>
</ObjectEntry>

Try this:
If $node holds your xml fragment then
$node//ObjectEntry[DicValue/ObjectEntry[Key eq "SubscriberStatus"] and DicValue/ObjectEntry[Key eq "SubscriberID"][Value ne ""]]
will give you back the ObjectEntry parent for the non empty SubscriberIDs

This is a simply XPath expression, there is no need for true XQuery. XPath is a subset of XQuery. Given that you want the <Value/> element of the SubscriberStatus you can get it like the following:
//ObjectEntry/DicValue[ObjectEntry[Key = "SubscriberID"]/Value = ""]/ObjectEntry[Key = "SubscriberStatus"]/Value
This fetches all ObjectEntries which do have an empty SubscriberID and then navigates to the SubscriberStatus. If you just want the actual string, you cann append /string()

Thanks for the suggestions! Unfortunately they didn't do what I was asking, but did help me get the right syntax. Here's a solution that works.
select Request.query('//ObjectEntry/DicValue/ObjectEntry[Key = "SubscriberStatus"]/Value') as SubscriberStatus
from RequestLog
where
Request.exist('//ObjectEntry/DicValue[ObjectEntry[Key = "SubscriberID" and Value = ""]]') = 1

Related

How to Concatenate multiple repetitive nodes into a single node - BizTalk

I have something like this in an input XML
<OrderText>
<text_type>0012</text_type>
<text_content>Text1</text_content>
</OrderText>
<OrderText>
<text_type>ZT03</text_type>
<text_content>Text2</text_content>
</OrderText>
The above data I need to map after concatenating as the below schema
<Order>
<Note>0012:Text1#ZT03:Text2</Note>
</Order>
Can anyone please help?
I'm going to assume that your input actually has a Root node, as otherwise it is not valid XML.
<Root>
<OrderText>
<text_type>0012</text_type>
<text_content>Text1</text_content>
</OrderText>
<OrderText>
<text_type>ZT03</text_type>
<text_content>Text2</text_content>
</OrderText>
</Root>
Then all you need is a map like this
With a String Concatenate functoid with
Input[0] = text_type
Input[1] = :
Input[2] = text_content
Input[3] = #
That goes into a Cumulative Concatenate
This will give you an output of
<Order>
<Note>0012:Text1#ZT03:Text2#</Note>
</Order>
Note: There is a extra # at the end, but you could use some more functoids to trim that off if needed.
You can use the Value-Mapping Flattening functoid in a map, then feed the result of each into a Concatenate functoid to generate the result string. The map can be executed on a port or in an orchestration.

How to add a value to the existing element value and return it as a new value

This is the xml file.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<AtcoCode> System-Start-Date= 2018-05-16T12:35:48.6929328-04:00, " ", System-End-Date = 9999-12-31, " ", 150042010003</AtcoCode>
<NaptanCode>esxatgjd</NaptanCode>
<PlateCode>
</PlateCode>
<CleardownCode>
</CleardownCode>
<CommonName>Upper Park</CommonName>
<CommonNameLang>
</CommonNameLang>
<ShortCommonName>
</ShortCommonName>
<ShortCommonNameLang>
</ShortCommonNameLang>
<Landmark>Upper Park</Landmark>
<LandmarkLang>
</LandmarkLang>
<Street>High Road</Street>
<StreetLang>
</StreetLang>
<Crossing>
</Crossing>
<CrossingLang>
</CrossingLang>
<Indicator>adj</Indicator>
<IndicatorLang>
</IndicatorLang>
<Bearing>NE</Bearing>
<NptgLocalityCode>E0046286</NptgLocalityCode>
<LocalityName>Loughton</LocalityName>
<ParentLocalityName>
</ParentLocalityName>
<GrandParentLocalityName>
</GrandParentLocalityName>
<Town>Loughton</Town>
<TownLang>
</TownLang>
<Suburb>
</Suburb>
<SuburbLang>
</SuburbLang>
<LocalityCentre>1</LocalityCentre>
<GridType>U</GridType>
<Easting>541906</Easting>
<Northing>195737</Northing>
<Co-ordinates>51.64255,0.04944</Co-ordinates>
<StopType>BCT</StopType>
<BusStopType>MKD</BusStopType>
<TimingStatus>OTH</TimingStatus>
<DefaultWaitTime>
</DefaultWaitTime>
<Notes>
</Notes>
<NotesLang>
</NotesLang>
<AdministrativeAreaCode>080</AdministrativeAreaCode>
<CreationDateTime>2006-11-06T00:00:00</CreationDateTime>
<ModificationDateTime>2010-01-16T07:58:02</ModificationDateTime>
<RevisionNumber>5</RevisionNumber>
<Modification>rev</Modification>
<Status>act</Status>
</root>
How to achieve this?
Question: Create the path range index for the status element and fetch all the documents that has status del
after fetching all the documents, you need to create the new element called currentreservationnumber under RevisionNumber element.
The value of the currentrevisionnumber will be +1 to the RevisionNumber.
I think the warning about sequential numbers is related to system-wide unique numbers/ids (like Oracle sequence), so not a worry in this case?
If you only ever have one RevisionNumber, and you can find it without a path index, you can maybe get by with element-value query on the RevisionNumber since it's already indexed.
Given that you get the document somehow, it could be as simple as:
let $doc := fn:doc ('/foo.xml')
let $rev-node := $doc/root/RevisionNumber
return xdmp:node-insert-after ($rev-node, <currentreservationnumber>{$rev-node + 1}</currentreservationnumber>)
though remember to consider locking if you are doing a big query/update. And you might need to switch to node-replace if there is already a currentreservationnumber.

Using XQUERY to retrieve attributes value

Is it possible to use XQUERY to retrieve the attributes filename from the following XML? I am trying to use /preFileDoc/inpXML/#filename but it doesn't work...
<?xml version="1.0"?>
<preFileDoc xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<senderId>ABC</senderId>
<receiverId>XYZ</receiverId>
<tranxCode>A001</tranxCode>
<inpXML version="1.0" encoding="UTF-8">
<soap-env:Envelope>
<soap-env:Header msgcode="SPPCONVAKT" orig-system="002FTB" refid="65355ff50a172064484bf9da64c1e245" timestamp="2009-02-11 21:00:10.741" filename="SPPCONVAKT20090128001.dat"/>
<soap-env:Body>
text1
text2
</soap-env:Body>
</soap-env:Envelope>
</inpXML>
</preFileDoc>
ps: Sometimes the filename attributes is sent as fileName in the incoming XML..thinking to retrieve value from attributes #filename OR #fileName.. can it achieve in single XQUERY? Thanks for advice...
I think your XPath is incomplete. The last child-step / in /preFileDoc/inpXML/#filename only matches attributes of the inpXML element, not its descendants.
One way to solve the problem would be the //-step:
/preFileDoc/inpXML//#filename
Note that this would find all attributes named filename in the soapenv:Body, too.
A more robust way would thus be to declare the soapenv prefix in the XQuery:
declare namespace soap-env="http://schemas.xmlsoap.org/soap/envelope/";
return /preFileDoc/inpXML//soap-env:Header/#filename
Finally, the different capitalizations of filename can be worked around by specifying both:
declare namespace soap-env="http://schemas.xmlsoap.org/soap/envelope/";
return /preFileDoc/inpXML//soap-env:Header/(#filename | #fileName)
You can take the union of multiple attributes. It will be unlikely that this attribute will appear multiple times with different casing, so that should always return a single node:
//soap-env:Header/#filename | //soap-env:Header/#fileName
Optionally, you could wrap it in parentheses, and add [1] behind it, to always take the first result.
(//soap-env:Header/#filename | //soap-env:Header/#fileName)[1]
If you replace the union with a comma, which creates a sequence instead of a document order node set, you can add a default as well at the end. Maybe not very usefull here, but perhaps in other situations:
(//soap-env:Header/#filename , //soap-env:Header/#fileName, "default.dat")[1]
HTH!
You need to respect and take into account the SOAP XML namespace!
Since I don't know what you're using, I cannot tell you how to do this - but there's the xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" on the root node, and your #filename attribute is on the <soap-env:Header .... /> node - so you need to include the XML namespace in your XQuery.
In .NET / C#, you could do it like this (using the "older" XmlDocument style which supports XPath directly):
// define test XML
string xmlContent =
#"<?xml version='1.0'?>
<preFileDoc xmlns:soap-env='http://schemas.xmlsoap.org/soap/envelope/'>
<senderId>ABC</senderId>
<receiverId>XYZ</receiverId>
<tranxCode>A001</tranxCode>
<inpXML version='1.0' encoding='UTF-8'>
<soap-env:Envelope>
<soap-env:Header msgcode='SPPCONVAKT' orig-system='002FTB' refid='65355ff50a172064484bf9da64c1e245' timestamp='2009-02-11 21:00:10.741' filename='SPPCONVAKT20090128001.dat'/>
<soap-env:Body>
text1
text2
</soap-env:Body>
</soap-env:Envelope>
</inpXML>
</preFileDoc>";
// create XmlDocument and load test data
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlContent);
// define XML namespace manager and add the SOAP namespace to it
XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
mgr.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
// use XPath and the XML namespaces to grab the <Header> node
// the first two nodes <preFileDoc> and <inpXML> are not inside any explicit
// XML namespace
// but the next two (<Envelope> and <Header>) are in the "soap" XML namespace
XmlNode header = doc.SelectSingleNode("/preFileDoc/inpXML/soap:Envelope/soap:Header", mgr);
// read the "filename" attribute from the header node
if(header != null && header.Attributes["filename"] != null)
{
string fileName = header.Attributes["filename"].Value;
}

How can I tell if E4X expression has a match or not?

I am trying to access an XMLList item and convert it to am XML object.
I am using this expression:
masonicXML.item.(#style_number == styleNum)
For example if there is a match everything works fine but if there is not a match then I get an error when I try cast it as XML saying that it has to be well formed. So I need to make sure that the expression gets a match before I cast it as XML. I tried setting it to an XMLList variable and checking if it as a text() propertie like this:
var defaultItem:XMLList = DataModel.instance.masonicXML.item.(#style_number == styleNum);
if(defaultItem.text())
{
DataModel.instance.selectedItem = XML(defaultItem);
}
But it still give me an error if theres no match. It works fine if there is a match.
THANKS!
In my experience, the simplest way to check for results is to grab the 0th element of the list and see if it's null.
Here is your code sample with a few tweaks. Notice that I've changed the type of defaultItem from XMLList to XML, and I'm assigning it to the 0th element of the list.
var defaultItem:XML =
DataModel.instance.masonicXML.item.(#style_number == styleNum)[0];
if( defaultItem != null )
{
DataModel.instance.selectedItem = defaultItem;
}
OK I got it to work with this:
if(String(defaultItem.#style_number).length)
Matt's null check is a good solution. (Unless there is the possibility of having null items within an XMLList.. probably not, but I haven't verified this.)
You can also check for the length of the XMLList without casting it to a String:
if (defaultItem.#style_number.length() > 0)
The difference to String and Array is that with an XMLList, length() is a method instead of a property.

how can I read an xml attribute using readXML? how does dataset.readxml translate into tables?

I just want to know how does the table resulting from readXML look like, say if the xml file looks like this:
<item attr="some attribute">
<descirption>anything</description>
</item>
I can reference tables directly by the Tables collection like this:
ds.ReadXml(xml);
... ds.Tables[i]
then I can access rows and columns using the rows collection:
ds.Tables[3].Rows[i].ItemArray(j);
but how can I access an "attribute" of any xml node?
It's in the Row. I fixed your xml a bit :)
var xml = "<item attr=\"some attribute\"><description>anything</description></item>";
var ds = new DataSet();
ds.ReadXml( new StringReader( xml ), XmlReadMode.Auto );
var ia = ds.Tables[0].Rows[0].ItemArray;
var att = ia[1]; // att == "some attribute"
If you don't have a schema, you might have to check the column to determine what it is.
Per comment: You will see I am letting it infer the schema (XmlReadMode.Auto). It takes elements under the root node as Rows then adds the attributes in order and then the value in the element. So for example the following XML ...
var xml = "<items>
<item attr1='attr1' attr2='attr2'>
<description>desc1</description>
</item>
<item attr1='attr3' attr2='attr4'>
<description>desc2</description></item>
</items>";
I will get two rows (one for each item) with Columns for attr1, attr2 and description. You can change the way it interprets the XML using a schema.

Resources