CQL WorkbenchDocs
Docs contents
CQL primer

CQL syntax cheat sheet

The Clinical Quality Language (CQL) syntax you meet most often when reading rules, grouped into a quick reference: each item gives the form, a one-line meaning and a short example. All examples belong to one diabetes follow-up library on the FHIR R4 data model.

New to CQL? Start with Reading the workbench’s CQL, which walks through one NHI clause from start to finish; this page is for looking things up while you read a rule. For unfamiliar terms, see the glossary.

File structure

A CQL file is a library. It opens with a series of declarations, by convention in the order below, followed by the defines. This header is shared by every example on the page: the examples in later sections can be pasted straight after it.

DiabetesCare.cql
library DiabetesCare version '1.0.0'

using FHIR version '4.0.1'

include FHIRHelpers version '4.0.1' called FHIRHelpers

codesystem "LOINC": 'http://loinc.org'

valueset "Diabetes": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.103.12.1001'

code "HbA1c": '4548-4' from "LOINC" display 'Hemoglobin A1c/Hemoglobin.total in Blood'
code "eGFR": '62238-1' from "LOINC"

// Measurement year 2026
parameter "Measurement Period" Interval<DateTime>
  default Interval[@2026-01-01T00:00:00.000, @2027-01-01T00:00:00.000)

context Patient
library DiabetesCare version '1.0.0'
The library’s name and version. Other libraries name these when they include it.
using FHIR version '4.0.1'
The data model. FHIR 4.0.1 is FHIR R4; it decides which resources a retrieve can return and which elements they have.
include FHIRHelpers version '4.0.1' called FHIRHelpers
Includes another library; the name after called is the alias used in this file. FHIRHelpers converts FHIR data types to CQL types, so nearly every library that reads FHIR data includes it.
codesystem "LOINC": 'http://loinc.org'
A code system, identified by its canonical URI after the colon.
valueset "Diabetes": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.103.12.1001'
A value set: a list of codes, named by its canonical URL. This one is the diabetes value set in the US Value Set Authority Center (VSAC).
code "HbA1c": '4548-4' from "LOINC" display 'Hemoglobin A1c/Hemoglobin.total in Blood'
A single code: the code and its code system. display is a human-readable label and can be left out, as the eGFR line does.
// Measurement year 2026
A comment: // runs to the end of the line, and /* */ wraps several lines. Comments don’t affect the result.
parameter "Measurement Period" Interval<DateTime> default Interval[@2026-01-01T00:00:00.000, @2027-01-01T00:00:00.000)
A parameter: supplied from outside at run time, falling back to the default. Here it is the whole of 2026; the closing parenthesis leaves 2027-01-01 out.
context Patient
Every define after this line is evaluated for one patient, and retrieves only return that patient’s data.

Note: double quotes hold names, like "HbA1c", and names may contain spaces; single quotes hold text, like 'http://loinc.org'.

Spec: Author’s Guide: DeclarationsUsing CQL with FHIRFHIRHelpers

define and retrieve

define gives an expression a name; a retrieve is how CQL fetches data, and it returns a list.

define "Name": expression

Names an expression so other expressions can refer to it by that name. The second define uses exists to check whether the first returned anything.

define "Diabetes Conditions":
  [Condition: "Diabetes"]

define "Has Diabetes":
  exists "Diabetes Conditions"
[Type: "value set"]

This patient’s Conditions (diagnoses) whose code is in the Diabetes value set.

[Condition: "Diabetes"]
[Type: "code"]

Filters by a single code: Observations coded LOINC 4548-4, that is, HbA1c results.

[Observation: "HbA1c"]
[Type]

No filter: every Encounter (visit) for this patient.

[Encounter]
[Type: element in "value set"]

Names the element to match on. Without it, the resource’s primary code element is used, which is code for both Condition and Observation, so this line returns the same as the first form.

[Condition: code in "Diabetes"]

A retrieve doesn’t filter by status: cancelled and entered-in-error records come back too. Filter them out yourself with a where clause, as the next section shows.

