Guide to constraints: examples to copy and learn from (part 3)

This is the third part of this feature guide on constraint expressions. To get started, read the first part of this guide by clicking here. You can also read the second part on additional properties and consideration for constraint expressions by clicking here.


In this last part of the guide on constraint expressions you can look through a series of example constraints to get a better idea of how they work with different field types and in different situations. Many of these examples will be directly useful in your form designs and might be copied and pasted, with a bit of editing. All of these examples appear in this sample form. Download the sample or make a copy in your Google Drive and upload to your SurveyCTO server on the Design tab. Click on Upload form definition (if you have no forms yet, click on Add your first form... first), select the sample form either as an Excel file or Google Sheet and upload to your server.

Example 1: Age with an exception

Example 1 is covered in the first part of this guide which you can view by clicking here. It is common to want to constrain the range of possible answers in an integer or decimal field. Equally, you might want to allow an exception outside that range to use as a special coded answer. In this example, the requirement of wanting to restrict answers in an integer field to a credible range for an adult (between 18 and 100) while allowing -99 as a special code is satisfied by the following constraint expression:

(. >= 18 and .<100) or . = -99

Literally, this expression is saying that the current answer must be greater than or equal to 18 and less than or equal to 100, or it can be equal to -99. -99 could represent "don't know" or "refused".

If you did not want to include -99 as an allowable exception, you could simplify down to the following:

. >= 18 and .< 100

Example 2: Double entry

When a survey involves pre-assigned unique IDs, a common practice in form designs is to require that enumerators enter the ID twice to help avoid error. In the second field, the constraint can required that the entered ID is equal to the value in the second field. The second field's constraint would look like this:

. = ${id_1}

"id_1" is the first field the ID is entered into. You might use the constraint message, "Does not match ID entered on previous screen".

Example 3: Reference to earlier numeric value

A great way to use constraints to make your form more logical and to improve the quality of your data, is to logically constrain responses based on previous answers where appropriate. For example, you might ask a respondent's age at the start of a questionnaire and later ask some questions about how old the respondent was at another point in their life (e.g. "How old were you when you graduated from high school?"). A constraint for this field might be:

. >= 0 and . <= ${age}

In English, the current value must be greater than or equal to 0, and less than or equal to the value stored in "age". You might accompany this constraint with the constraint message, "Answer cannot be greater than the respondent's age". You might mention "The respondent's age now is ${age}" in the hint.

Example 4: Dynamic date constraints

You might wish to dynamically constrain a date field's answer relative to today's date. "today()" is a function that returns today's date in an expression. For example, if you wish to ask someone's birthdate, it is not possible that someone was born in the future but they could have been born today, or any date in the past. The following expression achieves that:

. <= today()

This means the current entry must be less than or equal to today's date.

Tip: You can modify today() by adding or subtracting. For example, today() + 30 is the date 30 days from now.

Example 5: Reference to earlier date value

Like in example 3, you might do the same in a date field. Let's say you collected the date of birth of a respondent. Any life event for this person should be on or after their date of birth. If you wanted to ask the date the person had been inoculated on, the date would be between their date of birth and the current day's date. This expression will look like this:

. >= ${date_of_birth} and . <= today()

The above expression is saying that in order for it to evaluate as true, the current value must be greater than or equal to the value in "date_of_birth" and less than or equal to the value returned by "today()". "date_of_birth" is a date field in this example.

Give this field the constraint message, "Enter a date between the respondents birthday and today's date".

Example 6: Reference to static date values

Specific dates might be important logical constraints in some cases. You’ll use the function, “date()” to constrain response in a date field. For example, you may want to know the date of the respondent's last COVID-19 test. Since tests were not developed until early 2020 (source), you can add a constraint so that the data selected must be between January 1, 2020 and today. Such a constraint might look like this:

(. >= date('2020-01-01')) and (. <= today())

The . >= date('2020-01-01') part of the constraint means that the entered date must be more than or equal to January 1, 2020. The . <= today() part of the constraint means that the entered date must be less than or equal to today’s date.

Example 7: Illogical combinations of answer to select_multiple fields

Just because a select_multiple field can take more than one answer, that does not mean that any combination of answers might be logical. For example, if your choice list on food types eaten in the household included "don't know", it would not make sense to choose "millet" at the same time. An expression that achieves this could look like this:

not(selected(., 'dontknow') and count-selected(.) > 1)

This constraint depends on three different functions, "not()", "selected()", and "count-selected()". not() surrounds both other parts of this expression and means that in order for this expression to evaluate as true, whatever is inside the brackets cannot be true. selected() is a commonly used function that can evaluate whether a particular choice in a select_multiple field was selected (in this case, "don't know"). Lastly, count-selected() returns the number of selections made in a select_multiple field. Bringing things together, this field's constraint evaluates as true as long as the number of selections are not more than 1 when "don't know" is selected.

You can expand this logic, say, if you wished to prevent the selection of "refused" and "none of these" in combination with any other answer, like this:

not((selected(., 'refused') or selected(., 'noneofthese')) and 
count-selected(.) > 1)

In this second example, I've added a second set of brackets inside not(), around two selected() conditions joined by "or. So, now the expression evaluates as true as long as the number of selections are not more than 1 when "refused" or "none of these" are selected.

For an alternative formulation of this solution, see this article.

Example 8: Length of response

Perhaps in cases you will want the user of your form to include a certain amount of detail in a response entered into a text field. The "string-length()" function is useful in this case. Let's say you want to understand where possible, why respondents refused to participate in your survey, so you've asked enumerators to enter a few details into a text field to summarize the reason. If you wanted to require a more substantial answer, you might require that the answer is at least 20 characters long. This constraint expression would look like this:

string-length(.) >= 20

In other words, the length of the value captured in the current field must be greater than or equal to 50 characters in length.

Example 9: Formatted inputs like phone numbers and email address

Lots of values that you might wish to collect in a form might have a format to them which can be strict like a phone number or national ID, or a bit more flexible like an email address or a URL. Specially formatted values can be validated by using the "regex()" function. regex() is especially complex, arguably one of the most complex functions available in SurveyCTO. regex() is a standard part of many computer programming languages, so is nothing proprietary to SurveyCTO. The good thing is that you do not need to have a computer scientist level of understanding to make use of regex() constraints.

Let's start with an example though, of a regex() constraint to validate an email address:

regex(., '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}')

This expression requires that the inputted value has a string of any number of letters and/or numbers in front of an "@" symbol followed by any number of letters and/or numbers separated by "." from two to four letters at the end.

Notice how inside the brackets, the first character is ".", representing the current value, like in all constraints. After that, there's a comma and a series of coded instructions between single quotes - it is these instructions that you'll probably need help with.

The constraint builder and constraint wizard can help you to create a limited number of types of regex() constraints if you select the text field type. regex() is generally applicable to text fields in most cases but can be used with other fields too.

For more on regex() expressions, there are many online resources that can teach you how to write regex specifications, or even offer working examples. In either case, you need to thoroughly test regex() constraints, more thoroughly than any other type of constraint, particularly if your mastery of them is limited (which you can do with the Tools on the Design tab). Take a look at these websites:

Beyond the examples

The above examples are just a few of the possibilities with constraints. Constraints, like any other type of expression (including relevance, calculations and choice filter expressions) in SurveyCTO can make use of values stored in any field type and any of the available functions. Please experiment and see what you can come up with.

Do you have thoughts on this support article? We'd love to hear them! Feel free to fill out this feedback form.


Article is closed for comments.