Skip to main content
Version: 3.1.0

Run

The $sql-run operation executes a single SQL on FHIR subject and returns its result in the response body. A subject is one of three artefacts:

  • a ViewDefinition, which projects a resource type into a flat table;
  • a SQLQuery Library, which runs SQL over the tables its relatedArtifact entries name; or
  • a SQLView Library, which is a named, reusable SQL table source.

The operation is subject-polymorphic: which kind you supply decides which parameters apply and which output formats are available, but the endpoint, filters and error contract are the same for all three.

How to write each kind of subject, declare its dependencies and use value sets and concept maps in its SQL is described on the SQL on FHIR page, since it is common to this operation and $sql-export. Use $sql-export instead when the result is too large or too slow to return synchronously, or when you want to export several subjects together.

Endpoint​

GET  [base]/$sql-run
POST [base]/$sql-run

The operation is system level only. A GET can carry only primitive parameters, so it is available when the subject is already stored on the server; supplying a resource-valued parameter over GET is rejected with a 400.

Parameters​

NameCardinalityTypeDescription
subjectCanonical0..1canonicalThe subject's canonical URL, honouring a |version pin. Matched against both ViewDefinition.url and Library.url.
subjectReference0..1ReferenceA relative reference naming its type: ViewDefinition/[id] or Library/[id].
subjectResource0..1ResourceAn inline ViewDefinition, SQLQuery or SQLView.
parameters0..1ParametersRuntime bindings for a SQL subject. Every Library.parameter declaration must be bound by an entry matching it in name and type.
context0..*ResourceInline supporting artefacts (ViewDefinition, SQLView, ValueSet or ConceptMap) for dependencies the server cannot resolve. Matched by canonical URL.
resource0..*stringFHIR resources to project instead of server data, for a ViewDefinition subject. Each is a serialised resource or a Bundle to unwrap.
_format0..1codeOutput format; see below. Takes precedence over the Accept header.
header0..1booleanInclude the header row in CSV output. Defaults to true.
_limit0..1integerMaximum rows to return. When omitted, the whole result is returned.
patient0..*ReferenceRestricts the data the subject reads to these patients' compartments.
group0..*ReferenceRestricts the data the subject reads to these groups' member patients.
_since0..1instantRestricts to resources updated at or after this instant.
source0..1stringNot supported: an external data source. Supplying it is rejected with a 400.

Exactly one of subjectCanonical, subjectReference and subjectResource must be supplied; naming none, or more than one, is a 400.

parameters applies only to a SQL subject, and resource only to a ViewDefinition subject. Supplying either to the other kind is a 400 naming the parameter, rather than being silently ignored.

Output formats​

The formats on offer depend on the kind of subject:

FormatViewDefinitionSQLQuery / SQLViewMedia type
ndjsonyes (default)yes (default)application/x-ndjson
csvyesyestext/csv
jsonyesyesapplication/json
parquetnoyesapplication/vnd.apache.parquet
fhirnoyesapplication/fhir+json

In the ndjson, json and csv formats, a DATE value is written as an ISO 8601 date (2026-01-02) and a TIMESTAMP value as an ISO 8601 instant in UTC (2026-01-02T03:04:05Z), whatever the locale and time zone of the server. These are the same strings that the fhir format carries in valueDate and valueInstant. A timestamp parsed from text without a zone, such as the literal TIMESTAMP'2026-01-02 03:04:05', is still read in the Spark session time zone before it is written as an instant.

An explicit _format is parsed strictly: an unrecognised value, or one not available for the resolved kind, is a 400 naming _format. With no _format, the format is negotiated from the Accept header, falling back to NDJSON when nothing matches; content negotiation never fails, since a client that sends a header the server cannot honour still wants a result.

Supporting artefacts​

The context parameter supplies ViewDefinitions, SQLViews, ValueSets and ConceptMaps that the subject depends on, inline with the request. How a subject's dependencies are resolved, and how context entries are matched to them, is described under Dependencies. Value set and concept map dependencies are described under Terminology in SQL. On $sql-run, each value set and concept map is resolved once per request, and its content is current at the time of the request.

Examples​

Run a stored view over GET, as CSV:

GET [base]/$sql-run?subjectCanonical=https://example.org/ViewDefinition/demographics&_format=csv
Accept: text/csv

Run a stored query with a bound parameter:

POST [base]/$sql-run
Content-Type: application/fhir+json
Accept: application/x-ndjson

{
"resourceType": "Parameters",
"parameter": [
{
"name": "subjectReference",
"valueReference": { "reference": "Library/patients-by-family" }
},
{
"name": "parameters",
"resource": {
"resourceType": "Parameters",
"parameter": [{ "name": "family", "valueString": "Smith" }]
}
}
]
}

Run entirely ad hoc, with an inline query and the view it depends on supplied as context:

POST [base]/$sql-run
Content-Type: application/fhir+json

{
"resourceType": "Parameters",
"parameter": [
{ "name": "subjectResource", "resource": { "resourceType": "Library", "...": "..." } },
{ "name": "context", "resource": { "resourceType": "ViewDefinition", "...": "..." } }
]
}

Run a view over data supplied with the request, rather than server data:

POST [base]/$sql-run
Content-Type: application/fhir+json

{
"resourceType": "Parameters",
"parameter": [
{ "name": "subjectResource", "resource": { "resourceType": "ViewDefinition", "...": "..." } },
{ "name": "resource", "valueString": "{\"resourceType\":\"Patient\",\"id\":\"p1\"}" }
]
}

