Das Problem bei der Ausführung von eCQMs
Elektronische klinische Qualitätskennzahlen (eCQMs) sind das Instrument, mit dem Programme die Versorgungsqualität messen – ob anspruchsberechtigte Patienten ihr kolorektales Screening erhalten haben oder ob ihr Bluthochdruck kontrolliert ist. Die CMS- und HEDIS-Kennzahlen, über die jedes wertorientierte Versorgungsprogramm berichtet, werden in CQL (Clinical Quality Language) verfasst. Die übliche Ausführungsmethode ist eine dedizierte CQL-Engine: eine eigenständige Laufzeitumgebung, die die Kennzahl parsed, für Terminologie und Patientendaten auf den FHIR-Server zurückgreift und einen MeasureReport zurückgibt.
Das funktioniert gut, hat aber seinen Preis:
- Eine separate Compute-Ebene. Eine CQL-Engine ist eine eigene Laufzeitumgebung – sie wird neben dem FHIR-Server bereitgestellt und skaliert, und die Populationsberechnung findet außerhalb der Datenbank statt. Das ist echte Infrastruktur und bei großen Populationen ein Skalierungsproblem.
- Schwer erklärbar. Wenn ein Patient als Versorgungslücke auftaucht, können Sie einem Kliniker genau zeigen, warum? Dazu müsste man Engine-Interna und ValueSet-Expansionen nachverfolgen – nicht einfach eine Abfrage lesen.
Hier ist die Frage, die dieser Beitrag beantwortet: Was wäre, wenn die Kennzahlenlogik einfach SQL wäre, das dort ausgeführt wird, wo Ihre Daten bereits liegen?
Das ist tatsächlich möglich – und das Ergebnis ist mehr als ein Performance-Trick: Mit SQL on FHIR wird das gesamte Wissen, wie eine Kennzahl berechnet wird, zu portablen, standardkonformen FHIR-Artefakten, die sich in ein FHIR-Paket packen und auf einem anderen Server installieren lassen. Dies ist eine technische Schritt-für-Schritt-Erklärung, wie das funktioniert – mit einem vollständigen Arbeitsbeispiel, das Sie klonen und ausführen können.
Die Idee: Kennzahlen sind Mengenlogik, und SQL ist eine Mengensprache
Eine Qualitätskennzahl ist im Kern Mengenarithrmetik über eine Patientenpopulation:
- Initial Population – wer anspruchsberechtigt ist (Alter, Encounters, eine Diagnose).
- Denominator / Exclusions – wer gezählt wird, abzüglich derjenigen, die ausgeschlossen werden (Hospiz, Palliativversorgung, Gebrechlichkeit…).
- Numerator – wer die Kennzahl erfüllt hat (ein Screening, ein kontrollierter Messwert).
- Score –
numerator / (denominator − exclusions).
Jede dieser Größen ist eine Menge von Patienten. SQL ist sehr gut in Mengenoperationen. Das Einzige, was zwischen FHIR-Daten und einer SQL-Abfrage steht, ist, dass FHIR-Ressourcen tief verschachteltes JSON sind und Terminologie-Zugehörigkeit („Ist dieser Code im ValueSet?") keine Spalte ist, nach der man filtern kann. SQL on FHIR löst beides.
Ebene 1: FHIR mit ViewDefinitions in Tabellen abflachen
Eine ViewDefinition ist eine FHIR-Ressource, die beschreibt, wie ein Ressourcentyp in eine flache Tabelle umgewandelt wird. Wendet man sie auf Encounter an und materialisiert sie, erhält man eine übersichtliche Tabelle sof.encounter_flat mit den Spalten patient_id, type_system, type_code, status, period_start und so weiter – die Kennzahlenlogik liest einfache Spalten, anstatt durch verschachtelte FHIR-Strukturen zu navigieren.
Das Beispiel enthält eine ViewDefinition pro Ressourcentyp, den die Kennzahlen verwenden – Patient, Encounter, Condition, Observation, Procedure und einige weitere –, die jeweils in das Schema sof.* materialisiert und über eine schlanke Wrapper-View bereitgestellt werden. Diese gemeinsame flache Ebene wird von jeder Kennzahl wiederverwendet.
Ebene 2: Terminologie als JOIN, nicht als $expand
Der andere schwierige Teil einer Kennzahl ist die Codemengenzugehörigkeit: „Ist dieser Encounter einer der sieben Arten qualifizierender Besuche?" In einem CQL-Engine-Setup wird das typischerweise zur Laufzeit gegen einen Terminologiedienst aufgelöst. Bei SQL on FHIR flacht man die ValueSet-Expansionen einmalig in eine concepts-Tabelle ab – eine Zeile pro (valueset_url, system, code) – und Zugehörigkeit wird zu einem gewöhnlichen Join:
JOIN concepts c
ON c.system = e.type_system
AND c.code = e.type_code
AND c.valueset_url = 'http://cts.nlm.nih.gov/fhir/ValueSet/…'
Kein Netzwerk-Roundtrip, keine patientenspezifische Expansion. Das ValueSet wird offline expandiert und indiziert, sodass die Prüfung „Zählt der Code dieses Patienten?" mengenbasiert und schnell ist.
Ebene 3: Die Kennzahl ist eine einzige SQL-Abfrage
Mit flachen Tabellen und einem verfügbaren concepts-Join wird eine gesamte Kennzahl zu einer einzigen Abfrage aus CTEs – und sie liest sich bemerkenswert nah an der englischen Klartextdefinition der Kennzahl. Hier ist die Initial Population von CMS130 (Kolorektales Krebsscreening) – Patienten im Alter von 46–75 Jahren mit einem qualifizierenden Encounter während des Messzeitraums:
WITH mp AS (
SELECT '2026-01-01'::timestamptz AS mp_start,
'2026-12-31'::timestamptz AS mp_end
),
qualifying_encounters AS (
SELECT DISTINCT e.patient_id
FROM encounter_flat e
JOIN concepts c
ON c.system = e.type_system
AND c.code = e.type_code
AND c.valueset_url IN (
-- ValueSet URLs shortened for readability; full URLs in the example repo
'http://cts.nlm.nih.gov/fhir/ValueSet/…', -- Office Visit
'http://cts.nlm.nih.gov/fhir/ValueSet/…', -- Annual Wellness Visit
'…' -- + 5 more
)
CROSS JOIN mp
WHERE e.status = 'finished'
AND e.period_start BETWEEN mp.mp_start AND mp.mp_end
),
initial_population AS (
SELECT p.id AS patient_id
FROM patient_flat p
CROSS JOIN mp
WHERE EXTRACT(YEAR FROM AGE(mp.mp_end, p.birth_date::date)) BETWEEN 46 AND 75
AND p.id IN (SELECT patient_id FROM qualifying_encounters)
)
-- … denominator exclusions, numerator, and the final score follow as more CTEs
Der Numerator fügt einige weitere CTEs hinzu (Koloskopie innerhalb von 9 Jahren, FOBT während des Zeitraums usw.), Exclusions vereinen Hospiz/Palliativversorgung/Gebrechlichkeit, und ein abschließendes SELECT berechnet den Score. Der Punkt: Die Kennzahl ist lesbar. Man kann sie lesen, mit der Spezifikation vergleichen und mit SELECT * FROM initial_population genau sehen, wer sich qualifiziert hat.
Gemeinsame Logik bleibt gemeinsam
Ausschlüsse wie Hospiz, Palliativversorgung und schwere Erkrankung mit Gebrechlichkeit kommen in vielen Kennzahlen vor. Im Beispiel wurden sie in wiederverwendbare Bausteine ausgelagert, anstatt kopiert zu werden, sodass eine Korrektur der Hospizlogik diese überall gleichzeitig behebt.
Aber wer schreibt all dieses SQL?
Der offensichtliche Einwand: Die manuelle Übersetzung der CQL einer Kennzahl in SQL klingt nach viel sorgfältiger, fehleranfälliger Arbeit – und es gibt Dutzende von eCQMs. In der Praxis ist dies genau die Art von Aufgabe, die moderne KI-Coding-Assistenten gut bewältigen. CQL und SQL sind beide strukturierte, gut spezifizierte Sprachen, und die Logik einer Kennzahl lässt sich sauber auf das oben gezeigte CTE-Muster abbilden (Initial Population → Exclusions → Numerator → Score).
Das ist keine Hoffnung – so wurde das Beispiel selbst erstellt. Jede Kennzahl wurde mit einem KI-Assistenten aus dem veröffentlichten CQL in SQL übersetzt und anschließend anhand der CMS-Referenz-MeasureReport-Fixtures patientenweise verifiziert, bis die Zahlen exakt übereinstimmten. Die KI übernimmt die mechanische Übersetzung; die Fixtures sorgen für Verlässlichkeit.
Die Referenz-Fixtures machen die KI-gestützte Übersetzung vertrauenswürdig: Erwartete MeasureReport-Ergebnisse für die Testpatienten werden zusammen mit dem Kennzahleninhalt im Repository dqm-content-qicore-2025 veröffentlicht, sodass jede übersetzte Kennzahl gegen Grundwahrheitswerte geprüft werden kann – nicht auf Vertrauen hin akzeptiert wird.
Standardkonformität herstellen: SQLQuery + Measure/$evaluate-measure
SQL intern auszuführen ist gut, aber das Ziel ist ein konformer FHIR-Dienst, kein Datenbankskript. Zwei SQL-on-FHIR-Bausteine schließen diese Lücke:
- Das SQL jeder Kennzahl wird in Aidbox als SQLQuery-
Library-Ressource (ein SQL-on-FHIR-Profil aufLibrary) gespeichert und mit der Operation$sqlquery-runaufgerufen. Die Berechnungslogik lebt im FHIR-Server, als erstklassige Ressourcen – nicht im Anwendungscode. - Der Dienst beantwortet die standardmäßige FHIR R4-Operation
Measure/$evaluate-measureund gibt einen ordentlichenMeasureReportzurück – sodass jeder FHIR-Client ihn genauso konsumieren kann wie ein CQL-Engine-Ergebnis.
Jede SQLQuery Library deklariert außerdem depends-on-Abhängigkeiten zu den ViewDefinitions, die sie liest, und gibt Ihnen einen abfragbaren Graphen: Kennzahl → Views → Ressourcen. Alles, was eine Kennzahl benötigt – die flachen Views, die Terminologie, die Berechnung – ist jetzt als standardmäßige FHIR-Ressourcen im FHIR-Server ausgedrückt. Das schafft die Voraussetzung für den eigentlichen Mehrwert.
Die evaluate-measure-App in der Mitte enthält keine Kennzahlenlogik: Sie löst die richtige SQLQuery Library auf, ruft $sqlquery-run auf und formt die zurückgegebenen Zeilen zu einem MeasureReport. Die Berechnung selbst läuft in der Datenbank.
Der Mehrwert: Die gesamte Kennzahlensammlung ist ein portables FHIR-Paket
Da jeder Teil einer Kennzahl nun eine standardmäßige FHIR-Ressource ist, lässt sich die gesamte Sammlung in ein einziges FHIR-NPM-Paket packen: Terminologie (CodeSystems + ValueSets), die ViewDefinitions, die die Daten abflachen, und die SQLQuery Libraries, die die Berechnungslogik enthalten. Im Beispiel enthält dieses Paket 170 Ressourcen über ein Dutzend Kennzahlen – 10 CodeSystems, 107 ValueSets, 10 ViewDefinitions und 43 SQLQuery Libraries.
Das Paketformat ist standardisiert – dasselbe FHIR-NPM-Format, in dem Implementation Guides ausgeliefert werden –, sodass jeder FHIR-Server es mit seinem eigenen Paketinstallationsmechanismus laden kann; in Aidbox ist das ein $fhir-package-install-Aufruf beim Serverstart, und die Definitionen sind einfach vorhanden. Und hier ist der entscheidende Punkt: Dieses Paket ist nicht an Aidbox gebunden. Es sind standardmäßige SQL-on-FHIR-Bausteine – ViewDefinitions und SQLQuery Libraries, die durch die SQL on FHIR-Spezifikation definiert sind. Übertragen Sie das Paket auf einen beliebigen FHIR-Server, der diese Bausteine implementiert, installieren Sie es, und die gleichen Kennzahlen berechnen sich auf die gleiche Weise. Wie jeder Server sie ausführt, ist ein Implementierungsdetail – manche nutzen eine separate SQL-on-FHIR-Engine, andere führen sie datenbankinnern aus.
Aidbox wählt den datenbankinternen Weg: Die ViewDefinitions werden in Tabellen oder Views materialisiert, und die SQLQuery Libraries werden als natives SQL ausgeführt – direkt in PostgreSQL, genau dort, wo die Daten bereits liegen. Das Paket lässt sich also installieren und ausführen, ohne für die Berechnung selbst etwas Zusätzliches aufzusetzen: keine Ausführungs-Engine, die bereitgestellt, skaliert oder synchron gehalten werden muss. Die Kennzahlenlogik wird genauso verteilt wie ein Implementation Guide – als standardisierte, teilbare FHIR-Artefakte.
Ein Ergebnis untersuchen
Erinnern Sie sich an das Problem „schwer erklärbar"? Hier bekommt es eine direkte Antwort. Fragen Sie nach einem einzelnen Patienten, und der MeasureReport enthält ein evaluatedResource-Array: echte FHIR-Ressourcenreferenzen, jede über die standardmäßige cqf-criteriaReference-Extension mit der Population markiert, die sie erfüllt.
"evaluatedResource": [
{
"reference": "Encounter/abc",
"extension": [
{ "url": ".../cqf-criteriaReference", "valueString": "initial-population" },
{ "url": ".../cqf-criteriaReference", "valueString": "denominator" }
]
},
{
"reference": "Procedure/xyz",
"extension": [
{ "url": ".../cqf-criteriaReference", "valueString": "numerator" }
]
}
]
„Warum befindet sich dieser Patient im Numerator?" – diese Procedure. „Warum ausgeschlossen?" – dieser Hospiz-Encounter. Jede Referenz löst sich zu einer Ressource auf, die im Server existiert, sodass der nächste Schritt der Untersuchung nur ein GET ist.
Für populationsweite Untersuchungen enthält jede Kennzahl außerdem eine -evidence-SQLQuery Library: eine Zeile pro Patient mit der vollständigen Entscheidungskette – welcher Pfad den Numerator erfüllt hat (Koloskopie vs. FOBT vs. keines von beiden), die auslösende Ressource mit Code und Datum sowie welcher Ausschluss ausgelöst wurde. Das ist eine Arbeitsliste für Versorgungslücken – wer fehlt beim Screening und was genau fehlt – als eine einzige Abfrage. Die Demo-App des Beispiels macht diese Abfragen anklickbar: eine kennzahlenübergreifende Arbeitsliste, eine patientenspezifische 360-Grad-Ansicht mit Evidence-Drill-down und exportierbare Outreach-Listen.

Und wenn eine Zahl noch immer falsch aussieht, ist jede Population ein benannter CTE: SELECT * FROM initial_population und dann schrittweise eingrenzen – keine Engine-Interna, die nachverfolgt werden müssen.
Selbst ausprobieren
Alles oben Beschriebene ist ein vollständiges, quelloffenes Beispiel im Aidbox-Beispiel-Repository – ein Dutzend CMS-Kennzahlen (CMS130, CMS165, CMS125, CMS131 und weitere) mit Beispielpatientendaten und einer interaktiven Demo-App.
→ github.com/Aidbox/examples · aidbox-custom-operations/measure-evaluate
Befolgen Sie die Anweisungen in der README, um den gesamten Stack lokal auszuführen: Starten Sie Aidbox mit dem beim Boot installierten Kennzahlenpaket, laden Sie den Beispieldatensatz, berechnen Sie die Kennzahlen über die standardmäßige Operation Measure/$evaluate-measure und erkunden Sie die Evidenz jedes Patienten in der Demo-App-Oberfläche. Was zurückkommt, ist ein einfacher FHIR-MeasureReport mit den Populationszählungen und dem Score – berechnet durch SQL, innerhalb von Aidbox, ohne CQL-Engine irgendwo im Stack.

Fazit
CQL ist eine gute Authoring-Sprache für Qualitätskennzahlen. Aber sie muss nicht Ihre Ausführungs-Engine sein. Wenn Sie FHIR mit ViewDefinitions abflachen, Terminologie in einen Join umwandeln und jede Kennzahl als SQLQuery Library hinter Measure/$evaluate-measure ausdrücken, hört die Kennzahlenlogik auf, in einer Engine eingesperrt zu sein, und wird zu dem, was SQL on FHIR verspricht: standardmäßige FHIR-Artefakte, die Sie einmal paketieren und überall ausführen können. Liefern Sie die gesamte Sammlung als FHIR-Paket aus, installieren Sie es auf einem beliebigen SQL-on-FHIR-Server, und die gleichen Kennzahlen berechnen sich auf die gleiche Weise – nachvollziehbar bis zur einzelnen Ressource. Auf Aidbox laufen sie nativ in PostgreSQL, sodass es überhaupt keine separate Ausführungs-Engine zu betreiben gibt. Und der Weg dorthin ist zugänglicher als es klingt: KI-Assistenten übersetzen das CQL in SQL, und die eigenen Referenz-Fixtures von CMS verifizieren jede Kennzahl gegen erwartete Ergebnisse.
Sind Sie daran interessiert, diesen Kennzahlenberechnungsansatz auf Ihren eigenen Daten auszuprobieren? Kontaktieren Sie uns – wir erläutern Ihnen den Ansatz gerne im Detail.