Spec: Author’s Guide: StatementsAuthor’s Guide: Retrieve

Queries

Add an alias after a list and you have a query; the alias stands for the current item. Clauses follow in this order: with or without, where, return, sort.

source alias

O is the alias. A query with only an alias returns the whole list unchanged; the clauses below usually follow.

[Observation: "HbA1c"] O
where condition

Keeps only items for which the condition is true; false and null are both dropped. In FHIR, an Observation’s effective can be a point in time or a period. as dateTime takes the point in time; for a period it gives null, so that item is dropped.

define "HbA1c In Period":
  [Observation: "HbA1c"] O
    where O.status in { 'final', 'amended', 'corrected' }
      and (O.effective as dateTime) during "Measurement Period"
with source alias such that condition

Keeps items that have at least one related match: visits during which an HbA1c was taken.

define "Visits With HbA1c":
  [Encounter] E
    with [Observation: "HbA1c"] O
      such that (O.effective as dateTime) during E.period
without source alias such that condition

The opposite: keeps items with no related match at all, here visits with no HbA1c taken during them.

define "Visits Without HbA1c":
  [Encounter] E
    without [Observation: "HbA1c"] O
      such that (O.effective as dateTime) during E.period
return expression

Decides what each item becomes; Tuple builds a new structure with named fields. Tuple fields aren’t converted automatically, so this calls FHIRHelpers.ToDateTime to make takenAt a CQL DateTime. In comparisons, FHIRHelpers converts for you.

define "HbA1c Results":
  "HbA1c In Period" O
    return Tuple {
      takenAt: FHIRHelpers.ToDateTime(O.effective as dateTime),
      result: O.value as Quantity
    }
    sort by takenAt
sort by element ascsort by element desc

Sorts ascending (the default) or descending. sort by takes element names directly, without the alias; start of period is when the visit started.

define "Encounters Latest First":
  [Encounter] E
    sort by start of period desc

Spec: Author’s Guide: Queries

Types and literals

Every value in CQL has a type. These are the types you meet most often in rules, and how to write them.

Boolean

True or false. When data is missing it is null; see Three-valued logic and null.

true
false
Integer

A whole number.

42
Decimal

A decimal number.

7.5
String

Text, in single quotes. Double quotes hold names, not text.

'final'
Date

A date, starting with @.

@2026-03-15
DateTime

A date and time separated by T; +08:00 is Taiwan’s time zone.

@2026-03-15T08:30:00.000+08:00
Time

A time of day only, starting with @T.

@T08:30
Quantity

A number with a unit. Units use UCUM codes in single quotes; lengths of time use words such as years, months and days, without quotes.

7 '%'
30 'mL/min/{1.73_m2}'
90 days
Interval

A range. Square brackets include the boundary and parentheses exclude it: Interval[18, 65) is 18 or more and under 65.

Interval[18, 65)
Interval[@2026-01-01, @2026-12-31]
List

A list, in curly braces. Retrieves and queries also return lists.

{ 'final', 'amended', 'corrected' }
Tuple

A structure with named fields, each written as name: value.

Tuple { takenAt: @2026-03-15, result: 6.8 '%' }
Code

A single code: the code and its code system, optionally with a display.

Code '4548-4' from "LOINC" display 'Hemoglobin A1c/Hemoglobin.total in Blood'
Concept

A set of codes that mean the same thing, such as two LOINC codes for the same test. A FHIR CodeableConcept becomes a Concept in CQL.

Concept { Code '4548-4' from "LOINC", Code '17856-6' from "LOINC" } display 'Hemoglobin A1c'

Spec: Author’s Guide: ValuesUsing CQL with FHIR: UCUM

Lists and value sets

Retrieves and queries both return lists. These operations answer “is there one”, “how many” and “which one”.

element in listlist contains element

Whether the list holds the element. in and contains are the same test with the sides swapped.

'final' in { 'final', 'amended', 'corrected' }  // true
{ 'final', 'amended', 'corrected' } contains 'preliminary'  // false
value in interval

