---
{
  "title": "Über Patient/$merge hinaus: Ein ressourcenagnostisches, clientgesteuertes Merge für FHIR",
  "description": "Patient/$merge in FHIR R5 ist ein Anfang, aber produktives MDM erfordert mehr. Wir haben ein ressourcenagnostisches $merge mit clientgesteuerten Plänen, atomaren Audit-Trails und einer generischen $referencing-Operation entwickelt.",
  "date": "2026-04-09",
  "author": "Ivan Shukshin",
  "reading-time": "8 min read",
  "tags": ["FHIR Standard", "Aidbox", "System Design", "Integrations", "MDMbox"],
  "tldr": "Wir haben FHIR $merge erweitert, sodass es ein clientseitig bereitgestelltes Transaction-Bundle als plan-Parameter akzeptiert. Der Client entscheidet, was aktualisiert, erstellt oder gelöscht wird – der Server gewährleistet Atomarität und erstellt Task- und Provenance-Audit-Ressourcen innerhalb derselben Transaktion. In Kombination mit einer neuen $referencing-Operation für generisches Reverse-Include ergibt sich ein vollständig prüfbares, ressourcenagnostisches Merge, das für Patient, Organization, Practitioner oder beliebige andere Ressourcentypen funktioniert."
}
---
Wir betreiben Master Data Management in [MDMbox](https://www.health-samurai.io/mdmbox) seit zwei Jahren in Produktivumgebungen – wir deduplizieren Patienten, führen Organisationen zusammen und gleichen Leistungserbringer über Einrichtungen hinweg ab. Dabei haben wir gelernt, dass jede Organisation anders zusammenführt und kein einzelner serverseitiger Algorithmus die Vielfalt realer Merge-Richtlinien abdecken kann.

[FHIR R5](/articles/fhir-r4-vs-fhir-r5-choosing-the-right-version-for-your-implementation) hat `Patient/$merge` eingeführt – eine lang erwartete Operation, mit der Sie doppelte Patientendatensätze zusammenführen können. Sie definiert Eingabeparameter (`source-patient`, `target-patient`, `result-patient`), erwartet, dass der Server die Referenzaktualisierungen übernimmt, und gibt das zusammengeführte Ergebnis zurück. Die Operation befindet sich derzeit auf Reifegrad 0 und wurde seit ihrer Einführung nicht weiterentwickelt.

Das ist ein guter Anfang. Nach der Implementierung von Merge in der Produktion haben wir jedoch festgestellt, dass die Spezifikation nicht weit genug geht.

## Das Problem mit servergesteuertem Merge

Die FHIR-`$merge`-Spezifikation setzt voraus, dass der Server weiß, *wie* zusammenzuführen ist. Der Server deaktiviert die Quelle, kopiert Identifikatoren und aktualisiert Referenzen. Die Merge-Logik variiert jedoch erheblich zwischen Organisationen:

- **Krankenhaus A** löscht den Quellpatienten vollständig und schreibt alle Referenzen neu
- **Krankenhaus B** behält die Quelle inaktiv und erstellt eine Linkage-Ressource für die Provenienz
- **Krankenhaus C** führt Encounters und Observations in das Ziel zusammen, bewahrt aber separate AllergyIntolerance-Datensätze zur manuellen Überprüfung
- **Gesundheitsinformationsverbund D** muss Organizations und Practitioners zusammenführen, nicht nur Patients

Die Spezifikation deckt nichts davon ab – sie beschränkt sich auf Patient, und der Schritt „alle Referenzen aktualisieren" wird nur vage beschrieben.

![Merge flow](merge-flow.svg)

Was wir benötigten, war eine Merge-Operation, bei der:
- Der **Client** entscheidet, was geschieht (welche Ressourcen aktualisiert, erstellt oder gelöscht werden)
- Der **Server** Atomarität gewährleistet, einen Audit-Trail erstellt und Sicherheitsprüfungen durchsetzt
- Sie für **beliebige Ressourcentypen** funktioniert, nicht nur für Patient

## Unser Ansatz: der plan-Parameter

Wir haben ein FHIR-konformes `$merge` entwickelt, das ein Standard-Transaction-Bundle als zusätzlichen `plan`-Parameter akzeptiert:

```json
POST $merge

{
  "resourceType": "Parameters",
  "parameter": [
    {"name": "source", "valueReference": {"reference": "Patient/123"}},
    {"name": "target", "valueReference": {"reference": "Patient/456"}},
    {"name": "preview", "valueBoolean": false},
    {
      "name": "plan",
      "resource": {
        "resourceType": "Bundle",
        "type": "transaction",
        "entry": [
          {
            "resource": {"resourceType": "Patient", "id": "456", "...": "merged state"},
            "request": {"method": "PUT", "url": "Patient/456", "ifMatch": "W/\"3\""}
          },
          {
            "resource": {"resourceType": "Encounter", "id": "789",
                         "subject": {"reference": "Patient/456"}},
            "request": {"method": "PUT", "url": "Encounter/789", "ifMatch": "W/\"1\""}
          },
          {
            "request": {"method": "DELETE", "url": "Patient/123", "ifMatch": "W/\"2\""}
          }
        ]
      }
    }
  ]
}
```

Der Client sendet jeden PUT, POST und DELETE explizit. Der Server ergänzt den Plan um Audit-Ressourcen (Task + Provenance) und führt alles als einzelne FHIR-Transaktion aus. Entweder gelingt alles – einschließlich des Audit-Trails – oder nichts.

Beachten Sie die `ifMatch`-Header bei jedem Eintrag – dies ist optimistisches Locking. Wenn eine Ressource zwischen dem Zeitpunkt, zu dem der Client den Plan erstellt hat, und dem Zeitpunkt der Ausführung geändert wurde, schlägt die Transaktion fehl. Keine stillen Überschreibungen, keine verlorenen Aktualisierungen.

## Eine Obermenge, keine Abspaltung

Sie werden bemerken, dass wir `source`, `target` und `result` anstelle der spezifikationsgemäßen Bezeichnungen `source-patient`, `target-patient` und `result-patient` verwenden. Das ist beabsichtigt – die Operation ist ressourcenagnostisch, daher wäre eine patientenspezifische Benennung irreführend. Doppelte Organizations zusammenführen? Practitioners? Dieselbe Operation, derselbe Audit-Trail.

Lassen Sie den `plan`-Parameter weg und übergeben Sie stattdessen `result` – Sie erhalten das Standardverhalten von FHIR `$merge`. Die identifikatorbasierte Suche nach Quelle und Ziel wird unterstützt. Der `plan` ist das, was die Operation streng leistungsfähiger macht: Wenn er vorhanden ist, übernimmt der Client die vollständige Kontrolle über die Merge-Logik.

## Das fehlende Element: $referencing

Um einen guten Merge-Plan zu erstellen, muss der Client eine Frage beantworten: *„Welche Ressourcen referenzieren Patient/123?"*

FHIR bietet `_revinclude` für Suchergebnisse und `$everything` für Patient, aber keine generische Operation, die besagt: „Gib mir jede Ressource in der Datenbank, die auf diese Referenz verweist." Wir haben eine solche entwickelt.

![Before and after merge](before-after.svg)

Intern handelt es sich um eine PostgreSQL-Abfrage, die JSONPath verwendet, um über Ressourcentabellen zu suchen:

```sql
SELECT id, resource_type, resource
FROM <resource_table>
WHERE jsonb_path_query_array(resource, '$.** ? (@.reference == "Patient/123")')
      != '[]'::jsonb
```

Die obige Abfrage ist zur Verdeutlichung vereinfacht, aber der Kerngedanke bleibt: `$.**` durchsucht rekursiv die gesamte Ressourcenstruktur und findet jedes verschachtelte Objekt, bei dem `reference` dem Ziel entspricht – unabhängig davon, wo es im Ressourcenbaum vorkommt. Keine fest kodierten Pfade, keine ressourcentypspezifische Konfiguration. Eine einzige Abfrage findet Referenzen in `Encounter.subject`, `Observation.performer`, `Claim.provider` oder jedem anderen Referenzfeld.

**Zur Performance:** Diese Abfrage verwendet einen GIN-Index auf der JSONB-Spalte, sodass die rekursive Traversierung gegen den Index erfolgt – nicht als vollständiger Tabellen-Scan. Bei einem typischen Merge schließt die Operation in Millisekunden ab. Bei Ressourcentypen mit Millionen von Zeilen parallelisieren wir die Suche über Tabellen hinweg und streamen die Ergebnisse an den Client zurück.

Wir haben dies als eigenständige `$referencing`-Operation in [MDMbox](https://www.health-samurai.io/mdmbox) bereitgestellt – ein Baustein, den jeder Merge- (oder Impact-Analysis-)Workflow nutzen kann.

## Task + Provenance: mehr als nur Audit

Jeder Merge erstellt einen Task und eine Provenance innerhalb derselben Transaktion. Das geht über bloße Compliance hinaus:

![Audit trail](audit-trail.svg)

**Subscriptions.** Externe Systeme können die Erstellung von Merge-Tasks abonnieren und sofort reagieren – ihre Caches aktualisieren, nachgelagerte Workflows auslösen, Benutzer benachrichtigen. Standard-FHIR-Subscriptions, ohne benutzerdefinierte Infrastruktur.

**Versionierte Snapshots.** Die Provenance erfasst für jede durch den Merge geänderte oder gelöschte Ressource eine versionierte Referenz (z. B. `Patient/456/_history/3`). In Kombination mit der FHIR History API ist der vollständige Zustand vor dem Merge jederzeit wiederherstellbar.

**Lifecycle-Tracking.** Der Task protokolliert den Merge-Status (`requested` → `in-progress` → `completed` oder `failed`), wer ihn initiiert hat, wann er stattfand, und verknüpft ihn mit Quelle und Ziel. Die Abfrage „Zeig mir alle Merges, die diesen Patienten betroffen haben" ist eine einzelne FHIR-Suche auf Task.

## Ausblick: $unmerge

FHIR definiert noch keine Unmerge-Operation. Betrachten Sie folgendes Szenario: Ein Merge wird am Montag ausgeführt, und am Mittwoch stellt jemand fest, dass es sich um zwei tatsächlich verschiedene Patienten handelte. Zu diesem Zeitpunkt wurden 50 Ressourcen geändert – Encounters wurden umgezeigt, Observations neu zugewiesen, der Quellpatient gelöscht.

Da Task den Merge-Lifecycle verfolgt, Provenance jede betroffene Ressource mit ihrer Version vor dem Merge erfasst und die History API alle vorherigen Zustände bewahrt – haben wir alles, was nötig ist, um einen Merge zuverlässig rückgängig zu machen. Die `$unmerge`-Operation liest die Provenance, ruft die Versionen vor dem Merge aus der History ab und erstellt ein umgekehrtes Transaction-Bundle, das jede Ressource in ihren vorherigen Zustand zurückversetzt.

Wir werden `$unmerge` im nächsten Beitrag behandeln – jetzt veröffentlicht: [Designing $unmerge: Reversing the FHIR Patient Merge](/articles/designing-unmerge-fhir-patient). Der zweite Beitrag der Serie, [MPI Vendors Couldn't Agree. We Solved Patient Merge](/articles/mpi-vendors-patient-merge), erläutert, warum ein einzelnes servergesteuertes `$merge` nicht alle Produktionsrichtlinien abdecken kann. Hintergrundinformationen zu den zugrundeliegenden Master Patient Index-Grundlagen finden Sie unter [Master Patient Index (MPI): How It Works + Examples](/articles/master-patient-index-and-record-linkage).

---

*Möchten Sie clientgesteuertes Merge in Ihrem Projekt ausprobieren? [MDMbox](https://www.health-samurai.io/mdmbox) ist heute verfügbar.*