Scope of a xPath variable

Monday, 19 December 2022 · 9 messages · last reply 20 Dec 2022
LS
Leo Studer19 Dec 2022 22:43 #
Hello Cracks ;-)
In the code below I encounter the problem, that I have to define the same xPath function variable $organizations twice and I do not understand why.
Running the code with the newest versions of BaseX or with Saxon EE, I get a runtime error when I leave the second definition of $organizations out.
Any explanation? Thanks in advance. Happy Holidays Always, Leo
xquery version "3.1" encoding "UTF-8";
declare variable $doc := doc("factbook.xml");

element Memberships {
let $membershipPotentcy := function($orgs){if (count($orgs)le 0) then 'none'
                                           else if (count($orgs) le 5) then 'few'
                                           else 'many'}
let $organizations := function($country){$doc//organization[members/@country = $country/@id]/@abbrev/string(.)}
for $countries in $doc//country
  group by $mp:= $membershipPotentcy($organizations($countries))
  order by $mp
return
 element {$mp} {
 for $country in $countries
 let $organizations := function($country){$doc//organization[members/@country = $country/@id]/@abbrev/string(.)}
   order by $country/@name
 return
  <country>{$country/@name, $organizations($country)}</country>
}}
MH
Martin Honnen19 Dec 2022 22:51 #
Quoted text · Leo Studer · 38 lines
On 12/19/2022 10:43 PM, Leo Studer wrote: > Hello Cracks ;-) > > In the code below I encounter the problem, that I have to define the > same xPath function variable $organizations twice and I do not > understand why. > Running the code with the newest versions of BaseX or with Saxon EE, I > get a runtime error when I leave the second definition of > $organizations out. > > Any explanation? Thanks in advance. > > Happy Holidays > Always, Leo > > > > xquery version "3.1" encoding "UTF-8"; > declare variable $doc := doc("factbook.xml"); > > *element**Memberships*{ > *let**$membershipPotentcy*:=*function*(*$orgs*){*if*(/count/(*$orgs*)le0) > *then*'none' > *else**if*(/count/(*$orgs*) le5) *then*'few' > *else*'many'} > *let**$organizations*:=*function*(*$country*){*$doc*//*organization*[*members*/*/@country/*=*$country*/*/@id/*]/*/@abbrev/*//string/(.)} > *for**$countries**in**$doc*//*country* > *group by**$mp*:=*$membershipPotentcy*(*$organizations*(*$countries*)) > *order by**$mp* > *return* > *element*{*$mp*} { > *for**$country**in**$countries* > *let**$organizations*:=*function*(*$country*){*$doc*//*organization*[*members*/*/@country/*=*$country*/*/@id/*]/*/@abbrev/*//string/(.)} > *order by**$country*/*/@name/* > *return* > <country>{*$country*/*/@name/*,*$organizations*(*$country*)}</country> > }} >
Which runtime error do you get? I guess the group by binds your let variable for each country in the group so perhaps
   head($organizations)($country)
would suffice without redefining the variable. It would be an odd way to solve it, I would prefer to have a
declare function local:organizations($country)
declared before.
MH
Martin Honnenin reply to Graydon, 19 Dec 2022, 23:5819 Dec 2022 23:10 #
Quoted text · Graydon · 9 lines
On 12/19/2022 10:58 PM, Graydon wrote: > On Mon, Dec 19, 2022 at 10:43:25PM +0100, Leo Studer scripsit: >> In the code below I encounter the problem, that I have to define the same xPath function variable $organizations twice and I do not understand why. > In line 10, you've used a let clause to bind a variable of type function to the name organizations. That's in context a specific FLOWR expression. > > The expression in the return clause of that FLOWR expression returns an > element constructor where the contents expression contains another FLOWR > expression. The first definition on line 10 will be out of scope in > there.
I don't think so, the variable is not out of scope, it is bound for each item in the group to that function, meaning that value in the return clause of the group is a sequence of functions and that way
   head($organizations)($country)
would work.
Quoted text · Graydon · 9 lines
> You could define the function with scope for the module: > > declare function local:getOrganizations($in as element(country)) > as xs:string* { > let $thisCountry as xs:string := $in/@id/string(); > return $doc//organization[members/@country = $thisCountry]/@abbrev/string(.) > }; > >
That is certainly the cleaner way.
HR
Hans-Juergen Rennauin reply to Graydon, 19 Dec 2022, 23:5819 Dec 2022 23:19 #
I think the reason is a different one - it is related to the precise semantics of the group by clause. The group by clause maps each group of input tuples with equal grouping key(s) to a single output tuple, in which- the grouping variable(s) is/are bound to the key values (as you would expect)- all other variables are bound to the concatenated sequence of values bound to the variable name in the members of the group (which may cause surprises) So when a group has, say, 3 members, the variable $organisations is not any more bound to a single function item, but to a sequence of 3 function items (which are identical). Of course, a sequence of more than one function item cannot be called - only a single function item can be called. In order to check that this is the correct interpretation of the issue, you can remove the second assignment to $organisations, and append to the invocation the predicate [1] - then it works, as then you have again a single function item: <country>{$country/@name, $organizations[1]($country)}</country> One way to handle this in a more straightforward way is to shift the function item into an additional, outer FLWOR level at the beginning. While we are at it, let's shift both function items: let $organizations := function($country){ $doc//organization[members/@country = $country/@id]/@abbrev/string(.)}let $membershipPotentcy := function($orgs){ if (count($orgs)le 0) then 'none' else if (count($orgs) le 5) then 'few' else 'many'}return element Memberships { for $countries in $doc//country group by $mp:= $membershipPotentcy($organizations($countries)) order by $mp return element {$mp} { for $country in $countries return <country>{$country/@name, $organizations($country)}</country> }} Am Montag, 19. Dezember 2022 um 22:58:30 MEZ hat Graydon <graydonish at gmail.com> Folgendes geschrieben:
Quoted text · Graydon · 2 lines
On Mon, Dec 19, 2022 at 10:43:25PM +0100, Leo Studer scripsit: > In the code below I encounter the problem, that I have to define the same xPath function variable $organizations twice and I do not understand why.
In line 10, you've used a let clause to bind a variable of type function to the name organizations. That's in context a specific FLOWR expression. The expression in the return clause of that FLOWR expression returns an element constructor where the contents expression contains another FLOWR expression. The first definition on line 10 will be out of scope in there. You could define the function with scope for the module:
declare function local:getOrganizations($in as element(country))
    as xs:string* {
  let $thisCountry as xs:string := $in/@id/string();
  return $doc//organization[members/@country = $thisCountry]/@abbrev/string(.)
};
And then use it in both places with the single definition.
-- Graydon Saunders | graydonish at gmail.com Þæs oferéode, ðisses swá mæg. -- Deor ("That passed, so may this.")
IL
Imsieke, Gerrit, le-tex19 Dec 2022 23:48 #
Thank you, Hans-Jürgen, for shedding really insightful light (if there is such a thing) on this aspect of XQuery grouping, which I use gropingly (pun intended) without fully understanding it.
Quoted text · Hans-Juergen Rennau · 82 lines
On 19.12.2022 23:19, Hans-Juergen Rennau wrote: > I think the reason is a different one - it is related to the precise > semantics of the group by clause. > > The group by clause maps each group of input tuples with equal grouping > key(s) to a single output tuple, in which > - the grouping variable(s) is/are bound to the key values (as you would > expect) > - all other variables are bound to the concatenated sequence of values > bound to the variable name in the members of the group (which may cause > surprises) > > So when a group has, say, 3 members, the variable $organisations is not > any more bound to a single function item, but to a sequence of 3 > function items (which are identical). Of course, a sequence of more than > one function item cannot be called - only a single function item can be > called. In order to check that this is the correct interpretation of the > issue, you can remove the second assignment to $organisations, and > append to the invocation the predicate [1] - then it works, as then you > have again a single function item: > > <country>{$country/@name, $organizations[1]($country)}</country> > > One way to handle this in a more straightforward way is to shift the > function item into an additional, outer FLWOR level at the beginning. > While we are at it, let's shift both function items: > > let $organizations := function($country){ > $doc//organization[members/@country = $country/@id]/@abbrev/string(.)} > let $membershipPotentcy := function($orgs){ > if (count($orgs)le 0) then 'none' else if (count($orgs) le 5) then > 'few' else 'many'} > return > element Memberships { > for $countries in $doc//country > group by $mp:= $membershipPotentcy($organizations($countries)) > order by $mp > return > element {$mp} { > for $country in $countries > return > <country>{$country/@name, $organizations($country)}</country> > }} > > > Am Montag, 19. Dezember 2022 um 22:58:30 MEZ hat Graydon > <graydonish at gmail.com> Folgendes geschrieben: > > > On Mon, Dec 19, 2022 at 10:43:25PM +0100, Leo Studer scripsit: > > > In the code below I encounter the problem, that I have to define the > same xPath function variable $organizations twice and I do not > understand why. > > > In line 10, you've used a let clause to bind a variable of type function > to the name organizations. That's in context a specific FLOWR expression. > > The expression in the return clause of that FLOWR expression returns an > element constructor where the contents expression contains another FLOWR > expression. The first definition on line 10 will be out of scope in > there. > > You could define the function with scope for the module: > > declare function local:getOrganizations($in as element(country)) > as xs:string* { > let $thisCountry as xs:string := $in/@id/string(); > return $doc//organization[members/@country = > $thisCountry]/@abbrev/string(.) > }; > > And then use it in both places with the single definition. > > > -- > Graydon Saunders | graydonish at gmail.com <mailto:graydonish at gmail.com> > Þæs oferéode, ðisses swá mæg. > -- Deor ("That passed, so may this.") > >
G
Graydonin reply to Leo Studer, 19 Dec 2022, 22:4319 Dec 2022 23:58 #
Quoted text · Leo Studer · 2 lines
On Mon, Dec 19, 2022 at 10:43:25PM +0100, Leo Studer scripsit: > In the code below I encounter the problem, that I have to define the same xPath function variable $organizations twice and I do not understand why.
In line 10, you've used a let clause to bind a variable of type function to the name organizations. That's in context a specific FLOWR expression. The expression in the return clause of that FLOWR expression returns an element constructor where the contents expression contains another FLOWR expression. The first definition on line 10 will be out of scope in there. You could define the function with scope for the module:
declare function local:getOrganizations($in as element(country))
    as xs:string* {
  let $thisCountry as xs:string := $in/@id/string();
  return $doc//organization[members/@country = $thisCountry]/@abbrev/string(.)
};
And then use it in both places with the single definition.
-- Graydon Saunders | graydonish at gmail.com Þæs oferéode, ðisses swá mæg. -- Deor ("That passed, so may this.")
HR
Hans-Juergen Rennauin reply to Imsieke, Gerrit, le-tex, 19 Dec 2022, 23:4820 Dec 2022 08:42 #
Thank you for your kind words, Gerrit! Until a few weeks ago, my use of grouping was a groping one also in my case - until I learned from the spec that the FLWOR mechanics are only fully understood if we regard the clauses as a pipeline of stations, each one transforming an input tuple stream into an output tuple stream - that's the key. And that's at first weird, as we are used to think in terms of "assignments", "loops", sorting, grouping, filtering, counting devices. But when thinking of the different clauses as just variants of a single idea - "tuple stream transformer" - everything is decomposed into tiny bits - the details of how a particular kind of clause maps its input tuples to output tuples. No interaction whatsoever between the clauses, apart from output tuple streams becoming input tuple streams. Suddenly it is not amazing any more that you can have any number of any kind of clauses, e.g. several where clauses, and that the clauses can occur at any position within the expression (except for the constraint that the first clause must be let or for). And so, for example, I had always kept my fingers crossed when adding an order by after a group by. Now I understand that once the group by has done its thing and produced an output tuple stream according to its rules, you have a tuple stream without any memory of what has happened at earlier clauses. Am Montag, 19. Dezember 2022 um 23:48:45 MEZ hat Imsieke, Gerrit, le-tex <gerrit.imsieke at le-tex.de> Folgendes geschrieben: Thank you, Hans-Jürgen, for shedding really insightful light (if there is such a thing) on this aspect of XQuery grouping, which I use gropingly (pun intended) without fully understanding it.
Quoted text · Hans-Juergen Rennau · 82 lines
On 19.12.2022 23:19, Hans-Juergen Rennau wrote: > I think the reason is a different one - it is related to the precise > semantics of the group by clause. > > The group by clause maps each group of input tuples with equal grouping > key(s) to a single output tuple, in which > - the grouping variable(s) is/are bound to the key values (as you would > expect) > - all other variables are bound to the concatenated sequence of values > bound to the variable name in the members of the group (which may cause > surprises) > > So when a group has, say, 3 members, the variable $organisations is not > any more bound to a single function item, but to a sequence of 3 > function items (which are identical). Of course, a sequence of more than > one function item cannot be called - only a single function item can be > called. In order to check that this is the correct interpretation of the > issue, you can remove the second assignment to $organisations, and > append to the invocation the predicate [1] - then it works, as then you > have again a single function item: > > <country>{$country/@name, $organizations[1]($country)}</country> > > One way to handle this in a more straightforward way is to shift the > function item into an additional, outer FLWOR level at the beginning. > While we are at it, let's shift both function items: > > let $organizations := function($country){ > $doc//organization[members/@country = $country/@id]/@abbrev/string(.)} > let $membershipPotentcy := function($orgs){ > if (count($orgs)le 0) then 'none' else if (count($orgs) le 5) then > 'few' else 'many'} > return > element Memberships { > for $countries in $doc//country > group by $mp:= $membershipPotentcy($organizations($countries)) > order by $mp > return > element {$mp} { > for $country in $countries > return > <country>{$country/@name, $organizations($country)}</country> > }} > > > Am Montag, 19. Dezember 2022 um 22:58:30 MEZ hat Graydon > <graydonish at gmail.com> Folgendes geschrieben: > > > On Mon, Dec 19, 2022 at 10:43:25PM +0100, Leo Studer scripsit: > > > In the code below I encounter the problem, that I have to define the > same xPath function variable $organizations twice and I do not > understand why. > > > In line 10, you've used a let clause to bind a variable of type function > to the name organizations. That's in context a specific FLOWR expression. > > The expression in the return clause of that FLOWR expression returns an > element constructor where the contents expression contains another FLOWR > expression. The first definition on line 10 will be out of scope in > there. > > You could define the function with scope for the module: > > declare function local:getOrganizations($in as element(country)) > as xs:string* { > let $thisCountry as xs:string := $in/@id/string(); > return $doc//organization[members/@country = > $thisCountry]/@abbrev/string(.) > }; > > And then use it in both places with the single definition. > > > -- > Graydon Saunders | graydonish at gmail.com <mailto:graydonish at gmail.com> > Þæs oferéode, ðisses swá mæg. > -- Deor ("That passed, so may this.") > >
HR
Hans-Juergen Rennau20 Dec 2022 08:46 #
PS. Pardon - a correction. The first clause may also be a sliding window or a tumbling window. Am Dienstag, 20. Dezember 2022 um 08:44:02 MEZ hat Hans-Juergen Rennau <hrennau at yahoo.de> Folgendes geschrieben: Thank you for your kind words, Gerrit! Until a few weeks ago, my use of grouping was a groping one also in my case - until I learned from the spec that the FLWOR mechanics are only fully understood if we regard the clauses as a pipeline of stations, each one transforming an input tuple stream into an output tuple stream - that's the key. And that's at first weird, as we are used to think in terms of "assignments", "loops", sorting, grouping, filtering, counting devices. But when thinking of the different clauses as just variants of a single idea - "tuple stream transformer" - everything is decomposed into tiny bits - the details of how a particular kind of clause maps its input tuples to output tuples. No interaction whatsoever between the clauses, apart from output tuple streams becoming input tuple streams. Suddenly it is not amazing any more that you can have any number of any kind of clauses, e.g. several where clauses, and that the clauses can occur at any position within the expression (except for the constraint that the first clause must be let or for). And so, for example, I had always kept my fingers crossed when adding an order by after a group by. Now I understand that once the group by has done its thing and produced an output tuple stream according to its rules, you have a tuple stream without any memory of what has happened at earlier clauses. Am Montag, 19. Dezember 2022 um 23:48:45 MEZ hat Imsieke, Gerrit, le-tex <gerrit.imsieke at le-tex.de> Folgendes geschrieben: Thank you, Hans-Jürgen, for shedding really insightful light (if there is such a thing) on this aspect of XQuery grouping, which I use gropingly (pun intended) without fully understanding it.
Quoted text · Hans-Juergen Rennau · 82 lines
On 19.12.2022 23:19, Hans-Juergen Rennau wrote: > I think the reason is a different one - it is related to the precise > semantics of the group by clause. > > The group by clause maps each group of input tuples with equal grouping > key(s) to a single output tuple, in which > - the grouping variable(s) is/are bound to the key values (as you would > expect) > - all other variables are bound to the concatenated sequence of values > bound to the variable name in the members of the group (which may cause > surprises) > > So when a group has, say, 3 members, the variable $organisations is not > any more bound to a single function item, but to a sequence of 3 > function items (which are identical). Of course, a sequence of more than > one function item cannot be called - only a single function item can be > called. In order to check that this is the correct interpretation of the > issue, you can remove the second assignment to $organisations, and > append to the invocation the predicate [1] - then it works, as then you > have again a single function item: > > <country>{$country/@name, $organizations[1]($country)}</country> > > One way to handle this in a more straightforward way is to shift the > function item into an additional, outer FLWOR level at the beginning. > While we are at it, let's shift both function items: > > let $organizations := function($country){ > $doc//organization[members/@country = $country/@id]/@abbrev/string(.)} > let $membershipPotentcy := function($orgs){ > if (count($orgs)le 0) then 'none' else if (count($orgs) le 5) then > 'few' else 'many'} > return > element Memberships { > for $countries in $doc//country > group by $mp:= $membershipPotentcy($organizations($countries)) > order by $mp > return > element {$mp} { > for $country in $countries > return > <country>{$country/@name, $organizations($country)}</country> > }} > > > Am Montag, 19. Dezember 2022 um 22:58:30 MEZ hat Graydon > <graydonish at gmail.com> Folgendes geschrieben: > > > On Mon, Dec 19, 2022 at 10:43:25PM +0100, Leo Studer scripsit: > > > In the code below I encounter the problem, that I have to define the > same xPath function variable $organizations twice and I do not > understand why. > > > In line 10, you've used a let clause to bind a variable of type function > to the name organizations. That's in context a specific FLOWR expression. > > The expression in the return clause of that FLOWR expression returns an > element constructor where the contents expression contains another FLOWR > expression. The first definition on line 10 will be out of scope in > there. > > You could define the function with scope for the module: > > declare function local:getOrganizations($in as element(country)) > as xs:string* { > let $thisCountry as xs:string := $in/@id/string(); > return $doc//organization[members/@country = > $thisCountry]/@abbrev/string(.) > }; > > And then use it in both places with the single definition. > > > -- > Graydon Saunders | graydonish at gmail.com <mailto:graydonish at gmail.com> > Þæs oferéode, ðisses swá mæg. > -- Deor ("That passed, so may this.") > >
LS
Leo Studerin reply to Hans-Juergen Rennau, 20 Dec 2022, 08:4220 Dec 2022 11:27 #
Hans-Juergen, thanks for this insight, that sheds even more light for me into the grouping with xquery...
Quoted text · Hans-Juergen Rennau · 202 lines
> On 20 Dec 2022, at 08:42, Hans-Juergen Rennau <hrennau at yahoo.de> wrote: > > > > Thank you for your kind words, Gerrit! Until a few weeks ago, my use of grouping was a groping one also in my case - until I learned from the spec that the FLWOR mechanics are only fully understood if we regard the clauses as a pipeline of stations, each one transforming an input tuple stream into an output tuple stream - that's the key. And that's at first weird, as we are used to think in terms of "assignments", "loops", sorting, grouping, filtering, counting devices. But when thinking of the different clauses as just variants of a single idea - "tuple stream transformer" - everything is decomposed into tiny bits - the details of how a particular kind of clause maps its input tuples to output tuples. No interaction whatsoever between the clauses, apart from output tuple streams becoming input tuple streams. Suddenly it is not amazing any more that you can have any number of any kind of clauses, e.g. several where clauses, and that the clauses can occur at any position within the expression (except for the constraint that the first clause must be let or for). And so, for example, I had always kept my fingers crossed when adding an order by after a group by. Now I understand that once the group by has done its thing and produced an output tuple stream according to its rules, you have a tuple stream without any memory of what has happened at earlier clauses. > > > > > > > > > > Am Montag, 19. Dezember 2022 um 23:48:45 MEZ hat Imsieke, Gerrit, le-tex <gerrit.imsieke at le-tex.de> Folgendes geschrieben: > > > > > > > > > Thank you, Hans-Jürgen, for shedding really insightful light (if there > > is such a thing) on this aspect of XQuery grouping, which I use > > gropingly (pun intended) without fully understanding it. > > > > On 19.12.2022 23:19, Hans-Juergen Rennau wrote: > > > I think the reason is a different one - it is related to the precise > > > semantics of the group by clause. > > > > > > The group by clause maps each group of input tuples with equal grouping > > > key(s) to a single output tuple, in which > > > - the grouping variable(s) is/are bound to the key values (as you would > > > expect) > > > - all other variables are bound to the concatenated sequence of values > > > bound to the variable name in the members of the group (which may cause > > > surprises) > > > > > > So when a group has, say, 3 members, the variable $organisations is not > > > any more bound to a single function item, but to a sequence of 3 > > > function items (which are identical). Of course, a sequence of more than > > > one function item cannot be called - only a single function item can be > > > called. In order to check that this is the correct interpretation of the > > > issue, you can remove the second assignment to $organisations, and > > > append to the invocation the predicate [1] - then it works, as then you > > > have again a single function item: > > > > > > <country>{$country/@name, $organizations[1]($country)}</country> > > > > > > One way to handle this in a more straightforward way is to shift the > > > function item into an additional, outer FLWOR level at the beginning. > > > While we are at it, let's shift both function items: > > > > > > let $organizations := function($country){ > > > $doc//organization[members/@country = $country/@id]/@abbrev/string(.)} > > > let $membershipPotentcy := function($orgs){ > > > if (count($orgs)le 0) then 'none' else if (count($orgs) le 5) then > > > 'few' else 'many'} > > > return > > > element Memberships { > > > for $countries in $doc//country > > > group by $mp:= $membershipPotentcy($organizations($countries)) > > > order by $mp > > > return > > > element {$mp} { > > > for $country in $countries > > > return > > > <country>{$country/@name, $organizations($country)}</country> > > > }} > > > > > > > > > Am Montag, 19. Dezember 2022 um 22:58:30 MEZ hat Graydon > > > <graydonish at gmail.com> Folgendes geschrieben: > > > > > > > > > On Mon, Dec 19, 2022 at 10:43:25PM +0100, Leo Studer scripsit: > > > > > > > In the code below I encounter the problem, that I have to define the > > > same xPath function variable $organizations twice and I do not > > > understand why. > > > > > > > > > In line 10, you've used a let clause to bind a variable of type function > > > to the name organizations. That's in context a specific FLOWR expression. > > > > > > The expression in the return clause of that FLOWR expression returns an > > > element constructor where the contents expression contains another FLOWR > > > expression. The first definition on line 10 will be out of scope in > > > there. > > > > > > You could define the function with scope for the module: > > > > > > declare function local:getOrganizations($in as element(country)) > > > as xs:string* { > > > let $thisCountry as xs:string := $in/@id/string(); > > > return $doc//organization[members/@country = > > > $thisCountry]/@abbrev/string(.) > > > }; > > > > > > And then use it in both places with the single definition. > > > > > > > > > -- > > > Graydon Saunders | graydonish at gmail.com <mailto:graydonish at gmail.com> > > > Þæs oferéode, ðisses swá mæg. > > > -- Deor ("That passed, so may this.") > > > > > > > > > > > > > >

⚡Generated with XQuery