Whether the value falls within the range: 18 to 75, both ends included. contains works for intervals too.

AgeInYears() in Interval[18, 75]
code in "value set"

Whether a code is in a value set. C.code is a FHIR CodeableConcept, and it counts if any of its codes is in the value set, so this returns the same as [Condition: "Diabetes"].

define "Diabetes By Code":
  [Condition] C
    where C.code in "Diabetes"
exists list

True if the list has at least one item. An empty list and null both give false, never null.

exists "HbA1c In Period"
Count(list)

How many items the list has, not counting nulls. Here: at least two HbA1c results in the measurement period.

Count("HbA1c In Period") >= 2
First(list)Last(list)

The first or last item, in the list’s current order; null if the list is empty. HbA1c Results is already sorted by takenAt from oldest to newest, so Last is the most recent.

define "Latest HbA1c":
  Last("HbA1c Results")

Spec: CQL Reference: List OperatorsCQL Reference: Aggregate Functions

Comparison and logic

Comparison, logic and null checks. For how each behaves when a value is null, see the last section on this page.

= != < <= > >=

Comparison. Quantities need compatible units. If either side is null, for example when there is no HbA1c result, the comparison is null.

define "HbA1c Controlled":
  "Latest HbA1c".result < 7 '%'
~ !~

Equivalence. Text is compared ignoring case; codes are compared on code system and code only, ignoring display; two nulls are equivalent, so ~ never returns null.

'Final' ~ 'final'  // true
'Final' = 'final'  // false
and or not

And, or, not. For how they treat null, see Three-valued logic and null.

define "Adult With Diabetes":
  "Has Diabetes" and AgeInYears() >= 18
A implies B

“If A, then B”: when A is false the result is true; when A is true the result is B. Here: a patient with diabetes should have an HbA1c in the measurement period.

define "HbA1c Checked If Diabetic":
  "Has Diabetes" implies exists "HbA1c In Period"
X is nullX is not null

Tests for null. The result is always true or false.

"Latest HbA1c" is null
X is trueX is false

Holds only when the value is exactly true (or false); null gives false. For why this matters, see The result and is true.

"HbA1c Controlled" is true
if condition then A else B

A null condition takes the else branch. So test is true and is false first; the final else is then the missing-data case.

define "HbA1c Status":
  if "HbA1c Controlled" is true then 'controlled'
  else if "HbA1c Controlled" is false then 'uncontrolled'
  else 'no-result'

Spec: CQL Reference: Comparison OperatorsCQL Reference: Logical OperatorsDeveloper’s Guide: Conditional Expressions

Dates and times

Comparing points and periods in time, and date arithmetic. The most common rule is “this happened during the measurement period”.

X during interval

Falls entirely within the interval. When the left side is a point in time, the point is within the interval, as in (O.effective as dateTime) during "Measurement Period" under Queries.

define "Encounters In Period":
  [Encounter] E
    where E.period during "Measurement Period"
X before YX after Y

Earlier or later. start of and end of pick an interval’s start or end: here, HbA1c results before the measurement period began.

define "HbA1c Before Period":
  [Observation: "HbA1c"] O
    where (O.effective as dateTime) before start of "Measurement Period"
X 3 months or less before Y

Adds a length of time: within the 3 months before Y; or more means at least 3 months before. Here, HbA1c results in the last 3 months of the measurement period.

define "HbA1c Near Period End":
  [Observation: "HbA1c"] O
    where (O.effective as dateTime) 3 months or less before end of "Measurement Period"
X overlaps Y

The two periods share some time: a hospital stay over the new year counts toward 2026.

define "Encounters Overlapping Period":
  [Encounter] E
    where E.period overlaps "Measurement Period"
X starts during YX ends during Y

Looks only at the moment a period starts or ends: visits that ended during the measurement period.

define "Encounters Ending In Period":
  [Encounter] E
    where E.period ends during "Measurement Period"
start of intervalend of interval

