XQuery/Sequences

Motivation
You want to manipulate a sequence of items. These items may be very similar to each other or they may be of very different types.

Method
We begin with some simple examples of sequences. We then look at the most common sequence operators. XQuery uses the word sequence as a generic name for an ordered container of items.

Understanding how sequences work in XQuery is central to understanding how the language works. The use of generic sequences of items is central to functional programming and stands in sharp contrast to other programming languages such as Java or JavaScript that provide multiple methods and functions to handle key-value pairs, dictionaries, arrays and XML data. The wonderful thing about XQuery is that you only need to learn one set of concepts and a very small list of functions to learn how to quickly manipulate data.

Creating sequences of characters and strings
You use the parenthesis to contain a sequence, commas to delimit items and quotes to contain string values:

Note that you can use single or double quotes, but for most character strings a single quote is used.

You can also intermix data types. For example the following sequence has three strings and three integers in the same sequence.

You can then pass the sequence to any XQuery function that works with sequences of items. For example the "count" function takes a sequence as an input and returns the number of items in the sequence.

To see the results of these items you can create a simple XQuery that displays the items using a FLWOR statement.

Viewing items in a sequence
[/basics/seq.xq Execute]

Viewing select items in a sequence
Items within a sequence can be selected using a predicate expression.

Items can be selected by position ( 1-based ):

[/basics/seq-1.xq Execute]

Results:

or by value:

[/basics/seq-2.xq Execute]

Results:

Adding XML elements to your sequence
You can also store XML elements in a sequence:

Although you can use parenthesis to create a sequence of XML items, we can also use XML tags to begin and end a sequence and to store all items as XML elements.

Here is an example of this:

One layout convention is to put all individual items in their own item element tags and to place each item on a separate line if the list of items gets long:

The following FLWOR expression can then be used to display each of these items:

[/basics/seq-3.xq Execute]

This will return the following XML:

Note that when the resulting XML is returned, only double quotes are present in the output.

Common Sequence Functions
There are only a handful of functions you will need to use with sequences. We will review these functions and also show you how to create new functions using combinations of these functions.

Here are the three most common non-mathematical functions used with sequences. These three are the real workhorses of XQuery sequences. You can spend days writing XQueries and never need functions beyond these three functions:

count($seq as item*) - used to count the number of items in a sequence. Returns a non-negative integer.

distinct-values($seq as item*) - used to remove duplicate items in a sequence. Returns another sequence.

subsequence($seq as item*, $startingLoc as xs:double, $length as xs:double) - used to return only a subset of items in a sequence. Returns another sequence. [type xs:double for $startingLoc and $length seems strange; these will be rounded to the nearest integer]

All of these functions have a datatype of item* which is read zero or more items. Note that both the "distinct-values" function and the "subsequence" function both take in a sequence and return a sequence. This comes in very handy when you are creating recursive functions. Along with "count" are also a few sequence operators that calculate sums and average, min and max:

Along with "count" are also a few sequence operators that calculate sums and average, min and max:

sum($seq as item*) - used to sum the values of numbers in a sequence

avg($seq as item*) - used to calculate the average (arithmetic mean) of numbers in a sequence

min($seq as item*) - used to find the minimum value of a sequence of numbers

max($seq as item*) - used to find the maximum value of a sequence of numbers

These functions are designed to work on numeric values of items, and all return numeric values (you many want to use the "number" function when working with strings of items).

You may find that you can perform many tasks just by learning these few XQuery functions. You can also create most other sequence operators from these functions.

Occasionally Used Sequence Functions
In addition there are some functions which return modified versions of the original sequence:

insert-before($seq as item*, $position as xs:integer, $inserts as item*) - for inserting new items anywhere in a sequence

remove($seq as item*, $position as xs:integer) - removes an item from a sequence

reverse($seq as item*) - reverses the order of items in a sequence

index-of($seq as anyAtomicType*, $target as anyAtomicType) - returns a sequence of integers that indicate where an item is within a sequence (index counting starts at 1)

The following two functions can be used in conjunction with the bracketed predicate expression '[ ]', which operates on an item's position information within a sequence: last - when used in a predicate returns the last item in a sequence so (1,2,3)[last] returns 3

position - this function is used to output the position in a FLWOR statement, so      for $x in ('a', 'b', 'c', 'd')[position mod 2 eq 1] return $x returns ('a', 'c')

Example of Sum Function
Let's imagine that we have a basket of items and we want to count the total items in the basket:

To sum the counts of each item we will need to use an XPath expression to get the item counts:

$basket/item/count

We can then total this sequence and return the result: [/basics/seq-4.xq Execute]

The result is 37.

Finding if an Item is in a Sequence
Users find that XQuery is easy to use since it tries to do the right thing based on the data types you give it. XQuery checks if you have a sequence, an XML element or a single string, and performs the most logical operation. This behaviour keeps your code compact and easy to read. If you are comparing a element with a string, XQuery will look inside the element and get the string for you so you do not explicitly need to tell XQuery to use the content of an element. When comparing a sequence of items with a string with the "=" operator, XQuery will look for that string in the sequence and return "true" if the string is in the sequence. It just works!

