Coming from an XSLT background I sometimes struggle to find the XQuery equivalent of some XSLT approach I am used to. This question is about how to order groups in document order, which XSLT 2/3 seems to do automatically while with XQuery the order seems to be undefined.
In more detail: with grouping in XSLT 3 (or 2 as well) I am used to (https://www.w3.org/TR/xslt-30/#order-of-groups) getting the groups returned in "order of first appearance", that is, for instance, when I group some child elements of an element, the ordering of groups is determined by the document order of the first item in each group. This doesn't seem to be the case in XQuery, where https://www.w3.org/TR/xquery-31/#id-group-by says: "The order in which tuples appear in the post-grouping tuple stream is implementation-dependent.".
The question in short is: if
for $city in city
group by $country := $city/@country
return ...
doesn't guarantee me any order in XQuery, is using
for $city at $pos in city
group by $country := $city/@country
order by $pos[1]
return ...
instead the right way to have the "order of first appearance" in XQuery, like XSLT does it by default?
The background: in a simple example (taken from the XSLT 3 spec https://www.w3.org/TR/xslt-30/#grouping-examples) the city
elements in
<cities>
<city name="Milano" country="Italia" pop="5"/>
<city name="Paris" country="France" pop="7"/>
<city name="München" country="Deutschland" pop="4"/>
<city name="Lyon" country="France" pop="2"/>
<city name="Venezia" country="Italia" pop="1"/>
</cities>
have to be grouped by the @country
attribute value to sum up the @pop
population value for each country.
A compact XSLT 3 way to do that is
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
expand-text="yes"
version="3.0">
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:template match="cities">
<table>
<thead>
<tr>
<th>Country</th>
<th>Cities</th>
<th>Population</th>
</tr>
</thead>
<tbody>
<xsl:for-each-group select="city" group-by="@country">
<tr>
<td>{ current-grouping-key() }</td>
<td>
<xsl:value-of select="sort(current-group()/@name)" separator=", "/>
</td>
<td>{ sum(current-group()/@pop) }</td>
</tr>
</xsl:for-each-group>
</tbody>
</table>
</xsl:template>
</xsl:stylesheet>
and I think with a compliant XSLT 3 implementation I am guaranteed to get the result in the order (Italia
, France
, Deutschland
) from the input:
<table>
<thead>
<tr>
<th>Country</th>
<th>Cities</th>
<th>Population</th>
</tr>
</thead>
<tbody>
<tr>
<td>Italia</td>
<td>Milano, Venezia</td>
<td>6</td>
</tr>
<tr>
<td>France</td>
<td>Lyon, Paris</td>
<td>9</td>
</tr>
<tr>
<td>Deutschland</td>
<td>München</td>
<td>4</td>
</tr>
</tbody>
</table>
With XQuery however I think I am not guaranteed any specific order, with
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare option output:method 'html';
declare option output:html-version '5';
cities/<table>
<thead>
<tr>
<th>Country</th>
<th>Cities</th>
<th>Population</th>
</tr>
</thead>
<tbody>
{
for $city in city
group by $country := $city/@country
return
<tr>
<td>{ $country }</td>
<td>{ string-join( sort($city/@name/string()), ', ') }</td>
<td>{ sum($city/@pop) }</td>
</tr>
}
</tbody>
</table>
Saxon at https://xqueryfiddle.liberty-development.net/b4GWV7 gives me the result as
<table>
<thead>
<tr>
<th>Country</th>
<th>Cities</th>
<th>Population</th>
</tr>
</thead>
<tbody>
<tr>
<td>Deutschland</td>
<td>München</td>
<td>4</td>
</tr>
<tr>
<td>Italia</td>
<td>Milano, Venezia</td>
<td>6</td>
</tr>
<tr>
<td>France</td>
<td>Lyon, Paris</td>
<td>9</td>
</tr>
</tbody>
</table>
What is the right way to ensure the order is taken from the "order of first appearance" in XQuery, is using
for $city at $pos in city
group by $country := $city/@country
order by $pos[1]
return
<tr>
<td>{ $country }</td>
<td>{ string-join( sort($city/@name/string()), ', ') }</td>
<td>{ sum($city/@pop) }</td>
</tr>
the right or easiest, most compact way?
Seems to do the job at https://xqueryfiddle.liberty-development.net/b4GWV7/1 but I would like opinions whether there is a different way, more idiomatic way for XQuery.