Detect last post-grouping tuple - xquery

In XQuery 3.0, how is it possible to detect the last post-grouping tuple (defined by XQuery 3.0 spec) produced by the return clause? Is it possible to use something like position() = last()? What would be the context for the position function?
For example, say I want to generate CSV output via XQuery. In order to separate lines in the CSV output, I append a new-line after each tuple produced by the return clause:
xquery version "3.0";
declare option saxon:output "omit-xml-declaration=yes";
declare option saxon:output "method=text";
declare variable $data := (
<items>
<item>
<property name="a"/>
<property name="a"/>
<property name="b"/>
</item>
<item>
<property name="a"/>
</item>
<item>
<property name="b"/>
<property name="c"/>
<property name="d"/>
</item>
<item>
<property name="b"/>
<property name="c"/>
</item>
</items>
);
for $item in $data/item,
$name in $item/property/#name
group by $name
return (
text{string-join(($name, string(count($item))), ",")},
text{"
"}
)
However, this leaves an empty line after the last tuple. If I could test for the tuple position, I could avoid appending new-line after the last tuple.

The XQuery specs indeed seem to be missing something like group by $variable at $position here, similar what would be allowed in the for clause. Reading up the XQuery 3.0 specs again, I couldn't find anything that would help, either. position() and last() require a node context, which is not available within loops.
But regarding your underlying problem: why not use another string-join(...) to concatenate the items with newlines in-between, similar to like you did for the counts?
string-join(
for $item in $data/item,
$name in $item/property/#name
group by $name
return (
text{string-join(($name, string(count($item))), ",")}
),
text{"
"}
)

Related

Conditional combined indexes:When there are several decimal types of indexes, the desired results can not be queried

Use version:4.5.0
db/system/config/db/test/collection.xconf The code is as follows:
<range>
<create qname="item">
<condition attribute="name" value="number"/>
<field name="number" match="#value" type="xs:decimal"/>
</create>
<create qname="item">
<condition attribute="name" value="acreage"/>
<field name="acreage" match="#value" type="xs:decimal"/>
</create>
<create qname="item">
<condition attribute="name" value="radii"/>
<field name="radii" match="#value" type="xs:decimal"/>
</create>
<create qname="item">
<condition attribute="name" value="diameter"/>
<field name="diameter" match="#value" type="xs:decimal"/>
</create>
</range>
Browse Indexes
db/test The code of an XML file is as follows:
<root>
<item name="number" value="4"/>
<item name="acreage" value="5"/>
<item name="radii" value="6"/>
<item name="diameter" value="7"/> </root>
Query statement:
//item[#name='radii'][#value>5.0]
Query Profiling
No result
In theory, the XML file can be found, but the result can not be found for what? Can you help me? Thank you!
Based on the documentation for eXist's Conditional Combined Indexes feature you are trying to use here, it appears to me that this feature only support string comparisons (with an optional "numeric" mode). See https://exist-db.org/exist/apps/doc/newrangeindex#D3.21.18. In other words, your #type="xs:decimal" is not resulting in your attributes' values being cast to xs:decimal; effectively, instead, they are being indexed as xs:string.
Thus, for your query to work with the given data, change the predicate to [#value gt "5"].
Or, to force numeric comparisons, add numeric="yes" to the <field> element in your index definitions, and then change your predicate to [#value gt "5.0"].

XQuery aggregate result AND give total

I have an XML inventory of items.
The inventory lists how much if Item with ID 1 I have, how much of Item with ID 2, etc (think each item ID represents 1 product).
The list, however, is subdivided, depending on what quantity of items of particular type have a particular mark on them.
So, I have let us say:
Item ID Marked? Qty
1 (no) 500
1 ABC 100
1 (no) 50
1 FFFF 333
2 (no) 10000
....
This ir represented in a structure like this:
<Base>
<Item>
<Id>1</Id>
<Qty>500</Qty>
</Item>
<Item>
<Id>1</Id>
<Qty>100</Qty>
<Mark>ABC</Mark>
</Item>
<Item>
<Id>1</Id>
<Qty>50</Qty>
</Item>
<Item>
<Id>1</Id>
<Qty>333</Qty>
<Mark>FFFF</Mark>
</Item>
<Item>
<Id>2</Id>
<Qty>10000</Qty>
</Item>
...
</Base>
Using XQuery transformation I wish to produce, instead of multiple rows per item ID, 1 aggregate row for each item ID, which would list both total sum of quantities per item (both marked and unmarked), and a separate sum for only those quantities of item that are marked (have subtag. I am not concerned about the contents of the individual tags).
In table form what I want would be this:
Item ID Marked Qty Total Qty
1 433 983
2 0 10000
etc.
In actual XML form the transformation should produce something like this:
<Item><Id>1</Id><MarkedQuantity>433</MarkedQuantity><Total>983</Total></Item>
<Item><Id>2</Id><Total>10000</Total></Item>
....
UPD: Edited for clarity.
If you use
for $item in Base/Item
group by $id := $item/Id
order by $id
return <Item>
<Id>{$id}</Id>
{for $marked in $item[Mark]
group by $mark := $marked/Mark
return <Marked name="{$mark}">
{sum($marked/Qty)}
</Marked>
}
<Total>{sum($item/Qty)}</Total>
</Item>
you will get
<Item>
<Id>1</Id>
<Marked name="ABC">100</Marked>
<Total>650</Total>
</Item>
<Item>
<Id>2</Id>
<Total>10000</Total>
</Item>
I am not quite sure you need or want the inner grouping as I am not sure whether there can be different marks you want to distinguish but hopefully it gives you an idea.
If you simply want a total of Marked Items then I think
for $item in Base/Item
group by $id := $item/Id
order by $id
return <Item>
<Id>{$id}</Id>
{if ($item[Mark])
then <MarkedQuantity>{sum($item[Mark]/Qty)}</MarkedQuantity>
else ()
}
<Total>{sum($item/Qty)}</Total>
</Item>
does that.
Turns out that the answer was way easier (as I suspected):
for $item in //Item
let $d := $item/Id
group by $d
order by $d
return <Marked id="{$d}" Total="{ sum($item/Qty) }">{ sum($item[Mark]/Qty) }</Marked>
Basically, give 2 different sums for the same FOR clause: 1 with total sum of items, another with sum of filtered items (those that have Mark child tag in them).
I still do not understand though, why I can't use <Id>{ $d }</Id><Marked>{ sum($item[Mark]/Qty) }</Marked><Total>{ sum($item/Qty) }</Total> - in which case basex complains of finding } where it wanted >
But basically it works.

Mule ESB DataMapper: Aggregation of field 1 multiplied by field 2

I have the following structures:
Strcuture A:
<itemlist>
<item>
<id>123</id>
<price>1</price>
<quantity>1</quantity>
</item>
<item>
<id>124</id>
<price>2</price>
<quantity>1</quantity>
</item>
<item>
<id>125</id>
<price>3</price>
<quantity>1</quantity>
</item>
<itemlist>
Structure B:
<totals>
<total>
<totalPrice>3</totalPrice>
</total>
</totals>
If I want a sumation of all the field multiplied
by the field in structure A to be placed into the totalprice field of structure B, would that be possible in the DataMapper.
If it is possible, how would you do it?
Thanks
Yes it is, in datamapper find the mapping of the field 1 and then go to script view. There you will find a datamapper MEL script. Find output.total = input.field1 and add * input.field2.

Applying suggestion-source constraints for typeahead in Marklogic

Say, I have 5 documents that belong to either of different collections such as:
Biography, Fiction, Humour, Adventure
For instance, document 1 is in Adventure Collection:
<Book>
<title>Harry Potter and the Deathly Hallows</title>
<Author>J.K.Rowling</Author>
<year>2007</year>
</Book>
document 2, is in Biography Collection:
<Book>
<title>Steve Jobs</title>
<Author>Walter Issacson</Author>
<year>2011</year>
</Book>
Now I want to apply suggest on year element. I want to apply this on collections i.e. for example-suggest on Biography collections
I have defined element range index on year and ingested sample documents using application loader and configured collections for documents in document settings.
Follwing is my XQUERY code:
xquery version "1.0-ml";
import module namespace search = "http://marklogic.com/appservices/search"
at "/MarkLogic/appservices/search/search.xqy";
let $options :=
<options xmlns="http://marklogic.com/appservices/search">
<constraint name="Group">
<collection prefix="Biography/"/>
</constraint>
<suggestion-source ref="Group">
<range collation="http://marklogic.com/collation/"
type="xs:string" >
<element name="year"/>
</range>
</suggestion-source>
</options>
return
search:suggest("Group:20", $options)
On running this query I am getting suggestions for both 2011 and 2007 which is not what I am expecting.
Expected suggestion is 2011 (as only Biography collection should be searched).
I have refered this doc on search:suggest but I am not able to find out what exactly is the mistake.
Where am I doing wrong?
Shrey:
The values from the suggestion source are substituted for the values from the referenced constraint. In other words, the options say that input qualified by the group constraint should come from the year range index, which is what you're seeing. For more detail, please see:
http://docs.marklogic.com/guide/search-dev/search-api#id_40748
To filter a set of suggestions, you can call search:suggest() with two string queries. The first provides the input for the suggestions. The second provides the filtering constraint. In the case above, the filtering constraint would be the Biography collection. For more detail, please see:
http://docs.marklogic.com/guide/search-dev/search-api#id_91911
Hoping that helps
However, I am able to achieve the required output using additional-query in options as follows:
xquery version "1.0-ml";
import module namespace search = "http://marklogic.com/appservices/search"
at "/MarkLogic/appservices/search/search.xqy";
let $options :=
<options xmlns="http://marklogic.com/appservices/search">
<additional-query>{cts:collection-query("Biography")}
</additional-query>
<constraint name="Search_Element">
<range collation="http://marklogic.com/collation/"
type="xs:string" >
<element name="year"/>
</range>
</constraint>
<suggestion-source ref="Search_Element">
<range collation="http://marklogic.com/collation/"
type="xs:string" >
<element name="year"/>
</range>
</suggestion-source>
</options>
return
search:suggest("Search_Element:20", $options)
Still, I was wondering how could it be done without using additional-query paramater because it would be more optimized to use collection constraint for above.

how to save multiple rows in SQL using XML?

I have following XML, and i wish to save its data in my SQL table. I have a table named as tblDummy it has three columns "JobID" "ItemID" "SubitemID". there can be multiple subitemsid for particular combination of Jobid and Itemid. How can i do this?
<jobs>
<job>
<jobid>4711</jobid>
<items>
<itemid>1</itemid>
<subitems>
<subitemid>1</subitemid>
<subitemid>2</subitemid>
</subitems>
<itemid>2</itemid>
<subitems>
<subitemid>7</subitemid>
<subitemid>10</subitemid>
</subitems>
<itemid>9</itemid>
<subitems>
<subitemid>12</subitemid>
<subitemid>16</subitemid>
</subitems>
</items>
</job>
</jobs>
As this XML is, you cannot properly parse it. You would need to change it - you should put each item with its itemid and subitems into a separate <item> node - otherwise you just have a long list of <itemid> and <subitems> nodes under your <items> main node, but you have no means of telling which <itemid> and <subitems> nodes belong together ....
You need to change your XML to be something like this:
<job>
<jobid>4711</jobid>
<items>
<item>
<itemid>1</itemid>
<subitems>
<subitemid>1</subitemid>
<subitemid>2</subitemid>
</subitems>
</item>
<item>
<itemid>2</itemid>
<subitems>
......
</subitems>
</item>
... (possibly more <item> nodes) ....
</items>
</job>
THEN you could use basically the same code I had for your previous question - extended to cover three levels:
CREATE PROCEDURE dbo.SaveJobs (#input XML)
AS BEGIN
;WITH JobsData AS
(
SELECT
JobID = JobNode.value('(jobid)[1]', 'int'),
ItemID = ItemNode.value('(itemid)[1]', 'int'),
SubItemID = SubItemNode.value('.', 'int')
FROM
#input.nodes('/jobs/job') AS TblJobs(JobNode)
CROSS APPLY
JobNode.nodes('items/item') AS TblItems(ItemNode)
CROSS APPLY
ItemNode.nodes('subitems/subitem') AS TblSubItems(SubItemNode)
)
INSERT INTO dbo.tblDummy(JobID, ItemID, SubItemID)
SELECT JobID, ItemID, SubItemID
FROM JobsData
END
Basically, you need three "lists" of XML nodes:
first you need the list of all <jobs>/<job> nodes to get the jobid values
for each of those job nodes, you will also need to get their list of nested <items>/<item> to get the itemid value
from each node, you then also get the list of <subitems>/<subitem>
This will most likely work - but most likely, it will be rather slow (three nested calls to the .nodes() function!).
Update:
OK, so the first call #input.nodes('/jobs/job') AS TblJobs(JobNode) basically creates a "pseudo" table TblJobs with a single column JobNode and each <job> element in your XML is being stored into a row in that pseudo table - so the first row will contain this XML in it's JobNode column:
<job>
<jobid>4711</jobid>
<items>
<item>
<itemid>1</itemid>
<subitems>
<subitemid>1</subitemid>
<subitemid>2</subitemid>
</subitems>
</item>
<item>
<itemid>2</itemid>
<subitems>
......
</subitems>
</item>
... (possibly more <item> nodes) ....
</items>
</job>
and each further row will contain the additional XML fragments for each subsequent <job> element inside <jobs>
From each of those XML fragments, the second call
CROSS APPLY JobNode.nodes('items/item') AS TblItems(ItemNode)
again selects a list of XML fragments into a pseudo table (TblItems) with a single column ItemNode that contains the XML fragment for each <item> node inside that <job> node we're dealing with currently.
So the first row in this pseudo-table contains:
<item>
<itemid>1</itemid>
<subitems>
<subitemid>1</subitemid>
<subitemid>2</subitemid>
</subitems>
</item>
and the second row will contain
<item>
<itemid>2</itemid>
<subitems>
......
</subitems>
</item>
and so on.
And then the third call - you've guessed it - again extracts a list of XML elements as rows into a pseudo-table - one entry for each <subitem> node in your XML fragment.
Update #2:
I'm new to "JobID = JobNode.value('(jobid)[1]', 'int')" line of code
OK - given the <Job> XML fragment that you have:
<job>
<jobid>4711</jobid>
<items>
......
</items>
</job>
the .value() call just executes this XPath expression (jobid) on that XML and basically gets back the <jobid>4711</jobid> snippet. It then extracts the value of that node (the inner text), and the second parameter of the .value() call defines what SQL data type to interpret this as - so it basically grabs the 4711 from the <jobid> node and interprets it as an int
You can take a composite key of Jobid and Itemid as primary key.

Resources