XQuery multi-filter search algorithm

88 views Asked by At

I am new to XQuery 3.0 and trying to create a simple multi-filter search algorithm.

What I want to do is, check if a parameter is supplied and if so, add it to the query.

This is what I have in my mind:

let $query := doc("music.xml")//music/album

if (request:get-parameter('type', false)) then
    let $query := $query[ @type=request:get-parameter('type', '') ]
else
    let $query := $query

if (request:get-parameter('title', false)) then
    let $query := $query[ @title=request:get-parameter('title', '') ]
else
    let $query := $query

if (request:get-parameter('artist', false)) then
    let $query := $query[ @artist=request:get-parameter('artist', '') ]
else
    let $query := $query

This is incorrect, obviously. Any help to make it correct?

1

There are 1 answers

2
Joe Wicentowski On BEST ANSWER

The simplest pattern would be as follows, creating variables for each possible request parameter (supplying a default value for each as the empty sequence), and then in the return clause, check for the presence of each parameter, one at a time:

xquery version "3.0";

let $albums := doc("music.xml")//music/album
let $type := request:get-parameter('type', ())
let $title := request:get-parameter('title', ())
let $artist := request:get-parameter('artist', ())
return
    if ($type) then
        $albums[type = $type]
    else if ($title) then
        $albums[title = $title]
    else if ($artist) then
        $albums[artist = $artist]
    else
        $albums

This code assumes that <type>, <title>, and <artist> are child elements of <album>, and we check for an exact match for the parameters supplied. You could change the title = $title comparison to contains(title, $title) for case sensitive literal string matches, matches(title, $title, 'i') for case insensitive regex searches, or a full text index like ft:query(title, $title) if you configured your index for a full text index on <title> elements, etc.

The weakness of this approach is that we've hard-coded a strict order of priority for the parameters that affect in the query. If a type parameter is supplied, the queries on title and album wouldn't be considered even if they were supplied.

To chain them together so that any and all supplied parameters are queried, you could take the following approach:

xquery version "3.0";

let $albums := 
    <albums>
        <album><type>country</type><title>Holiday Classics</title><artist>Jane</artist></album>
        <album><type>country</type><title>Lonesome Cowboy</title><artist>Jim</artist></album>
        <album><type>country</type><title>Lonesome Holiday</title><artist>Jane</artist></album>
    </albums>//album
let $type := request:get-parameter('type', ())
let $title := request:get-parameter('title', ())
let $artist := request:get-parameter('artist', ())
return
    $albums
        [if ($type) then type = $type else true()]
        [if ($title) then title = $title else true()]
        [if ($artist) then artist = $artist else true()]

I supplied sample data just to confirm for myself and others testing the code that this works. The comparisons in the return clause are only evaluated if the parameters are supplied. This code assumes at most one value per parameter; some adjustments would be need to made if you were allowing multiple values for each parameter.