SQL on FHIR
The Ignixa.SqlOnFhir package implements the SQL on FHIR v2 specification for projecting FHIR resources into tabular formats.
Installation
dotnet add package Ignixa.SqlOnFhir
Quick Start
using Ignixa.SqlOnFhir.Evaluation;
using Ignixa.SqlOnFhir.Parsing;
using Ignixa.Serialization;
using Ignixa.Specification;
// Get a schema provider
var schemaProvider = FhirVersion.R4.GetSchemaProvider();
// Load a ViewDefinition from JSON
var viewDefJson = File.ReadAllText("patient-view.json");
var viewDefNode = JsonSourceNodeFactory.Parse(viewDefJson);
var viewDefNavigator = viewDefNode.ToSourceNavigator();
// Parse the ViewDefinition into an expression tree
var viewDefExpression = ViewDefinitionExpressionParser.Parse(viewDefNavigator);
// Create an evaluator
var evaluator = new SqlOnFhirEvaluator();
// Load and evaluate a FHIR resource
var resourceJson = File.ReadAllText("patient.json");
var resourceNode = JsonSourceNodeFactory.Parse(resourceJson);
var resourceElement = resourceNode.ToElement(schemaProvider);
// Execute against the FHIR resource
var rows = evaluator.Evaluate(viewDefNavigator, resourceElement);
// Process results
foreach (var row in rows)
{
foreach (var (columnName, value) in row)
{
Console.WriteLine($"{columnName}: {value}");
}
}
CLI Tool
The ignixa-sqlonfhir tool converts FHIR NDJSON to Parquet, CSV, or NDJSON using SQL on FHIR ViewDefinitions. It supports single-file processing and batch directory processing.
Installation
# Install as a global .NET tool
dotnet tool install --global Ignixa.SqlOnFhir.Cli
Usage
# Convert one NDJSON file using R4
ignixa-sqlonfhir r4 run \
--views patient-view.json \
--input patients.ndjson \
--out patients.parquet
# Convert to CSV or NDJSON by changing the output extension
ignixa-sqlonfhir r4 run --views patient-view.json --input patients.ndjson --out patients.csv
ignixa-sqlonfhir r4 run --views patient-view.json --input patients.ndjson --out patients.ndjson
# Batch process ViewDefinitions and resource NDJSON directories
ignixa-sqlonfhir r4 run \
--views views \
--input fhir-ndjson \
--out output \
--format parquet \
--pattern "**/*.json" \
--input-pattern "*{resource}*.ndjson" \
--stats-out output/stats.json
# Preview schema only, or include sample rows with --input
ignixa-sqlonfhir r4 preview --views patient-view.json
ignixa-sqlonfhir r4 preview --views patient-view.json --input patients.ndjson --rows 10
# Validate one ViewDefinition or a directory
ignixa-sqlonfhir r4 validate --views patient-view.json
ignixa-sqlonfhir r4 validate --views views --pattern "**/*.json"
Runtime FHIRPath variables are available for run and preview:
ignixa-sqlonfhir r4 run \
--views patient-view.json \
--input patients.ndjson \
--out patients.csv \
--var cohortId=research-2026 \
--var effectiveDate=2026-01-01
Supported FHIR Versions
stu3- FHIR STU3r4- FHIR R4r4b- FHIR R4Br5- FHIR R5r6- FHIR R6
Features
- ViewDefinition Support: SQL on FHIR v2 / 2.1.0-pre ViewDefinition support with compiled FHIRPath expressions
- Multiple FHIR Versions: Supports STU3, R4, R4B, R5, and R6
- Multiple Output Formats: Export to Parquet, CSV, or NDJSON via the CLI tool
- Batch CLI Processing: Run, preview, or validate ViewDefinitions from directories with resource-aware NDJSON matching
- Runtime Variables: Override ViewDefinition constants through repeatable
--var name=valueCLI arguments - FHIRPath Columns: Define columns using FHIRPath expressions with automatic caching
%rowIndex: 0-based row index environment variable available insideforEach,forEachOrNull, andrepeatiterations- Column Tags: Implementation metadata (
ansi/type, custom tags) attached per column and exposed in schema output - Streaming: CLI tool processes large NDJSON datasets with minimal memory
- Schema Extraction: Automatically extract column schemas (including tags) from ViewDefinitions
ViewDefinition Example
{
"resourceType": "ViewDefinition",
"name": "patient_names",
"resource": "Patient",
"fhirVersion": ["4.0.1"],
"select": [
{ "column": [{ "name": "id", "path": "id" }] },
{
"forEach": "name",
"column": [
{
"name": "row_index",
"path": "%rowIndex",
"type": "integer",
"tag": [{ "name": "ansi/type", "value": "INTEGER" }]
},
{ "name": "family", "path": "family", "type": "string" }
]
}
]
}
%rowIndex
The %rowIndex environment variable is a 0-based integer available inside any forEach, forEachOrNull, or repeat iteration. It resets to 0 for each new outer context. At the top level (outside any iteration) it is always 0.
{
"forEach": "name",
"column": [
{ "name": "name_index", "path": "%rowIndex", "type": "integer" },
{ "name": "family", "path": "family", "type": "string" }
]
}
A common use is constructing surrogate keys: id & '-' & %rowIndex.toString().
Column Tags
Tags attach implementation-specific metadata to a column. They flow through to ColumnSchema.Tags in the schema output and are not evaluated against FHIR data.
{
"name": "family_name",
"path": "name.first().family",
"type": "string",
"tag": [
{ "name": "ansi/type", "value": "VARCHAR(100)" },
{ "name": "custom/indexed", "value": "true" }
]
}
Retrieve tags programmatically:
var schemaEvaluator = new SqlOnFhirSchemaEvaluator();
var schema = schemaEvaluator.GetSchema(viewDefExpression);
foreach (var column in schema)
{
Console.WriteLine($"{column.Name}: {column.Type}");
foreach (var tag in column.Tags ?? [])
Console.WriteLine($" [{tag.Name}] = {tag.Value}");
}