XQuery Introduction
This article introduces the main concepts of XQuery. It presents the language as it is today, including the features of the upcoming version 4.0, and links to the pages that cover each topic in detail.
The changes between the language versions are summarized in XQuery 4.0, XQuery 3.1 and XQuery 3.0.
Getting Started
Running Queries
Queries can be written and run in the editor of the Graphical User Interface. On the Command-Line Interface, a query can be supplied as a string or as a file:
basex "1 + 2"
basex query.xq
Sample Data
Most examples in this article refer to the following document:
<feminists>
<feminist born="1759" died="1797" country="England">
<name>Mary Wollstonecraft</name>
<work year="1792" xml:lang="en">A Vindication of the Rights of Woman</work>
</feminist>
<feminist born="1831" died="1897" country="India">
<name>Savitribai Phule</name>
<work year="1854" xml:lang="mr">काव्यफुले</work>
</feminist>
<feminist born="1875" died="1907" country="China">
<name>Qiu Jin</name>
</feminist>
<feminist born="1908" died="1986" country="France">
<name>Simone de Beauvoir</name>
<work year="1949" xml:lang="fr">Le Deuxième Sexe</work>
</feminist>
<feminist born="1931" died="2021" country="Egypt">
<name>Nawal El Saadawi</name>
<work year="1975" xml:lang="ar">امرأة عند نقطة الصفر</work>
</feminist>
<feminist born="1952" died="2021" country="USA">
<name>bell hooks</name>
<work year="1981" xml:lang="en">Ain't I a Woman?</work>
<work year="2000" xml:lang="en">Feminism Is for Everybody</work>
</feminist>
<feminist born="1959" country="Guatemala">
<name>Rigoberta Menchú</name>
<work year="1983" xml:lang="es">Me llamo Rigoberta Menchú y así me nació la conciencia</work>
</feminist>
<feminist born="1977" country="Nigeria">
<name>Chimamanda Ngozi Adichie</name>
<work year="2013" xml:lang="en">Americanah</work>
<work year="2014" xml:lang="en">We Should All Be Feminists</work>
</feminist>
<feminist born="1997" country="Pakistan">
<name>Malala Yousafzai</name>
<work year="2013" xml:lang="en">I Am Malala</work>
</feminist>
</feminists>
Save it as feminists.xml and create a database from it, either via Database → New in the GUI or with the CREATE DB command. An opened database serves as context value, so paths can start with / or //. On the command line, the document can be assigned as context value with -i:
basex -i feminists.xml "count(//feminist)"
Values
Sequences
Every expression evaluates to a sequence of zero or more items. An item is an atomic value (a string, a number, a date, …), a node, or a function item (which includes maps and arrays). A single item is the same as a sequence with one item, and sequences are never nested:
(: a sequence with a string, an integer and a boolean :)
('Qiu Jin', 1875, true()),
(: nested sequences are flattened: 1, 2, 3 :)
(1, (2, 3), ()),
(: a range of integers :)
1 to 5
The empty sequence () represents the absence of a value. Most operations return the empty sequence if an operand is empty.
Atomic Values
Strings and numbers can be written as literals; values of other types are created with constructor functions. cast as converts a value to another type, and instance of checks its type:
'Simone de Beauvoir', (: xs:string :)
1908, (: xs:integer :)
0.5, (: xs:decimal :)
1e3, (: xs:double :)
xs:date('1908-01-09'), (: xs:date :)
'1949' cast as xs:integer, (: 1949 :)
1949 instance of xs:integer (: true :)
Strings
String literals are enclosed in single or double quotes. A quote of the same kind is escaped by doubling it:
"Ain't I a Woman?",
'Ain''t I a Woman?'
Strings are concatenated with ||. String templates, enclosed in backticks, embed the results of expressions in curly braces. Both expressions return Malala Yousafzai was born in 1997.:
let $name := 'Malala Yousafzai'
let $born := 1997
return (
$name || ' was born in ' || $born || '.',
`{ $name } was born in { $born }.`
)
A selection of the string functions:
upper-case('bell hooks'), (: 'BELL HOOKS' :)
tokenize('Chimamanda Ngozi Adichie'), (: 'Chimamanda', 'Ngozi', 'Adichie' :)
contains('We Should All Be Feminists', 'Feminist'), (: true :)
substring-after('Rigoberta Menchú', ' '), (: 'Menchú' :)
replace('Le Deuxième Sexe', '(\w+)$', '[$1]'), (: 'Le Deuxième [Sexe]' :)
string-length('秋瑾') (: 2 (Qiu Jin in Chinese) :)
Strings consist of Unicode characters, and regular expressions are used for pattern matching.
Paths
Path Expressions
Path expressions navigate through XML documents. Steps are separated by /, // selects descendants at any depth, @ selects attributes, and .. the parent. Predicates in square brackets filter the results:
(: all 9 names :)
//feminist/name,
(: <name>Simone de Beauvoir</name> :)
//feminist[@country = 'France']/name,
(: the names of all feminists who have published a work in 2013 :)
//work[@year = 2013]/../name,
(: the names of all living feminists, as strings :)
//feminist[not(@died)]/name/string(),
(: 'fr: Le Deuxième Sexe', 'es: Me llamo Rigoberta Menchú y así me nació la conciencia' :)
//work[lang('fr') or lang('es')] ! `{ @xml:lang }: { . }`
A numeric predicate selects an item by its position. It applies to the step it is attached to, so parentheses are required to select from the whole result:
(: the first work of each feminist: 8 works :)
//feminist/work[1],
(: the first work in the document :)
(//work)[1]
If a node is used where an atomic value is expected, its string value is taken. This is called atomization. It is the reason why @born < 1900 compares numbers and why $work || '!' concatenates the text of an element. The simple map operator ! evaluates an expression for each item of a sequence:
(: 'Mary Wollstonecraft (England)', 'Savitribai Phule (India)', … :)
//feminist ! (name || ' (' || @country || ')'),
(: 10 :)
count(//work),
(: the name of the eldest feminist: 'Mary Wollstonecraft' :)
//feminist[@born = min(//@born)]/name/string()
With XQuery 4.0, the same syntax can be used to navigate JSON data; see JNodes.
Comparisons
General comparisons (=, !=, <, <=, >, >=) compare sequences: they are true if any pair of items from the two operands matches. Value comparisons (eq, ne, lt, le, gt, ge) compare single values:
(: true: at least one work is from 2013 :)
//work/@year = 2013,
(: true as well: at least one work is not from 2013 :)
//work/@year != 2013,
(: true :)
'Qiu Jin' eq 'Qiu Jin'
A value comparison with more than one item on either side raises an error.
FLWOR Expressions
FLWOR expressions (pronounced “flower”) iterate over sequences, bind variables, and filter, sort and group the results. The name is derived from the clauses for, let, where, order by and return:
for $work in //work
let $author := $work/../name
where $work/@year < 1980
order by $work/@year
return `{ $work } ({ $work/@year }) by { $author }`
The result is:
A Vindication of the Rights of Woman (1792) by Mary Wollstonecraft
काव्यफुले (1854) by Savitribai Phule
Le Deuxième Sexe (1949) by Simone de Beauvoir
امرأة عند نقطة الصفر (1975) by Nawal El Saadawi
After a group by clause, the variables of the preceding clauses are bound to all items of the respective group:
for $feminist in //feminist
group by $century := $feminist/@born idiv 100 + 1
order by $century
return `Century { $century }: { string-join($feminist/name, ', ') }`
The result is:
Century 18: Mary Wollstonecraft
Century 19: Savitribai Phule, Qiu Jin
Century 20: Simone de Beauvoir, Nawal El Saadawi, bell hooks, Rigoberta Menchú, Chimamanda Ngozi Adichie, Malala Yousafzai
Further clauses exist for counting, windowing and early termination; see FLWOR Expressions and XQuery 3.0.
Conditions
Conditional Expressions
In a conditional expression, the else branch can be omitted if curly braces are used. The otherwise operator returns its second operand if the first one is empty:
let $died := //feminist[name = 'Malala Yousafzai']/@died/string()
return (
(: 'alive' :)
if ($died) then `died in { $died }` else 'alive',
(: empty sequence :)
if ($died) { `died in { $died }` },
(: 'alive' :)
$died otherwise 'alive'
)
For choosing between more than two branches, switch and typeswitch expressions are available.
Quantifiers
(: true :)
some $feminist in //feminist satisfies $feminist/@country = 'Egypt',
(: false :)
every $work in //work satisfies $work/@year > 1800
Node Constructors
XML can be written directly in a query. Expressions in curly braces are evaluated, both in element content and in attribute values:
<ul>{
for $feminist in //feminist[not(@died)]
return <li born='{ $feminist/@born }'>{ string($feminist/name) }</li>
}</ul>
The result is:
<ul>
<li born="1959">Rigoberta Menchú</li>
<li born="1977">Chimamanda Ngozi Adichie</li>
<li born="1997">Malala Yousafzai</li>
</ul>
With computed constructors, names can be generated as well:
(: <country name="Pakistan">Malala Yousafzai</country> :)
element { 'country' } {
attribute { 'name' } { 'Pakistan' },
text { 'Malala Yousafzai' }
}
Functions
Function Calls
The standard functions can be called without prefix. BaseX provides many more function modules, such as db: for databases or file: for the file system. Arguments can be passed by position or by name, and the arrow operators pass on a value to the next function:
(: 'काव्यफुले; Le Deuxième Sexe' :)
string-join((//work)[2 to 3], separator := '; '),
(: 5 :)
'We Should All Be Feminists' => tokenize() => count(),
(: 'NAWAL EL SAADAWI', 'BELL HOOKS' :)
//feminist[@died = 2021]/name =!> upper-case(),
(: 222 :)
//work/@year -> (max(.) - min(.))
The arrow operator => passes on the whole sequence, whereas the mapping arrow =!> calls the function for each item. The pipeline operator -> binds the value to the context value ..
Declaring Functions
Functions are declared in the prolog, which precedes the query body. Parameters can have default values:
declare function local:initials(
$name as xs:string,
$separator as xs:string := '.'
) as xs:string {
string-join(tokenize($name) ! (substring(., 1, 1) || $separator))
};
(: 'S.d.B.' :)
local:initials('Simone de Beauvoir'),
(: 'bh' :)
local:initials('bell hooks', separator := '')
Function Items
Functions are values: they can be bound to variables and passed on to other functions. Inline functions are written with fn. If the parameter is omitted, the argument is bound to the context value:
(: 'Rigoberta Menchú', 'Chimamanda Ngozi Adichie', 'Malala Yousafzai' :)
let $alive := fn($feminist) { empty($feminist/@died) }
return filter(//feminist, $alive)/name/string(),
(: 'Qiu Jin' :)
sort(//name, key := fn { string-length() })[1] => string()
More functions that take function items as arguments are listed in Higher-Order Functions.
Maps and Arrays
A map is a set of key/value entries, and an array is a list of members. Both can be nested and are accessed with the lookup operator ?:
let $feminist := {
'name': 'Chimamanda Ngozi Adichie',
'born': 1977,
'works': [ 'Americanah', 'We Should All Be Feminists' ]
}
return (
$feminist?name, (: 'Chimamanda Ngozi Adichie' :)
$feminist?works?2, (: 'We Should All Be Feminists' :)
array:size($feminist?works) (: 2 :)
)
Maps and arrays are the natural representation of JSON data. The following query converts the living feminists to JSON:
array {
for $feminist in //feminist[not(@died)]
return {
'name': string($feminist/name),
'born': xs:integer($feminist/@born),
'works': array { $feminist/work ! string() }
}
} => serialize({ 'method': 'json', 'indent': true() })
Maps are also useful for grouping and fast lookups:
(: 'Americanah', 'I Am Malala' :)
let $works := map:build(//work, fn { string(@year) }, fn { string() })
return $works?('2013')
for member iterates over the members of an array, and for key … value over the entries of a map:
(: 'Qiu Jin: 1875', 'Malala Yousafzai: 1997' :)
for key $name value $born in { 'Qiu Jin': 1875, 'Malala Yousafzai': 1997 }
return `{ $name }: { $born }`
See XQuery 3.1 for an introduction, XQuery 4.0 for recent additions, and the Map Functions and Array Functions. JSON Functions convert between JSON and XQuery values.
Modules
The prolog of a query can contain declarations for namespaces, options, variables and functions. An external variable can be assigned a value when the query is run:
declare variable $country external := 'France';
(: 'Simone de Beauvoir' :)
//feminist[@country = $country]/name/string()
On the command line, values are assigned with -b:
basex -b country=India -i feminists.xml query.xq
Functions and variables can be placed in library modules, which are identified by a namespace URI:
(: feminists.xqm :)
module namespace fem = 'http://basex.org/examples/feminists';
declare function fem:alive($feminists as element(feminist)*) as element(feminist)* {
$feminists[empty(@died)]
};
A main module imports the library module, either from a relative location or from the Repository:
import module namespace fem = 'http://basex.org/examples/feminists' at 'feminists.xqm';
(: 'Rigoberta Menchú', 'Chimamanda Ngozi Adichie', 'Malala Yousafzai' :)
fem:alive(//feminist)/name/string()
Errors
Errors can be caught with try/catch. An error code or a wildcard specifies which errors are caught, and variables like $err:code and $err:description provide details on the error:
(: 'MDCCXCII is not an integer.' :)
try {
xs:integer('MDCCXCII')
} catch err:FORG0001 {
'MDCCXCII is not an integer.'
},
(: 'local:unknown' :)
try {
error(xs:QName('local:unknown'), 'Unknown feminist.')
} catch * {
string($err:code)
}
All error codes are listed in XQuery Errors.
Next Steps
- Databases: Store and manage documents.
- Indexes: Speed up queries on large databases.
- Updates: Modify documents and databases with XQuery Update.
- Full-Text: Search texts with stemming, wildcards and scoring.
- Web Application and RESTXQ: Build web applications with XQuery.
- XQuery Extensions: Options, pragmas and annotations provided by BaseX.