13

I want to create a counter in xquery. My initial attempt looked like the following:

let $count := 0
for $prod in $collection
let $count := $count + 1
return 
<counter>{$count }</counter>

Expected result:

<counter>1</counter>
<counter>2</counter>  
<counter>3</counter>

Actual result:

<counter>1</counter>
<counter>1</counter>  
<counter>1</counter>

The $count variable either failing to update or being reset. Why can't I reassign an existing variable? What would be a better way to get the desired result?

7 Answers 7

22

Try using 'at':

for $d at $p in $collection
return 
element counter { $p }

This will give you the position of each '$d'. If you want to use this together with the order by clause, this won't work since the position is based on the initial order, not on the sort result. To overcome this, just save the sorted result of the FLWOR expression in a variable, and use the at clause in a second FLWOR that just iterates over the first, sorted result.

let $sortResult := for $item in $collection
                   order by $item/id
                   return $item

for $sortItem at $position in $sortResult
return <item position="{$position}"> ... </item>
0
9

As @Ranon said, all XQuery values are immutable, so you can't update a variable. But if you you really need an updateable number (shouldn't be too often), you can use recursion:

declare function local:loop($seq, $count) {
  if(empty($seq)) then ()
  else
    let $prod  := $seq[1],
        $count := $count + 1
    return (
      <count>{ $count }</count>,
      local:loop($seq[position() > 1], $count)
    )
};

local:loop($collection, 0)

This behaves exactly as you intended with your example.

In XQuery 3.0 a more general version of this function is even defined in the standard library: fn:fold-right($f, $zero, $seq)

That said, in your example you should definitely use at $count as shown by @tohuwawohu.

6

Immutable variables

XQuery is a functional programming language, which involves amongst others immutable variables, so you cannot change the value of a variable. On the other hand, a powerful collection of functions is available to you, which solves lots of daily programming problems.

let $count := 0
for $prod in $collection]
  let $count := $count + 1
return 
<counter>{$count }</counter>

let $count in line 1 defines this variable in all scope, which are all following lines in this case. let $count in line 3 defines a new $count which is 0+1, valid in all following lines within this code block - which isn't defined. So you indeed increment $count three times by one, but discard the result immediatly.

BaseX' query info shows the optimized version of this query which is

for $prod in $collection
  return element { "counter" } { 1 }

The solution

To get the total number of elements in $collection, you can just use

return count($collection)

For a list of XQuery functions, you could have a look at the XQuery part of functx which contains both a list of XQuery functions and also some other helpful functions which can be included as a module.

1
  • I'm not sure that your example runs, or, at least, I have trouble running it. would you elaborate?
    – Thufir
    Commented Feb 19, 2020 at 15:37
6

Specific to MarkLogic you can also use xdmp:set. But this breaks functional language assumptions, so use it conservatively.

http://docs.marklogic.com/5.0doc/docapp.xqy#display.xqy?fname=http://pubs/5.0doc/apidoc/ExsltBuiltins.xml&category=Extension&function=xdmp:set

For an example of xdmp:set in real-world code, the search parser https://github.com/mblakele/xqysp/blob/master/src/xqysp.xqy might be helpful.

6

All the solution above are valid but I would like to mention that you can use the XQuery Scripting extension to set variable values:

variable $count := 0;

for $prod in (1 to 10)
return {
  $count := $count + 1;
  <counter>{$count}</counter>
}

You can try this example live at http://www.zorba-xquery.com/html/demo#twh+3sJfRpHhZR8pHhOdsmqOTvQ=

3
  • 3
    XQuery Scripting is an extension offered by some XQuery implementations. It is especially fine if you want to do both updates and return elements at the same time (eg. for web services), but prevents lots of possible optimizations. You should stick to basic XQuery whenever possible.
    – Jens Erat
    Commented Apr 24, 2012 at 14:41
  • The live example has expired (404) :( Commented Dec 30, 2014 at 15:57
  • this crashes at least with BaseX as per the comment by @JensErat -- but how else is the accomplished?
    – Thufir
    Commented Feb 19, 2020 at 15:19
4

Use xdmp:set instead of the below query

let $count := 0
for $prod in (1 to 4)
return ( xdmp:set($count,number($count+1)) ,<counter>{$count }</counter> 
2

I think you are looking for something like:

XQUERY:

for $x in (1 to 10)
return
  <counter>{$x}</counter>

OUTPUT:

<counter>1</counter>
<counter>2</counter>
<counter>3</counter>
<counter>4</counter>
<counter>5</counter>
<counter>6</counter>
<counter>7</counter>
<counter>8</counter>
<counter>9</counter>
<counter>10</counter>

Not the answer you're looking for? Browse other questions tagged or ask your own question.