FQL walkthrough — nested selects that make a variables section unnecessary — May 26, 2023
Walkthrough — FQL rebuilds the blood-pressure example
The authors of FQL — the FHIR Query Language, from Firely — walked the group through it, rebuilding the blood pressure example the group had been using.
It reads like SQL but runs over FHIR resources rather than tables: from Observation, a where, then selects that nest in brackets as you go deeper. Nothing is flattened unless you ask:
- A plain select of
name.givenhands you back an array jointurns it into rowsfetchfollows a reference into the linked resource and pulls columns out of it
The structural takeaway — nested selects remove the variables section
FQL's nested selects remove the need for a separate variables section. The group's design had one block declaring variables and another declaring columns; FQL folds them into a single hierarchy where a select scopes everything inside it.
The semantics look more predictable than the
whenManymechanism we sketched.
Ryan volunteered to translate the existing examples into a vars-free shape so they could be compared side by side.
Where the ambitions diverge
Stated plainly by FQL's own authors: because it allows a FHIRPath expression anywhere, it needs the whole resource in memory and cannot be fully transpiled to SQL. That is exactly the difference in ambition — the working group wants a subset small enough to compile into one query.
The idea ran both ways: a view definition could compile down into FQL, or serve as a constrained AST that FQL compiles into. Meanwhile a Postgres implementation was translating FHIRPath into JSONPath and reaching for lateral joins to handle variables.
Why filters is a list, not one big expression
Not for power — you can and them together and get the same thing. It's convenience: appending conjunctions one at a time while narrowing something down interactively, and attaching a description to each.
Openly acknowledged as a judgement call rather than a semantic argument.
Dates — type hints per column, plus earliest/latest instant
The leaning was a type hint per column rather than FHIRPath's toDate, partly because toDate can still hand back just a year, and partly because type hints generalise — you also want to say give me a decimal type so there's no precision loss.
Alongside it, earliest-instant and latest-instant functions, so a lab result that only has a date can be compared against an encounter that has a full timestamp without either being fudged.
The argument for putting this in the view layer rather than the annotation layer: you don't always control the data source you're reading.