XQuery Reference

XQuery Recipes

Task-shaped answers to the jobs people actually bring to XQuery: unique values, joins across documents, top-N lists, reshaping XML into a new format, and per-category counts. Each recipe shows the XQuery and, where one exists, the plain XPath equivalent — so you can see exactly where XPath runs out and XQuery takes over. Every example runs against a built-in sample in the Sandbox, so you can open it, change it, and watch the result.

distinct-values(…)

Return a list of unique values

`distinct-values()` atomises its argument and drops duplicates, so it works identically in XPath 3.1 and XQuery. Reach for XQuery only when you want to *label* the result, e.g. `for $c in distinct-values(//recipe/cuisine) return <cuisine>{$c}</cuisine>`. Note that the result is a sequence of atomic values, not nodes — so you cannot navigate from it back into the document.

Example: for $c in distinct-values(//recipe/cuisine) return <cuisine>{$c}</cuisine>

join two documents

Correlate items from two sequences on a shared key

Two `for` clauses separated by a comma produce every combination, and `where` keeps the pairs that match on a key — the XQuery form of an inner join. XPath can express simple correlations with a predicate, but once you need fields from *both* sides in the output, you need the two variable bindings a FLWOR gives you. Add `allowing empty` to the second clause for a left outer join.

Example: for $d in //department, $e in $d/employee return <row dept="{$d/@name}">{string($e/name)}</row>

sort and take the top N

Rank a sequence, then keep only the first few

`order by` is the only way to leave document order, and a predicate applied to the parenthesised FLWOR trims the sorted sequence. Keep the parentheses: without them the predicate binds to the `return` expression and filters *within* each iteration instead of across the ranked list. `subsequence($seq, 1, 3)` does the same job and reads better for larger offsets.

Example: (for $a in //album order by number($a/@year) descending return $a/title)[position() <= 3]

reshape XML into a new element

Transform input markup into a different output shape

A constructor wrapping a FLWOR is the workhorse XQuery pattern: the outer element gives the result a single root, and the loop fills it with one child per input item. This is the thing XPath simply cannot do — XPath 3.1 can build maps and arrays, but it has no element constructors. Enclosed expressions inside attribute values are atomised to strings.

Example: <report>{for $r in //recipe return <dish name="{$r/name}">{count($r//ingredient)}</dish>}</report>

count per category

One summary row per distinct key, without `group by`

This is the engine-portable form of `group by`: loop over the distinct keys, then re-select the members of each group inside the body. It is more verbose than `group by $c := $r/cuisine`, but it runs everywhere — including in this Sandbox, whose engine parses `group by` without implementing it. See **Engine Support & Limits** for the full list of clauses that need this treatment.

Example: for $c in distinct-values(//recipe/cuisine) return <cuisine name="{$c}">{count(//recipe[cuisine = $c])}</cuisine>

string-join(…, ', ')

Flatten a sequence into one delimited string

`string-join()` is shared ground with XPath 3.1, and it is the usual last step of a report: collapse a sequence of values into a single readable string. Combine it with a `let` inside a FLWOR when the sequence you want to flatten is per-iteration, e.g. `let $ing := $r//ingredient return string-join($ing, '; ')`.

Example: string-join(//recipe/name, ', ')

compute an average safely

Aggregate numerically without dividing by zero

`avg()`, `sum()`, `min()` and `max()` atomise their input, so untyped attribute values are treated as strings until you convert them. Filtering with `castable as` keeps non-numeric values from raising a type error, and `avg()` on an empty sequence returns the empty sequence rather than zero — check for it with `if (empty($seq)) then 0 else avg($seq)`.

Example: avg(//album/@year[. castable as xs:decimal])

← Back to the XQuery reference