An interval’s start and end. The measurement period’s upper bound is open, so end of returns the millisecond before 2027-01-01.

start of "Measurement Period"  // @2026-01-01T00:00:00.000
end of "Measurement Period"  // @2026-12-31T23:59:59.999
years between A and B

How many whole units passed between two times; the unit can be years, months, days and so on.

years between @1960-05-01 and @2026-10-28  // 66
days between @2026-03-01 and @2026-03-15  // 14
difference in years between A and B

How many unit boundaries were crossed. 31 December to the next day is one day: zero whole years, but one year boundary crossed, so 1.

years between @2025-12-31 and @2026-01-01  // 0
difference in years between @2025-12-31 and @2026-01-01  // 1
date + quantitydate - quantity

Adds or subtracts time. Today() is the day of evaluation, so Today() - 90 days is 90 days ago.

@2026-01-15 + 3 months  // @2026-04-15
Today() - 90 days
duration in days of interval

The length of an interval: 1 March to 4 March is 3 days.

duration in days of Interval[@2026-03-01, @2026-03-04]  // 3
AgeInYears()AgeInYearsAt(time)

The patient’s age from the Patient’s birthDate, or null if it is missing. AgeInYears() uses the day of evaluation; AgeInYearsAt uses the time you give, so the result doesn’t depend on when it runs. Quality measures usually use the latter.

AgeInYears() >= 18
AgeInYearsAt(end of "Measurement Period") >= 18

Spec: Author’s Guide: Timing and Interval OperatorsCQL Reference: Date and Time OperatorsCQL Reference: Clinical Operators

Functions

When the same check is needed in many places, write it as a function. Functions live in a library like defines, and other libraries can use them.

define function "Name"(argument Type): expression

Arguments need a type; Observation here is the FHIR Observation. Call it by putting the value in parentheses. To state the return type, add returns Boolean after the parentheses.

define function "Is Final"(obs Observation):
  obs.status in { 'final', 'amended', 'corrected' }

define "Final HbA1c":
  [Observation: "HbA1c"] O
    where "Is Final"(O)
define fluent function name(argument Type): expression

With fluent, the function can be called with a dot after a value: O.isFinal() is the same as isFinal(O). Fluent functions arrived in CQL 1.5.

define fluent function isFinal(obs Observation):
  obs.status in { 'final', 'amended', 'corrected' }

define "Final eGFR":
  [Observation: "eGFR"] O
    where O.isFinal()
alias.function(arguments)

Calls a function from an included library, prefixed with the alias given after called.

define "HbA1c Dates":
  "HbA1c In Period" O
    return FHIRHelpers.ToDateTime(O.effective as dateTime)

Spec: Developer’s Guide: Defining Functions

Three-valued logic and null

CQL uses null for “unknown”, such as a lab test that isn’t in the record. Null propagates: most operations return null if any input is null. A few always return true or false, and those are what you use to handle missing data.

ExpressionResultWhy
true and nullnullIt depends on the null side, so it can’t be decided.
false and nullfalseOne side already fails, so the other doesn’t matter.
true or nulltrueOne side already holds.
not nullnullThe opposite of unknown is still unknown.
null = 7nullA comparison with null is null.
null ~ 7falseEquivalence only returns true or false.
null is nulltrueNull tests never return null.
null is truefalseOnly true holds.
Count({ 1, null, 3 })2Count skips nulls.
Coalesce(null, 'unknown')'unknown'Returns the first value that isn’t null.
if null then 'yes' else 'no''no'A null condition takes the else branch.

Coalesce and if turn “unknown” into an answer: Coalesce("HbA1c Controlled", false) counts a patient with no HbA1c result as poorly controlled. When deciding whether criteria are met, separate the two settled outcomes with is true and is false first, and treat the rest as missing data. See The result and is true.

The full truth tables for and, or and not are in Three-valued logic, and the scenario simulator lets you switch conditions and watch the result change.

Spec: CQL Reference: Nullological OperatorsDeveloper’s Guide: Nullological Operators

Further reading