Skip to main content

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 STU3
  • r4 - FHIR R4
  • r4b - FHIR R4B
  • r5 - FHIR R5
  • r6 - 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=value CLI arguments
  • FHIRPath Columns: Define columns using FHIRPath expressions with automatic caching
  • %rowIndex: 0-based row index environment variable available inside forEach, forEachOrNull, and repeat iterations
  • 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}");
}

Conformance Matrix​

Ignixa runs the shared sql-on-fhir.js test-suite definitions through its own .NET evaluator in CI, and the conformance matrix compares the test-by-test results against other known SQL on FHIR implementations:

Open Conformance Matrix

  • Raw Report: test_report.json
  • Other implementations' results are mirrored locally at docs-deploy time, so the matrix stays available even if a third-party host is down.