Filter the data every subject reads:

GET [base]/$sql-run?subjectReference=ViewDefinition/demographics&patient=Patient/p1&patient=Patient/p2

Status codes​

StatusCondition
200 OKSuccessful execution, in the negotiated format.
400 Bad RequestNo subject or more than one; an inapplicable conditional parameter; an unsupported _format; source; an unresolvable filter value; a context entry, of any kind, without a url, sharing its url with another entry, or matching no dependency.
404 Not FoundThe subject's canonical or reference resolves to nothing, or a dependency matches no ViewDefinition, SQLView, external table, concept map or value set. The issue names the label and the reference.
404 Not FoundIn server terminology mode, a SNOMED CT implicit concept map URL (http://snomed.info/sct?fhir_cm=...) that the terminology server's ConceptMap search does not return; the issue adds that such maps are resolved only in local terminology mode.
404 Not FoundAn unpinned concept map held in several versions with no determinable latest, or a pinned one held twice at that version; the issue states that the version to use cannot be determined, and why.
422 Unprocessable EntityThe subject is of no admitted kind, or is conformant but cannot be processed (for example a column type _format=fhir cannot express).
422 Unprocessable EntityA value set is found but its membership cannot be determined: a later page of its expansion fails, the terminology layer cannot expand a supplied ValueSet's compose, the local store cannot evaluate the compose, or a supplied ValueSet has neither an expansion nor a compose. The issue names the label, the canonical URL and the reason.
422 Unprocessable EntityA value set's membership exceeds pathling.sqlQuery.valueSetMaxMembers; the issue names the label, the canonical URL and the maximum.
422 Unprocessable EntityA supplied ValueSet's expansion is incomplete (carries an offset, or a total greater than its entry count) or has an entry that does not identify a member; the issue names context.
422 Unprocessable EntityIn server terminology mode, nothing resolves a dependency and at least one terminology lookup had an unknown outcome: $expand of the URL was answered with a failure status other than 404 before any part of an expansion was returned, or the ConceptMap search was answered with a 5xx other than 501 or could not reach the server. There is one issue per failed lookup, value set first, each naming the label, the reference, the operation that failed and the server's reason, without calling the dependency a value set or a concept map.
422 Unprocessable EntityIn server terminology mode, the terminology server cannot be reached while a dependency is looked up as a value set; the issue names the label, the reference and the configured server URL, and no ConceptMap search is attempted.
422 Unprocessable EntityA concept map is found but carries content the relation cannot represent (see Concept maps), or, in server terminology mode, cannot be read once chosen; the issue names the label, the canonical URL and the reason.
422 Unprocessable EntityA concept map has more rows than pathling.sqlQuery.conceptMapMaxMappings; the issue names the label, the canonical URL and the maximum.
422 Unprocessable EntityIn local terminology mode, a SNOMED CT implicit concept map URL carrying a |version pin, which cannot apply since such a URL carries its version in its base.
422 Unprocessable EntityIn local terminology mode, a SNOMED CT implicit concept map URL naming a reference set other than the four association reference sets, such as a simple map; the issue names the label, the canonical URL and the reference set, and states that only the four can be read as a concept map.
500 Internal Server ErrorAn unexpected execution or infrastructure fault.
500 Internal Server ErrorA configured external table cannot be read; the issue names the table's url and never its storage path.

Every 4xx and 5xx response carries an OperationOutcome whose issues name the parameter at fault in expression. A request that is wrong in more than one way is answered with one outcome naming every problem, so it can be corrected in a single round trip.

An unbound parameter is a 400, and a fault in the subject's SQL that only Spark's analyser can catch is a 422; see The SQL.

A fault that only appears when rows are evaluated - for example a column declared as singular that yields more than one value for some resource - is reported the same way as it is by $sql-export: 400 for a failure raised by the query itself, 500 for anything else. The result is streamed, so this is only possible while no rows have been sent. Rows are evaluated in batches, and the first batch is evaluated before anything is written; with _limit, _format=json or _format=parquet, every row that could be returned is evaluated first. Without _limit, a failure in a later batch can arrive after the 200 status and the earlier rows. The server then closes the connection without completing the response, so an HTTP/1.1 client reports an incomplete transfer rather than a result that is silently missing rows. Use $sql-export to have every failure reported as a status.

Conformance​

The operation declares the spec canonical http://hl7.org/fhir/uv/sql-on-fhir/OperationDefinition/SQLRun in the server CapabilityStatement, whose documentation states the per-kind format sets and the parameters this server declines. Pathling serves no OperationDefinition of its own for it.

Configuration and authorisation​

The operation is enabled by pathling.operations.sqlRunEnabled (default true) and guarded by the pathling:sql-run authority. Running a subject also requires read authority for the resource type it projects, and reading a stored subject requires read authority for ViewDefinition or Library. A value set or concept map dependency requires only the operation authority. See authorization.

Python client​

from pathling import PathlingContext

# Subjects and results are exchanged as FHIR Parameters and flat files, so any
# HTTP client will do; the example below uses requests directly.
import requests

response = requests.get(
"https://example.org/fhir/$sql-run",
params={
"subjectReference": "ViewDefinition/demographics",
"_format": "csv",
},
headers={"Accept": "text/csv"},
)
response.raise_for_status()
print(response.text)