AQL — Archetype Query Language
AQL (Archetype Query Language) is a SQL-like query language designed for openEHR data. Unlike SQL, which queries database tables and columns, AQL queries use archetype paths — references to the clinical model structure. This means you can retrieve specific data points (e.g., all blood pressure readings above 140 mmHg) regardless of which template was used to capture them or how the data is physically stored.
AQL sits between the semantic clinical models (archetypes and templates) and the storage layer. It references RM attributes and archetype node identifiers to locate values, making queries portable across different openEHR implementations.
After reading this guide, you should be able to:
- Write SELECT, FROM, WHERE, ORDER BY, and LIMIT clauses
- Use parameters for dynamic queries
- Execute queries against the Cadasto Query API
For the full language specification, see the openEHR AQL specification.
AQL support is under development. Some features are experimental and may change in future releases.
Executing queries in Cadasto
Use the Query API to run AQL queries. The recommended approach is POST with the query in the request body:
POST /query/aql
Content-Type: application/json
Authorization: Bearer <token>
{
"q": "SELECT e/ehr_id/value FROM EHR e",
"query_parameters": {}
}
See the Query API reference for full request/response details.
SELECT — choosing data
The SELECT clause specifies which columns to return. You use archetype paths to navigate the openEHR data structure.
Syntax:
SELECT [DISTINCT] <column_list>
Examples:
SELECT * FROM EHR
SELECT e/ehr_id FROM EHR e
SELECT c/uid/value, c/name/value FROM COMPOSITION c
SELECT MAX(c/time_created) FROM COMPOSITION c
SELECT DISTINCT c/archetype_node_id FROM COMPOSITION c
SELECT COUNT(*) FROM EHR
FROM — specifying data sources
The FROM clause defines which openEHR types to query and how they are nested. Use CONTAINS to express the containment hierarchy (e.g., an EHR contains compositions, which contain observations).
Syntax:
FROM <source> [CONTAINS [<alias>] <source> [AND | OR] ...]
Examples:
-- All EHRs
SELECT * FROM EHR e
-- Compositions within EHRs
SELECT * FROM EHR e CONTAINS COMPOSITION c
-- Observations within compositions
SELECT * FROM EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION o
-- Multiple content types (OR)
SELECT * FROM EHR e CONTAINS (COMPOSITION c CONTAINS OBSERVATION o OR EVALUATION v)
-- Multiple content types (AND)
SELECT * FROM EHR e CONTAINS (COMPOSITION c CONTAINS OBSERVATION o AND EVALUATION v)
-- Nested containment
SELECT * FROM EHR e CONTAINS (COMPOSITION c CONTAINS (OBSERVATION o AND EVALUATION v CONTAINS CLUSTER cl))
Understanding archetype paths
Archetype paths like o/data[at0001]/events[at0006]/data[at0003]/items[at0004] navigate the data tree defined by an archetype:
o— alias for the OBSERVATIONdata[at0001]— thedataattribute, constrained by archetype nodeat0001events[at0006]— a specific event type within the dataitems[at0004]— a specific data item (e.g., systolic blood pressure)
You can find these paths in the archetype definition or by retrieving the template from the Definition API.
WHERE — filtering results
The WHERE clause filters results using predicates on archetype paths.
Syntax:
WHERE <predicate>
Examples:
-- Filter by EHR ID
SELECT * FROM EHR e WHERE e/ehr_id/value = '7d44b88c-4199-4bad-97dc-d78268e01398'
-- Combine conditions
SELECT * FROM EHR e
CONTAINS COMPOSITION c
CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.blood_pressure.v2]
WHERE o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude >= 140
ORDER BY — sorting results
Syntax:
ORDER BY <column> [ASC | DESC]
Examples:
SELECT * FROM EHR e ORDER BY e/ehr_id/value ASC
SELECT c/uid/value, c/time_created FROM EHR e CONTAINS COMPOSITION c ORDER BY c/time_created DESC
LIMIT — paging results
Syntax:
LIMIT <number> [OFFSET <number>]
Examples:
SELECT * FROM EHR e LIMIT 10
SELECT * FROM EHR e LIMIT 10 OFFSET 20
Parameters — dynamic queries
Use parameters to make queries reusable. Parameter names are prefixed with $ and supplied in the query_parameters object.
In the query:
SELECT c/uid/value
FROM EHR e CONTAINS COMPOSITION c
WHERE e/ehr_id/value = $ehr_id AND c/name/value = $composition_name
In the API request:
{
"q": "SELECT c/uid/value FROM EHR e CONTAINS COMPOSITION c WHERE e/ehr_id/value = $ehr_id AND c/name/value = $composition_name",
"query_parameters": {
"ehr_id": "7d44b88c-4199-4bad-97dc-d78268e01398",
"composition_name": "Vital Signs"
}
}
Querying demographic data
Cadasto supports an experimental AQL language extension for querying the Demographic Information Model. This allows you to query demographic entities (PERSON, ORGANISATION, ROLE, PARTY_RELATIONSHIP) and join them with clinical data in a single query.
Demographic queries use the same syntax but start from demographic types instead of EHR:
-- All persons
SELECT p/uid/value FROM PERSON p
-- Persons with a specific role
SELECT p/uid/value FROM PERSON p CONTAINS ROLE r[openEHR-DEMOGRAPHIC-ROLE.patient.v1]
You can combine clinical and demographic data in a single query — for example, finding patients with high blood pressure filtered by demographic attributes. See the examples below.
Demographic AQL querying is an experimental extension. Syntax and behaviour may change in future releases.
Clinical examples
Blood glucose values above threshold
Retrieve all blood glucose results where the value exceeds 11 mmol/L or 200 mg/dL, with the corresponding subject IDs:
SELECT
e/ehr_status/subject/external_ref/id/value AS subjectId,
a/items[at0001]/value AS analyteName,
a/items[at0024]/value AS analyteResult
FROM EHR e
CONTAINS COMPOSITION c
CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.laboratory_test_result.v1]
CONTAINS CLUSTER a[openEHR-EHR-CLUSTER.laboratory_test_analyte.v1]
WHERE
(a/items[at0001]/value/defining_code/code_string MATCHES {'14743-9', '2345-7'}
AND a/items[at0001]/value/defining_code/terminology_id = 'LOINC')
AND
((a/items[at0024]/value/magnitude > 11 AND a/items[at0024]/value/units MATCHES {'mmol/L'})
OR (a/items[at0024]/value/magnitude >= 200 AND a/items[at0024]/value/units MATCHES {'mg/dL'}))
Patients with high blood pressure
Find patients with systolic blood pressure >= 140 mmHg, including demographic details:
SELECT
o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude AS systolic,
pd/items[at0021]/value AS education_level,
pd/items[at0018]/value AS marital_status
FROM PERSON p
CONTAINS ITEM_TREE pd[openEHR-DEMOGRAPHIC-ITEM_TREE.person_details.v1]
AND ROLE [openEHR-DEMOGRAPHIC-ROLE.patient.v1]
AND EHR e
CONTAINS COMPOSITION c
CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.blood_pressure.v2]
WHERE o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude >= 140
AND pd/items[at0021]/value MATCHES {'primary_education', 'no_formal'}
Administered medication by practitioner
Find medications administered to a patient by a specific caregiver:
SELECT
patient/uid/value AS uid,
caregiver/details[at0001]/items[at0002] AS caregiver_name,
comm/details[at0001]/items[at0004] AS caregiver_contact,
instruct AS medications
FROM PERSON patient
CONTAINS (PARTY_RELATIONSHIP rel[openEHR-DEMOGRAPHIC-PARTY_RELATIONSHIP.informal_carer.v1]
CONTAINS PERSON caregiver
CONTAINS ADDRESS comm[openEHR-DEMOGRAPHIC-ADDRESS.electronic_communication.v2])
AND ROLE role[openEHR-DEMOGRAPHIC-ROLE.patient.v1]
AND EHR
CONTAINS INSTRUCTION instruct[openEHR-EHR-INSTRUCTION.medication_order.v1]
WHERE instruct/activities/description/items[at0001]/value LIKE '*insulin*'
AND rel/details[at0005]/value = 'primary_medication_manager'
Stored queries
For queries you run repeatedly, you can store them on the server via the Definition API and execute them by name. Stored queries support versioning, better governance, and potential performance optimizations.
PUT /definition/query/my_org::blood_pressure_readings
Content-Type: text/plain
Authorization: Bearer <token>
SELECT o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude AS systolic
FROM EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.blood_pressure.v2]
WHERE e/ehr_id/value = $ehr_id
Execute by name. Stored queries use GET with parameters passed as query string values (unlike ad-hoc queries which use POST with a JSON body):
GET /query/my_org::blood_pressure_readings?ehr_id=7d44b88c-4199-4bad-97dc-d78268e01398
Authorization: Bearer <token>
Next steps
- Query API Reference — Full API specification for query execution
- openEHR AQL Specification — Complete language reference
- openEHR Concepts — Understand the data model behind the queries
- Quick Start — End-to-end workflow including querying