SQL on FHIR WG Meetings

The $export draft, and an hour spent working out what _count should actually count — Jul 8, 2025

Nikolai Ryzhikov
Nikolai Ryzhikov
CTO at Health Samurai
Steve Munini
Steve Munini
CEO and CTO, Helios Software
Arjun Sanyal
Arjun Sanyal
Principal Antidote Solutions
Gino Canessa
Gino Canessa
Principal Software Engineer at Microsoft
Adam Culbertson
Adam Culbertson
Senior Director Emerging Technology at b.well Connected Health
Jul 8, 2025

Per-view or per-export?

Steve: wanted them on the export — people work in one format per project, and wanting two formats in one request would be odd.

Gino agreed and put it plainly:

Gino Canessa
Gino Canessa
Principal Software Engineer at Microsoft

I wouldn't want to build a pipeline that produced a Parquet export and a CSV export from the same call. Issue two requests.

The nested per-view parameters came out for the same reason Steve gave — nesting leaves implementers asking when to use the nested slot versus the root, and what happens if you fill in both. It also can't survive a GET, since those only carry simple types.

Why $export takes multiple views at all

Arjun questioned it, given nothing joins them — convenience only?

Nikolai Ryzhikov
Nikolai Ryzhikov
CTO at Health Samurai

One call lets the server snapshot and export every view against the same data. Two separate requests guarantee you nothing.

The problem multiple views created

If you can pass the same view twice with different filters or groups, the outputs need a key, and the view's name isn't unique.

So each view gets an id, defaulting to the view name, and it's only required once you have more than one — the single-view case stays simple.

Paging — dropped, headers as escape route

Steve had started implementing _page and hit a wall: FHIR search has a Bundle with a tidy slot for links, but a CSV or a Parquet file has nowhere to put them.

He found the RFC that standardises pagination links in headers; Gino noted Bundle.link already uses that same RFC and only lives in the body because messaging has no headers. Paging was dropped for now, with headers as the route back in if anyone asks.

The long one — _count

Gino explained the FHIR distinction: _count is page size, while _max-results — added in R5 — is a hard truncation, so the server can throw its cursor away and stop tracking your search.

Steve preferred truncation, since $run is really a preview.

That exposed the deeper question: one resource can produce many rows, so does the number count resources going in or rows coming out?

  • Nikolai: match FHIR and count resources
  • Gino: we're in SQL land, we do it by rows

They landed on _limit, which sidesteps the collision entirely, with the underscore because the FHIR style guide reserves it for parameters that aren't tied to a resource.

Underscoring all parameters — Gino argued against

You'd risk collisions, and plenty of search parameters already carry underscores — _id, _lastUpdated, _profile, _list, _content.

Default type hints — held for John

The shape of the problem surfaced: FHIR dateTime is a strange beast because partial dates make it a range rather than a point, so it won't map to an ISO type, whereas instant looks safely convertible to a timestamp.