For example, given the sequence: let $sequence := ('a', 'b', 'c', 'd', 'e', 'f')

If we execute:

{$sequence = 'd'}

[/basics/seq-10.xq Execute]

we get: "true" is returned because 'd ' is in the sequence. However:

{$sequence = 'x'}

Will return "false" because 'x' is not in the sequence.

You can use the "index-of" function to get the position of an item in a sequence. If the item is in the sequence then it will return a non-zero integer, if not then the empty sequence.

[/basics/seq-11.xq Execute ]

Sorting Sequence
There is no "sort" function in XQuery. To sort your sequence you just create a new sequence that contains a FLWOR loop of your items with the order statement in it.


 * For example if you have a list of items with titles as one of the elements you can use the following to sort the items by title:

let $sorted-items := for $item in $items order by $item/title/text return $item


 * You can also use "descending" with "order by" to reverse the order:

for $item in $items order by name($item) descending return $item


 * You can sort with your own order by creating a separate order sequence and then using the "index-of" function in the "order by" clause to indicate, according to the specified order, how to arrange/return $i items from the queried sequence:

for $i in /root/* let $order := ("b", "a", "c") order by index-of($order, $i) return $i

Set Operations: Concatenation, Unions, Intersections and Exclusions
XQuery also provides functions to join sets and to find items that are in both sets.

Assume that we have two sets that contain overlapping items: let $sequence-1 := ('a', 'b', 'c', 'd') let $sequence-2 := ('c', 'd', 'e', 'f')

Concatenation
You can concatenate  both sequences by doing the following:

let $both := ($sequence-1, $sequence-2)

or, alternatively:

for $item in ( ($sequence-1, $sequence-2)) return $item

Which will return:

a b c d c d e f

Union
You can also create a union  set that removes duplicates for all items that are in both sets by using the "distinct-values" function:

distinct-values(($sequence-1, $sequence-2))

This will return the following:

a b c d e f

Note that the 'c d' pair is not repeated.

Intersection
You can now use a variation of this to find the intersection  of all items in $sequence-1 that are also in $sequence-2:

distinct-values($sequence-1[.=$sequence-2])

This will return only items that are in BOTH $sequence-1 AND $sequence-2:

c d

The way you read this is "for each item in $sequence-1, if this item (.) is also in $sequence-2, then return it."

Exclusion
The last set operation you might want to do is the exclusion  function, where we find all items in the first sequence that are NOT in the second sequence:

distinct-values($sequence-1[not(.=$sequence-2)])

This will return:

a b

Returning Duplicates
The following example returns a list of all items that occur more than once in a sequence. This process is known as "duplicate detection."

Method 1: using distinct-values
We can use the distinct-values function on a sequence to find all the unique values in a sequence. We can then check to see if there are any items that occur twice.

[/basics/seq-7.xq Execute]

This returns:

You can also remove all duplicates just by moving the $item to the "else" portion of the "if" statement and putting "" in the "then" portion of the "if" statement:

Method 2: Using index-of
The following method uses the "index-of" function to find the duplicates:

[/basics/seq-8.xq Execute]

Creating Sequences of Letters
You can use the codepoints functions to convert letters to numbers and numbers to letters. For example, to generate a list of all the letters from 'a' to 'z' you can write the following XQuery:

This will return the sequence rendered as text:

a b c d e f g h i j k l m n o p q r s t u v w x y z

[/basics/codepoints.xq Execute]

Creating Letter Collections
You can also use this to create a list of subcollections:

This process is very common way to store related files in subcollections.

Counting Items
It is very common to need to count your items as you go through them. You can do this by adding the "at $count" to your FLWOR loop:

Note that the modulo function:

($count mod 2)

returns 1 for odd numbers, which gets converted to "true", and zero for even numbers, which gets converted to "false". You can use this technique to make alternating rows of tables different colors.

Removing Numbers
You can filter out specific types of numbers by simply adding a predicate to the end of a sequence of numbers. For example, if you wanted to remove all odd numbers from a sequence of numbers, the expression you would use would be:

$my-sequence-of-integers[. mod 2 = 0]

Which says "of all the current numbers in the sequence, if the current number modulo 2 has a value of 0 (i.e., 'if the current number is not odd'), then keep it in the result sequence".

Here is a full example:

[/basics/seq-5.xq Execute]

which returns:

Converting Sequences to a String
One of the most common things we do with a sequence is to convert it to a single string for display. And we frequently want to put a separator string between the values but not after the final value. XQuery includes a very handy function for this called "string-join". Its format is:

string-join($input-sequence as item*, $separator-string as xs:string) as xs:string

For example, the output of:

let $sequence := ('a', 'b', 'c') return string-join($sequence, '--')

would be:

a--b--c

Note that there is no "--" after the last string. The separator is only used between the items of a sequence.

Combining Sequence Operations
It is very common to need to "chain" sequence operations in a linear sequence of steps. For example if you wanted to sort a list of sequences and then select the first 10 items your query might look like the following:

[/basics/seq-6.xq Execute]

This technique can be used to paginate results for search results so that users see the first 10 results of a search. A control can then be used to get the next N items from the search result.