---
{
  "title": "CMS-Qualitätskennzahlen als SQL on FHIR berechnen – ohne CQL-Engine",
  "description": "CMS/HEDIS eCQMs als SQL on FHIR ausführen – ohne CQL-Engine. ViewDefinitions und SQLQuery Libraries werden in einem einzigen FHIR-Paket gebündelt, das sich auf jeden SQL-on-FHIR-Server übertragen lässt und die Kennzahlenberechnung portabel und standardkonform macht.",
  "date": "2026-07-20",
  "author": "Aleksandr Kislitsyn",
  "reading-time": "11 min read",
  "tags": [
    "SQL on FHIR",
    "Analytics",
    "Compliance"
  ],
  "seo-tags": [
    "SQL on FHIR",
    "Quality Measures",
    "eCQM"
  ],
  "tldr": "CQL-basierte CMS-Qualitätskennzahlen lassen sich in Aidbox/PostgreSQL als einfaches SQL via SQL on FHIR ausführen – ohne CQL-Engine zur Laufzeit. ViewDefinitions flachen FHIR zu Tabellen ab, ValueSet-Zugehörigkeit wird zu einem JOIN, und jede Kennzahl ist eine einzige SQL-Abfrage, die als SQLQuery Library verpackt und über die standardkonforme Measure/$evaluate-measure-API bereitgestellt wird. Ein vollständiges Arbeitsbeispiel (ein Dutzend CMS-Kennzahlen) ist auf GitHub verfügbar.",
  "utm-campaign": "analytics",
  "utm-content": "cms-measures-sql"
}
---

> For the complete documentation index, see [llms.txt](https://www.health-samurai.io/llms.txt).
> Use it to discover all available pages before guessing URLs.

---

## 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](https://cql.hl7.org/) (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](https://www.health-samurai.io/docs/aidbox/modules/sql-on-fhir) löst beides.

```mermaid
flowchart TD
    A["Aidbox — FHIR JSONB"] -->|ViewDefinition +<br/>$materialize| B["sof.* flat tables<br/>patient, encounter, condition, observation…"]
    C["ValueSet expansions"] -->|flatten| D["concepts table<br/>(valueset_url, system, code)"]
    B --> E["Measure SQL (CTEs)<br/>IP → exclusions → numerator"]
    D --> E
    E --> F["MeasureReport"]
```

## Ebene 1: FHIR mit ViewDefinitions in Tabellen abflachen

Eine [ViewDefinition](/blog/what-is-a-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](https://www.health-samurai.io/docs/aidbox/terminology-module/fhir-terminology/valueset)-Expansionen **einmalig** in eine `concepts`-Tabelle ab – eine Zeile pro `(valueset_url, system, code)` – und Zugehörigkeit wird zu einem gewöhnlichen Join:

```sql
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:

```sql
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.

{% hint style="info" %}
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](https://github.com/cqframework/dqm-content-qicore-2025) veröffentlicht, sodass jede übersetzte Kennzahl gegen Grundwahrheitswerte geprüft werden kann – nicht auf Vertrauen hin akzeptiert wird.
{% endhint %}

## 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 auf `Library`) gespeichert und mit der Operation `$sqlquery-run` aufgerufen. Die Berechnungslogik lebt *im FHIR-Server*, als erstklassige Ressourcen – nicht im Anwendungscode.
- Der Dienst beantwortet die standardmäßige FHIR R4-Operation [`Measure/$evaluate-measure`](https://hl7.org/fhir/R4/operation-measure-evaluate-measure.html) und gibt einen ordentlichen `MeasureReport` zurü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.

```mermaid
flowchart LR
    Client -->|"POST /Measure/<br/>$evaluate-measure"| Aidbox
    Aidbox -->|routes to| App["evaluate-measure app"]
    App -->|"$sqlquery-run"| Lib["SQLQuery Library<br/>(the measure SQL)"]
    Lib -->|reads| SOF["sof.* views + concepts"]
    App -->|builds| MR["MeasureReport<br/>→ back to the client"]
```

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`](https://www.health-samurai.io/docs/aidbox/reference/package-registry-api#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](https://build.fhir.org/ig/HL7/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.

```mermaid
flowchart LR
    Pkg["FHIR package<br/>terminology + ViewDefinitions + SQLQuery Libraries"]
    Pkg -->|"package install"| S1["FHIR server A<br/>(SQL on FHIR)"]
    Pkg -->|"package install"| S2["FHIR server B<br/>(SQL on FHIR)"]
    Pkg -->|"package install"| S3["…any SQL-on-FHIR server"]
```

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.

```json
"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.

![Patient 360 view in the demo app: the CMS130 decision chain with a per-CTE verdict for every population step, and the qualifying encounter shown as evidence](image-1.png "Patient-360-Ansicht in der Demo-App: eine offene Versorgungslücke beim kolorektalen Screening, die vollständige CMS130-Entscheidungskette (ein Ergebnis pro CTE) und die Evidenzressource hinter der Initial-Population-Zugehörigkeit des Patienten.")

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](https://github.com/Aidbox/examples/tree/main/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.

![Demo app Overview: twelve CMS measure cards with scores, and a sidebar summarizing patients with gaps and open gaps](image-2.png "Die Übersichtsseite der Demo-App: ein Dutzend CMS-Kennzahlen, per SQL berechnet, mit Scores und offenen Versorgungslückenzählungen über 530 Beispielpatienten.")

## 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](https://www.health-samurai.io/contacts?utm_source=article&utm_medium=blog&utm_campaign=cms-measures-sql) – wir erläutern Ihnen den Ansatz gerne im Detail.