SQL on FHIR
0.0.1-pre - ci-build
SQL on FHIR, published by HL7. This guide is not an authorized publication; it is the continuous build for version 0.0.1-pre built by the FHIR (HL7® FHIR® Standard) CI Build. This version is based on the current content of https://github.com/FHIR/sql-on-fhir-v2/ and changes regularly. See the Directory of published versions
This spec defines the ViewDefinition model and logical system layers, but intentionally leaves the details of those layers to implementations. However, implementations themselves are encouraged to share common approaches when helpful.
TODO: add links to implementation patterns (like Spark, JSON, etc) as these efforts materialize.
The following description provides an algorithm for how to process a FHIR
resource as input for a ViewDefinition
. Implementations do not need to follow
this algorithm directly, but their outputs should be consistent with what this
model produces.
Purpose: This step ensures that a ViewDefinition’s columns are valid, by setting up a recursive call.
Inputs
V
: a ViewDefinition
to validateValidateColumns(V, C)
according to the recursive step below.ValidateColumns(S, C)
(recursive step)Purpose: This step ensures that column names are unique across S
and disjoint from C
Inputs
S
: a single Selection StructureC
: a list of Columns that exist prior to this callOutputs
Ret
: a list of ColumnsErrors
Initialize Ret
to equal C
col
in S.column[]
col.name
already exists in Ret
, throw “Column Already Defined”col
to Ret
sel
in S.select[]
c
in Validate(sel, Ret)
c.name
already exists in Ret
, throw “Column Already Defined”c
to the end of Ret
S.unionAll[]
is present
u0
as Validate(S.unionAll[0], Ret)
sel
in S.unionAll[]
u
as ValidateColumns(sel, Ret)
u0
is different from the list of names from u
, throw “Union Branches Inconsistent”col
in u0
col
to Ret
Ret
Purpose: This step emits all rows produced by a ViewDefinition on an input Resource, by setting up a recursive call.
Inputs
V
: a ViewDefinition
R
: a FHIR Resource to process with V
Emits: one output row at a time
R.resourceType
is different from V.resource
, return immediately without emitting any rowsV.where
is defined, ensure constraints are met
fhirpath(V.where.path, R)
to determine whether R
is a candidate for V
R
is not a candidate for V
, return immediately without emitting any rowsProcess(S, V)
Process(S, N)
(recursive step)Purpose: This step emits all rows for a given Selection Structure and Node. We first generate sets of “partial rows” (i.e., sets of incomplete column bindings from the various clauses of V
) and combine them to emit complete rows. For example, if there are two sets of partial rows:
[{"a": 1},{"a": 2}]
with bindings for the variable a
[{"b": 3},{"b": 4}]
with bindings for the variable b
Then the Cartesian product of these sets consists of four complete rows:
[
{"a": 1, "b": 3},
{"a": 1, "b": 4},
{"a": 2, "b": 3},
{"a": 2, "b": 4}
]
Inputs
S
: a Selection StructureN
: a Node (element) from a FHIR resourceErrors
Emits: One output row at a time
foci
as
S.forEach
is defined: fhirpath(S.forEach, N)
S.forEachOrNull
is defined: fhirpath(S.forEachOrNull, N)
[N]
(a list with just the input node)f
of foci
parts
(each element of parts
will be a list of partial rows)col
of S.column
, define val
as fhirpath(col.path, f)
b
as a row whose column named col.name
takes the value
val
was the empty set: null
val
has a single element e
: e
col.collection
is true: val
[b]
to parts
parts
is now a list containing the single row b
).sel
of S.select
rows
as the collection of all rows emitted by Process(sel, f)
rows
to parts
parts
is now the list rows
)urows
as an empty list of rowsu
of S.unionAll
r
in Process(u, f)
r
to urows
urows
to parts
parts
is now the list urows
)prows
in the Cartesian product of parts
r
p
in prows
p
’s column bindings to the row r
r
.select[0].select[0].select[0]
will eventually bubble up to the top level, but the bubbling happens one level at a time.)foci
is an empty list and S.forEachOrNull
is defined
r
c
in ValidateColumns(V, [])
c.name
to null
in the row r
3. Emit the row r