296 lines
14 KiB
Markdown
296 lines
14 KiB
Markdown
# HowTo: Der FHIR `date`-Suchparameter – Präfixe, Intervalle und Kombinationen richtig verstehen
|
||
|
||
Wer FHIR-Server baut oder abfragt, stolpert früher oder später über den
|
||
`date`-Suchparameter. Auf den ersten Blick sieht er simpel aus –
|
||
`?date=2023-06-15` – aber sobald Präfixe wie `ge`, `sa` oder `ap` ins Spiel
|
||
kommen und mehrere `date`-Parameter kombiniert werden, wird es schnell
|
||
unübersichtlich. Dieser Artikel bringt Ordnung rein: Schritt für Schritt, mit
|
||
Zeitleisten-Grafiken zu jedem Fall — und als Kurzreferenz zum schnellen
|
||
Nachschlagen.
|
||
|
||
## Inhaltsverzeichnis
|
||
|
||
- [Kurzreferenz: Alle Präfixe auf einen Blick](#kurzreferenz-alle-präfixe-auf-einen-blick)
|
||
- [1. Das Grundprinzip: FHIR vergleicht Intervalle, keine Zeitpunkte](#1-das-grundprinzip-fhir-vergleicht-intervalle-keine-zeitpunkte)
|
||
- [2. Die neun Präfixe im Überblick](#2-die-neun-präfixe-im-überblick)
|
||
- [3. Matching gegen `Period` und `Range`](#3-matching-gegen-period-und-range)
|
||
- [4. Offene (unvollständige) Periods](#4-offene-unvollständige-periods)
|
||
- [5. Mehrere `date`-Parameter kombinieren](#5-mehrere-date-parameter-kombinieren)
|
||
- [6. Die vollständige 3×3-Matrix aller Bereichs-Kombinationen](#6-die-vollständige-3×3-matrix-aller-bereichs-kombinationen)
|
||
- [7. Sonderfälle: redundant oder widersprüchlich](#7-sonderfälle-wenn-kombinationen-redundant-oder-widersprüchlich-werden)
|
||
- [8. Praxis-Empfehlungen zum Schluss](#8-praxis-empfehlungen-zum-schluss)
|
||
|
||
---
|
||
|
||
## Kurzreferenz: Alle Präfixe auf einen Blick
|
||
|
||
Für den schnellen Blick zwischendurch — Details und Grafiken zu jeder Zeile
|
||
folgen in den Abschnitten unten.
|
||
|
||
| Präfix | Name | Kurzregel | Beispiel |
|
||
|---|---|---|---|
|
||
| *(keins)* | implizit `eq` | Precision des Werts bestimmt das Intervall | `date=2023-06-15` |
|
||
| `eq` | gleich | Resource-Intervall vollständig im Such-Intervall | `date=eq2023-06-15` |
|
||
| `ne` | ungleich | Komplement von `eq` | `date=ne2023-06-15` |
|
||
| `gt` | größer als | Ragt über das Ende hinaus (Overlap genügt) | `date=gt2023-06-15` |
|
||
| `lt` | kleiner als | Beginnt vor dem Anfang (Overlap genügt) | `date=lt2023-06-15` |
|
||
| `ge` | größer/gleich | `gt` oder vollständig enthalten (`eq`) | `date=ge2023-06-15` |
|
||
| `le` | kleiner/gleich | `lt` oder vollständig enthalten (`eq`) | `date=le2023-06-15` |
|
||
| `sa` | starts after | Vollständig danach, **keine** Überlappung | `date=sa2023-06-15` |
|
||
| `eb` | ends before | Vollständig davor, **keine** Überlappung | `date=eb2023-06-15` |
|
||
| `ap` | approximately | Überlappung + ähnliche Größenordnung | `date=ap2023-06-15` |
|
||
|
||
**Fehlender `start`/`end` bei `Period`:** fehlender `start` = `-∞`, fehlendes
|
||
`end` = `+∞` → `sa` kann bei fehlendem `start` nie matchen, `eb` kann bei
|
||
fehlendem `end` nie matchen (Details in [Abschnitt 4](#4-offene-unvollständige-periods)).
|
||
|
||
---
|
||
|
||
## 1. Das Grundprinzip: FHIR vergleicht Intervalle, keine Zeitpunkte
|
||
|
||
Der wichtigste gedankliche Reset zuerst: FHIR behandelt Datumswerte nie als
|
||
exakten Zeitpunkt, sondern immer als **Intervall**. Wie breit dieses Intervall
|
||
ist, hängt von der Precision des angegebenen Werts ab:
|
||
|
||
- `2023` → das ganze Jahr 2023
|
||
- `2023-06` → der ganze Juni 2023
|
||
- `2023-06-15` → der ganze 15. Juni 2023 (00:00–24:00 Uhr)
|
||
- `2023-06-15T10:00:00` → (fast) ein exakter Zeitpunkt
|
||
|
||
Jeder Vergleichsoperator vergleicht anschließend **zwei Intervalle**
|
||
miteinander: das Such-Intervall aus deinem Query-Parameter und das
|
||
Resource-Intervall aus dem Feld der Ressource (z. B. `Patient.birthDate` oder
|
||
`Encounter.period`).
|
||
|
||
### Ohne Präfix = `eq`, aber die Precision entscheidet mit
|
||
|
||
Gibst du kein Präfix an, nimmt der Server `eq` an. Das Resource-Datum muss
|
||
dann **vollständig** innerhalb deines Such-Intervalls liegen. Je gröber die
|
||
Precision, desto breiter das Intervall – und desto mehr Resource-Werte passen
|
||
hinein:
|
||
|
||

|
||
|
||
`date=2023-06-15` matched nur diesen einen Tag. `date=2023-06` matched dagegen
|
||
jeden Tag im Juni – auch den 15. Das ist der häufigste Stolperstein für
|
||
Einsteiger: eine „ungenaue" Suchanfrage ist nicht strenger, sondern lockerer.
|
||
|
||
---
|
||
|
||
## 2. Die neun Präfixe im Überblick
|
||
|
||
FHIR definiert insgesamt neun Präfixe für `date`. Jeder davon beschreibt eine
|
||
andere Beziehung zwischen Such-Intervall (rot gestrichelt in der Grafik) und
|
||
Treffer-Bereich (grün/farbig schattiert):
|
||
|
||

|
||
|
||
| Präfix | Name | Bedeutung |
|
||
|---|---|---|
|
||
| `eq` | gleich | Resource-Intervall liegt vollständig im Such-Intervall |
|
||
| `ne` | ungleich | Komplement von `eq` |
|
||
| `gt` | größer als | Resource-Intervall **ragt über** das Ende des Such-Intervalls hinaus |
|
||
| `lt` | kleiner als | Resource-Intervall **beginnt vor** dem Anfang des Such-Intervalls |
|
||
| `ge` | größer/gleich | `gt` **oder** vollständig im Such-Intervall enthalten (`eq`) |
|
||
| `le` | kleiner/gleich | `lt` **oder** vollständig im Such-Intervall enthalten (`eq`) |
|
||
| `sa` | starts after | Beginnt vollständig **nach** dem Ende des Such-Intervalls, keine Überlappung |
|
||
| `eb` | ends before | Endet vollständig **vor** dem Beginn des Such-Intervalls, keine Überlappung |
|
||
| `ap` | approximately | Überlappung, plus ähnliche Größenordnung der Intervalle |
|
||
|
||
**Wichtig — Containment vs. Overlap:** Nur `eq` (und implizit `sa`/`eb`)
|
||
verlangen, dass das Resource-Intervall **vollständig** innerhalb bzw.
|
||
**vollständig getrennt** vom Such-Intervall liegt. Bei `gt`, `lt`, `ge`
|
||
und `le` reicht dagegen eine **Überlappung** — das Resource-Intervall muss
|
||
nicht komplett außerhalb des Suchbereichs liegen, es genügt, dass es über
|
||
die jeweilige Grenze hinausragt. Details dazu im nächsten Abschnitt.
|
||
|
||
---
|
||
|
||
## 3. Matching gegen `Period` und `Range`
|
||
|
||
Die Grafiken oben zeigen das Resource-Datum als schmalen roten Marker – das
|
||
passt für einfache `date`/`dateTime`-Felder wie `Patient.birthDate`. Sobald
|
||
das Zielfeld selbst ein **Intervall** ist (`Period`, z. B.
|
||
`Encounter.period`, `Condition.onsetPeriod`, oder `Range`), wird die
|
||
Unterscheidung zwischen **Containment** (vollständiges Enthaltensein) und
|
||
**Overlap** (bloße Überlappung) entscheidend:
|
||
|
||
- **`eq`** verlangt Containment: das gesamte Resource-Intervall muss im
|
||
Such-Intervall liegen.
|
||
- **`gt` / `lt` / `ge` / `le`** verlangen nur Overlap: es reicht, wenn das
|
||
Resource-Intervall über die jeweilige Grenze hinausragt – auch wenn ein
|
||
Teil davon auf der "falschen" Seite liegt.
|
||
- **`sa` / `eb`** verlangen das Gegenteil von Overlap: das Resource-Intervall
|
||
muss vollständig und ohne jede Überlappung auf der jeweiligen Seite liegen.
|
||
|
||
Bei einem einfachen Zeitpunkt-Feld (kein `Period`) fällt dieser Unterschied
|
||
praktisch nicht auf, da Resource-Start und -Ende (fast) identisch sind — dort
|
||
verhalten sich `gt` und `sa` de facto gleich. Der Unterschied wird erst bei
|
||
echten Zeitspannen relevant. Das ist auch der Grund, warum `sa`/`eb`
|
||
laut Spezifikation in erster Linie für `Period`/`Range`-Werte gedacht sind.
|
||
|
||
**Beispiel:** Ein Encounter läuft vom 10. bis 20. Juni. Die Suche
|
||
`date=gt2023-06-15` soll ihn finden, obwohl der Encounter *vor* dem 15.
|
||
begonnen hat — entscheidend ist nur, dass er über den 15. hinausreicht
|
||
(Overlap mit dem Bereich "nach dem 15."). Bei `date=sa2023-06-15` würde
|
||
derselbe Encounter dagegen **nicht** matchen, weil er nicht vollständig nach
|
||
dem 15. liegt — genau das ist der praktische Unterschied zwischen `gt` und
|
||
`sa`.
|
||
|
||
Die folgende Grafik macht das für alle vier "einseitigen" Präfixe (`gt`,
|
||
`sa`, `lt`, `eb`) an denselben drei Beispiel-Periods sichtbar — einer
|
||
Period komplett davor, einer, die die Suchgrenze überlappt, und einer
|
||
komplett danach:
|
||
|
||

|
||
|
||
Man sieht deutlich: `gt` und `lt` (blau schattierter Bereich, durchgezogene
|
||
Grenzlinie) lassen die überlappende Period B als Treffer durch, während `sa`
|
||
und `eb` (gestrichelte Grenzlinie) sie ablehnen — nur eine Period, die
|
||
komplett auf der richtigen Seite liegt, zählt dort.
|
||
|
||
| Encounter-Zeitraum | `date=gt2023-06-15` | `date=sa2023-06-15` |
|
||
|---|---|---|
|
||
| 10.–20. Juni (überlappt die Grenze) | ✅ Treffer (Overlap) | ❌ kein Treffer (nicht vollständig danach) |
|
||
| 16.–20. Juni (komplett danach) | ✅ Treffer | ✅ Treffer |
|
||
| 01.–10. Juni (komplett davor) | ❌ kein Treffer | ❌ kein Treffer |
|
||
|
||
---
|
||
|
||
## 4. Offene (unvollständige) Periods
|
||
|
||
In der Praxis sind Periods oft **nicht abgeschlossen** — z. B. ein laufender
|
||
Encounter ohne `end`, oder ein importierter Datensatz mit unbekanntem
|
||
`start`. Der FHIR-Standard regelt das eindeutig:
|
||
|
||
> Ein fehlender unterer Rand (`start`) gilt implizit als **kleiner als jedes
|
||
> reale Datum** (also `-∞`). Ein fehlender oberer Rand (`end`) gilt implizit
|
||
> als **größer als jedes reale Datum** (also `+∞`).
|
||
|
||
Das hat spürbare Konsequenzen, gerade für `sa` und `eb`:
|
||
|
||
- Ein laufender Encounter (`start` gesetzt, `end` fehlt → `end = +∞`)
|
||
kann **niemals** `eb` (ends before) matchen — sein Ende liegt ja nie vor
|
||
irgendeinem endlichen Datum. Er matcht aber sehr wohl `gt`, weil sein
|
||
(unendliches) Ende immer über jede Suchgrenze hinausragt.
|
||
- Ein Encounter mit unbekanntem Start (`start` fehlt → `start = -∞`) kann
|
||
**niemals** `sa` (starts after) matchen — sein Beginn liegt ja nie nach
|
||
irgendeinem endlichen Datum. Er matcht aber `lt`, weil sein (unendlicher)
|
||
Anfang immer vor jede Suchgrenze hineinreicht.
|
||
- Eine Period ganz ohne `start` und `end` überlappt praktisch **jeden**
|
||
overlap-basierten Filter (`gt`, `lt`, `ge`, `le`) — sie „reicht" ja per
|
||
Definition über jede Grenze hinaus.
|
||
|
||
Dieselben vier Präfixe wie in [Abschnitt 3](#3-matching-gegen-period-und-range),
|
||
jetzt mit offenen statt geschlossenen Periods:
|
||
|
||

|
||
|
||
**Praktische Konsequenz:** Wenn du „laufende" Ressourcen (z. B. aktive
|
||
Encounter, offene Verordnungen) gezielt aus einer Zeitraumsuche ausschließen
|
||
willst, reicht `eb`/`sa` allein nicht — du musst zusätzlich nach dem Status
|
||
oder explizit nach `end:missing=true`/`start:missing=true` filtern, je
|
||
nachdem, was dein Server unterstützt.
|
||
|
||
---
|
||
|
||
## 5. Mehrere `date`-Parameter kombinieren
|
||
|
||
In der Praxis reicht ein einzelner Präfix selten – meist willst du einen
|
||
**Bereich** definieren. FHIR-Server verknüpfen mehrere `date`-Parameter in
|
||
derselben Query immer per **UND**. Vier typische Muster:
|
||
|
||

|
||
|
||
| Kombination | Beispiel | Charakter |
|
||
|---|---|---|
|
||
| `ge` + `le` | `ge2023-01-01&le2023-12-31` | Geschlossenes Intervall (beide Grenzen inklusive) |
|
||
| `gt` + `lt` | `gt2023-01-01<2023-12-31` | Offenes Intervall (beide Grenzen exklusive) |
|
||
| `sa` + `eb` | `sa2023-01-01&eb2023-12-31` | Strikt getrennter Bereich, primär für Period-Werte |
|
||
| `ge` + `lt` | `ge2023-06-01<2023-07-01` | Halboffenes Intervall `[start, ende)` – Standardmuster für „genau ein Monat" |
|
||
|
||
Wenn du in Timestamp-lastigen ETL-Pipelines mit `[start, ende)` arbeitest,
|
||
kommt dir `ge` + `lt` bekannt vor – es ist exakt dasselbe Muster wie bei
|
||
klassischen Zeitfenster-Queries in SQL.
|
||
|
||
---
|
||
|
||
## 6. Die vollständige 3×3-Matrix aller Bereichs-Kombinationen
|
||
|
||
Die vier Muster oben sind nur die gebräuchlichsten. Tatsächlich lässt sich
|
||
**jede** Untergrenze mit **jeder** Obergrenze kombinieren:
|
||
|
||
- Untergrenzen: `ge`, `gt`, `sa`
|
||
- Obergrenzen: `le`, `lt`, `eb`
|
||
|
||
Das ergibt 3 × 3 = **9 mögliche Kombinationen**, alle spec-konform:
|
||
|
||

|
||
|
||
Sie unterscheiden sich nur darin, ob die jeweilige Grenze inklusiv
|
||
(durchgezogene Linie) oder exklusiv (gestrichelte Linie) ist, und ob es sich
|
||
um einen einfachen Werte-Vergleich (`ge`/`gt`/`le`/`lt`) oder einen strengen
|
||
Period-Vergleich (`sa`/`eb`) handelt. Es gibt **keine explizite
|
||
Ausschluss-Regel** im FHIR-Standard – jede Zelle dieser Matrix ist eine
|
||
gültige, funktionierende Query.
|
||
|
||
---
|
||
|
||
## 7. Sonderfälle: Wenn Kombinationen redundant oder widersprüchlich werden
|
||
|
||
Nicht jede syntaktisch gültige Kombination ist auch praktisch sinnvoll. Drei
|
||
Kategorien solltest du kennen, bevor du sie versehentlich in einer
|
||
generierten Query landen lässt:
|
||
|
||

|
||
|
||
### a) Redundante Kombinationen
|
||
Ändern nichts am Ergebnis, sind aber technisch kein Fehler:
|
||
- `eq` + eine Grenze, die vom `eq`-Intervall ohnehin erfüllt wird
|
||
(`eq2023-06-15&ge2020-01-01`) – die Zusatzbedingung ist überflüssig.
|
||
- Zwei Bedingungen desselben Richtungstyps
|
||
(`ge2020-01-01&ge2023-01-01`) – nur die strengere (spätere) Untergrenze
|
||
wirkt effektiv.
|
||
|
||
### b) Kombinationen mit garantiert leerem Ergebnis
|
||
Strukturell unmöglich zu erfüllen:
|
||
- `eq` + `ne` auf denselben Wert (`eq2023-06-15&ne2023-06-15`) –
|
||
sich gegenseitig ausschließende Bedingungen.
|
||
- Untergrenze liegt zeitlich nach der Obergrenze
|
||
(`ge2023-06-01&le2023-01-01`) – kein Datum erfüllt beides gleichzeitig.
|
||
|
||
Der Server wird solche Queries **syntaktisch akzeptieren** und einfach eine
|
||
leere Ergebnismenge zurückgeben – kein Fehler, aber auch keine hilfreiche
|
||
Rückmeldung. Es lohnt sich, so etwas clientseitig vorab zu validieren.
|
||
|
||
### c) Ungewöhnliche, aber gültige Sonderfälle
|
||
- **Drei oder mehr `date`-Parameter**, z. B. ein Jahresbereich mit
|
||
ausgeschlossenem Einzeltag: `ge2023-01-01&le2023-12-31&ne2023-06-15`.
|
||
- **`ap` kombiniert mit einer Grenze**, z. B.
|
||
`ap2023-06-15&ge2023-01-01` – selten genutzt, da `ap` selbst schon
|
||
unscharf ist und die Toleranzberechnung serverabhängig bleibt.
|
||
|
||
---
|
||
|
||
## 8. Praxis-Empfehlungen zum Schluss
|
||
|
||
- Für einfache Bereichsfilter ist **`ge` + `lt`** (halboffenes Intervall) meist
|
||
das robusteste Muster – es vermeidet Rundungsprobleme an der
|
||
Precision-Grenze (kein Off-by-one bei „bis einschließlich 31.12.").
|
||
- **`sa`/`eb`** bewusst einsetzen, wenn du bei `Period`/`Range`-Feldern eine
|
||
*vollständige* Trennung brauchst (siehe [Abschnitt 3](#3-matching-gegen-period-und-range))
|
||
– z. B. „Encounter vollständig nach Entlassung X". Für einen simplen
|
||
„ab Datum X"-Filter ist meist `ge`/`gt` gemeint, nicht `sa`.
|
||
- Bei `Period`-Feldern mit möglichen offenen Enden (siehe
|
||
[Abschnitt 4](#4-offene-unvollständige-periods)) nicht vergessen: `sa`/`eb`
|
||
greifen dort strukturell nie — ggf. zusätzlich `:missing` oder Status
|
||
filtern.
|
||
- Vor dem Absetzen einer Multi-Parameter-Query lohnt sich ein kurzer Check auf
|
||
logische Konsistenz zwischen Unter- und Obergrenze, um leere Ergebnisse
|
||
durch Konfigurationsfehler in der eigenen Pipeline zu vermeiden.
|
||
|
||
Mit der Kurzreferenz oben und den sechs Grafiken im Hinterkopf – ohne Präfix,
|
||
alle neun Präfixe einzeln, Containment vs. Overlap bei Period/Range (offen
|
||
wie geschlossen), typische Kombinationen, die volle 3×3-Matrix und die
|
||
Sonderfälle – sollte die nächste `date`-Query im FHIR-Suchparameter kein
|
||
Rätsel mehr sein. |