Case Query Language

Underpinning the advanced, flexible search capabilities of the Case List Explorer is the Case Query Language (CQL). This page describes the syntax and capabilities of the language as well as its limitations. 

Syntax

  • Available operators:  and, or , =, !=, <, <=, >, >=, (, )

  • Parentheses: () can be used to group logic and make complex expressions

  • Dates: dates can be filtered with format 'YYYY-MM-DD'.  This must include the apostrophes around the date.  The date()  function may be used to validate a date value: date('2021-08-20') 

Examples
name = 'john' and age > 10 region = 'london' and registration_date < '2019-12-01' (sex = 'm' and weight < 23) or (sex = 'f' and weight < 20) status = 'negative' and subcase-exists('host', @case_type = 'lab_result' and result = 'positive')

Supported Functions

The following functions are supported:

date

  • Behavior: Will convert a string or a number value into an equivalent date.  Will throw an error if the format of the string is wrong or an invalid date is passed.  

  • Return: Returns a date

  • Arguments:  The value to be converted (either a string in the format YYYY-MM-DD or a number).  Numbers are interpreted as days since Jan 01 1970.

  • Usage: date(value_to_convert)

today

  • Return:  Returns the current date according in the timezone of the project space.

  • Arguments: None

  • Usage: today()

  • Note: When comparing the output of this function to a value with a date and a time by using the =  operator, this function returns the current date at midnight. For example, last_modified=today() will look for an exact match between the date and time in last_modified and the current date exactly at midnight.

not

  • Behavior: Invert a boolean search expression

  • Arguments: The expression to invert

  • Usage: not(is_active = 0 and stock_level < 15)

starts-with

  • Behavior:  Match cases where a multi-select case property value begins with the given argument value.

  • Arguments:  Two arguments, the multi-select case property and the value to check.

  • Notes:

    • This filter is case sensitive

    • Using this filter may impact the performance of your query

  • Usage:

    • starts-with(social_security_num, "123")

    • starts-with(timezone, "Africa/")

selected

  • Behavior:  Match cases where a multi-select case property contains the given value. The behavior of this function matches that of the selected-any  function.

  • Return: True if that particular value is present in the case property.  Otherwise False.  

  • Arguments:  Two arguments, the multi-select case property and the value to check.

  • Notes:

    • If the 'value to check' contains spaces, each word will be considered independently as in 'select-any'

  • Usage:

    • selected(tests_performed, "testA")

    • selected(tests_performed, "testA testB")

      • This works the same as the 'selected-any' function. Best practice would be to use 'selected-any' in this instance to make the intention clear.

selected-any

  • Behavior:  Match cases where a multi-select case property contains ANY of the values in the input provided

  • Arguments:  Two arguments, the multi-select case property and the values to check represented as a space separated string.

  • Usage: selected-any(tests_performed, "testA testB testC") 

Outcomes table for selected-any 

Search term

Case Property Value

Search Result

Note

Search term

Case Property Value

Search Result

Note

value1

value2 value1 value3

Match

Property contains all of the search terms

value1 value2

value2 value5 value1 value3

Match

Property contains all of the search terms

value1 value2

value1 value3

Match

Property contains at least one of the search terms

value1 value2

value3 value4

No Match

Property does not contain any of the search terms

selected-all

  • Behavior:  Match cases where a multi-select case property contains ALL of the values in the input provided

  • Arguments:  Two arguments, the multi-select case property and the values to check represented as a space separated string.

  • Usage: selected-all(tests_performed, "testA testB testC") 

Outcomes table for selected-all 

Search term

Case Property Value

Search Result

Note

Search term

Case Property Value

Search Result

Note

value1

value2 value1 value3

Match

Property contains all of the search terms

value1 value2

value2 value5 value1 value3

Match

Property contains all of the search terms

value1 value2

value1 value3

No match

Property does not contain ALL of the search terms

fuzzy-match

  • Behavior:  Determines if a given value is a fuzzy match for a given case property.

  • Return: True if that particular value matches the case property.  Otherwise False.  

  • Arguments:  Two arguments: the case property and the value to check.  

  • Usage: fuzzy-match(first_name, "Sara")

phonetic-match

  • Behavior:  Match cases if a given value "sounds like" (using Soundex) the value of a given case property. (e.g. "Joolea" will match "Julia")

  • Return: True if that particular value matches the case property. Otherwise False. 

  • Arguments:  Two arguments: the case property and the value to check.  

  • Usage: phonetic-match(first_name, "Julia")

Filtering on parent (ancestor) cases

Searches may be performed against ancestor cases (e.g. parent cases) using the "/" operator. 

For example, the below search will search for cases that have a parent case that matches the filter of age greater than 55:

parent/age > 55

And if you want to perform a search against cases higher in the case heirarchy (e.g. the "grandparent" case), you can add multiple parent/parent/ steps as shown below:

parent/parent/dod = ''

ancestor-exists

  • Behavior:  Match cases that have an ancestor with the given relation that matches the ancestor filter expression.

  • Arguments:  Two arguments, the ancestor relationship (usually one of parent or host) and the ancestor filter expression.

  • Usage:

    • ancestor-exists(parent/parent, city = 'SF')

    • ancestor-exists(parent, food_included = 'yes' and ancestor-exists(parent, city!='' and selected(city, 'Boston')))

  • Limitation

    • The arguments can't be a standalone function and must be a binary expression

      • This will not work: ancestor-exists(parent, selected(city, 'SF'))

      • This will work:  ancestor-exists(parent, city != '' and selected(city, 'SF'))

    • The ancestor filter expression may not include subcase-exists or subcase-count

Filtering on child cases (subcases)

Special functions are provided to support filtering based on the properties of subcases. These are:

subcase-exists

  • Behavior:  Match cases that have a subcase with the given relation that matches the subcase filter expression.

  • Arguments:  Two arguments, the subcase relationship (usually one of 'parent' or 'host') and the subcase filter expression.

  • Usage: subcase-exists('parent', lab_type = 'blood' and result = 1) 

subcase-count

  • Behavior:  Match cases where the number of subcases matches the given expression.

  • Arguments:  Two arguments, the subcase relationship (usually one of 'parent' or 'host') and the subcase filter expression.

  • Usage: subcase-count('parent', lab_type = 'blood' and result = 1) > 3

    • The count function must be used in conjunction with a comparison operator. All operators are supported ( >, <, >=, <=, =, !=)

When utilizing the special subcase function, be mindful that the quantity of search results and the number of subcase functions in a single search are important factors. As the number of subcase functions and search results increases, the time required to perform the search will also increase.

Closed cases are included in the results by default. The closed cases can be filtered out by adding (@status != 'closed') to reduce the quantity of results.

Keep in mind that a higher number of search results will lead to longer execution times for the search query. The threshold is around 400K to 500K search results, after which a timeout error may occur. It is recommended to keep your search results well below this number for optimal performance. 

Multi-select case property searches

As shown above, the selected , selected-any  and selected-all  functions can be used to filter cases based on multi-select case properties.

A multi-select case property is a case property whose value contains multiple 'terms'. Each 'term' in the case property value is typically separated by a space.

The following table illustrates how a case property value is split up into component terms. Note that some characters are removed and other are used as separators.

Case property value

Searchable terms

Note

Case property value

Searchable terms

Note

word1 word2     word3

[word1, word2, word3]

Split on white space

word1 word-two 9-8

[word1, word, two, 9, 8]

Split on ‘-’

word1 word_2

[word1, word_2]

Not split on ‘_’

word1 5.9 word.2

[word1, 5.9, word, 2]

Split on ‘period’ between 'letters' but not between
numbers.

‘word1’ “word2” word3?!

[word1, word2, word3]

Quotes and punctuation are ignored

你好

[你, 好]

Supports unicode characters

word1 🧀 🍌 word2

[word1, word2]

Emoji are ignored

word’s

[words, words]

Apostrophe are removed

word”s

[word, s]

Split on double quote between letters

word1\nword2

(“\n” = newline)

[word1, word2]

Split on white space

12/2=6x1   4*5   98%  3^2

[12, 2, 6x1, 4, 5, 98, 3, 2]

Split on special characters

start<point<end

[start, point, end]

Split on special characters

you&me

[you, me]

Split on special characters

(w1) ( w2 ) [w3] [ w4 ] ( [ w5

[w1, w2, w3, w4, w5]

Special characters are removed

word1,word2,word3

[word1, word2, word3]

Split on special characters

Note that the CommCare functions selected and selected-at do not follow this pattern. They only consider white space as the term separator and do not strip punctuation etc.

Limitations

  • Comparison between case properties is not supported e.g. activity_completion_date < opened_on 

  • Calculations within search criteria are not supported e.g. age = 7+3 , dob = today() - 7