0Pricing
SQL Academy · Lektion

Zustand aus Events rekonstruieren

Führen Sie Events zum aktuellen Zustand zusammen

Zustand aus Events rekonstruieren ist eine kostenlose SQL Academy-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des SQL Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der SQL Academy-Kurs umfasst insgesamt 4 Lektionen.

Was bedeutet den Zustand neu aufzubauen?

Beim Event Sourcing werden Daten als unveränderliches Ereignisprotokoll statt als veränderliche Zeilen gespeichert. Um den aktuellen Zustand zu ermitteln, müssen Sie diese Ereignisse wiedergeben und zu einem einzigen Ergebnis zusammenführen.

Dies wird als Wiederaufbau des Zustands aus Ereignissen bezeichnet. Stellen Sie sich ein Bankkonto vor: Statt den Kontostand zu speichern, speichern Sie jede Einzahlung und jede Auszahlung. Der Kontostand ist immer die Summe all dieser Ereignisse.

Eine einfache Ereignistabelle

Beginnen wir mit dem Erstellen eines minimalen Ereignisprotokolls für ein Bankkontosystem. Jede Zeile stellt etwas dar, das geschehen ist — eine Einzahlung oder eine Auszahlung — zusammen mit dem Betrag und dem Zeitstempel.

Diese Tabelle wird nie aktualisiert oder gelöscht. Neue Fakten werden immer als neue Zeilen angehängt.

CREATE TABLE account_events (
  event_id   SERIAL PRIMARY KEY,
  account_id INT NOT NULL,
  event_type VARCHAR(20) NOT NULL,  -- 'deposit' or 'withdrawal'
  amount     NUMERIC(12, 2) NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

INSERT INTO account_events (account_id, event_type, amount, created_at) VALUES
  (1, 'deposit',    1000.00, '2024-01-01 09:00:00+00'),
  (1, 'deposit',     500.00, '2024-01-03 14:00:00+00'),
  (1, 'withdrawal',  200.00, '2024-01-05 10:00:00+00'),
  (1, 'deposit',     300.00, '2024-01-07 11:00:00+00'),
  (1, 'withdrawal',  150.00, '2024-01-09 16:00:00+00');

Ereignisse zu einem Saldo zusammenführen

Um den aktuellen Saldo wiederherzustellen, aggregieren wir alle Ereignisse. Einzahlungen erhöhen den Saldo, und Abhebungen verringern ihn. Ein CASE-Ausdruck ermöglicht es uns, jeden Ereignistyp vor der Summierung mit dem richtigen Vorzeichen zu behandeln.

Diese einzelne Abfrage liefert den aktuellen Zustand, der vollständig aus dem historischen Ereignisprotokoll abgeleitet wird.

SELECT
  account_id,
  SUM(
    CASE event_type
      WHEN 'deposit'    THEN  amount
      WHEN 'withdrawal' THEN -amount
      ELSE 0
    END
  ) AS current_balance
FROM account_events
WHERE account_id = 1
GROUP BY account_id;

Zustand zu einem bestimmten Zeitpunkt

Einer der leistungsfähigsten Aspekte von Event Sourcing ist die Möglichkeit, den Zustand zu jedem beliebigen Zeitpunkt zu rekonstruieren. Fügen Sie vor der Aggregation einfach einen Filter WHERE created_at <= :target_time hinzu.

Damit erhalten Sie eine Zeitreiseabfrage, ohne das Schema ändern zu müssen – der Verlauf ist bereits im Ereignisprotokoll enthalten.

-- What was the balance at the end of January 5th?
SELECT
  account_id,
  SUM(
    CASE event_type
      WHEN 'deposit'    THEN  amount
      WHEN 'withdrawal' THEN -amount
      ELSE 0
    END
  ) AS balance_at_snapshot
FROM account_events
WHERE account_id = 1
  AND created_at <= '2024-01-05 23:59:59+00'
GROUP BY account_id;

Laufender Saldo mit Fensterfunktionen

Statt nur eine Gesamtsumme zu berechnen, können wir einen laufenden Saldo ermitteln – den Saldo nach jedem Ereignis. Die Fensterfunktion SUM(...) OVER (ORDER BY ...) berechnet die kumulative Summe, während sich die Ereignisse in chronologischer Reihenfolge ansammeln.

Das ist äußerst nützlich für Audit-Trails und zum Debuggen von Zustandsübergängen.

SELECT
  event_id,
  created_at,
  event_type,
  amount,
  SUM(
    CASE event_type
      WHEN 'deposit'    THEN  amount
      WHEN 'withdrawal' THEN -amount
      ELSE 0
    END
  ) OVER (PARTITION BY account_id ORDER BY created_at, event_id)
    AS running_balance
FROM account_events
WHERE account_id = 1
ORDER BY created_at, event_id;

Zustand in einer Snapshot-Tabelle materialisieren

Alle Ereignisse bei jeder Abfrage erneut abzuspielen, kann teuer werden, wenn das Protokoll wächst. Eine übliche Optimierung besteht darin, den aktuellen Zustand in einer Snapshot-Tabelle zu materialisieren und diese regelmäßig oder bei Bedarf neu aufzubauen.

Der Snapshot speichert das zusammengeführte Ergebnis. Abfragen lesen daraus, statt jedes Mal das vollständige Protokoll erneut abzuspielen.

CREATE TABLE account_snapshots (
  account_id      INT PRIMARY KEY,
  current_balance NUMERIC(12, 2) NOT NULL,
  as_of_event_id  INT NOT NULL,
  updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Populate / refresh the snapshot from the event log
INSERT INTO account_snapshots (account_id, current_balance, as_of_event_id, updated_at)
SELECT
  account_id,
  SUM(CASE event_type WHEN 'deposit' THEN amount WHEN 'withdrawal' THEN -amount ELSE 0 END),
  MAX(event_id),
  NOW()
FROM account_events
GROUP BY account_id
ON CONFLICT (account_id) DO UPDATE
  SET current_balance = EXCLUDED.current_balance,
      as_of_event_id  = EXCLUDED.as_of_event_id,
      updated_at      = EXCLUDED.updated_at;

Inkrementelle Snapshot-Aktualisierungen

Wenn neue Ereignisse eintreffen, müssen Sie nicht den gesamten Verlauf erneut abspielen. Wenn Sie die zuletzt verarbeitete event_id im Snapshot gespeichert haben, können Sie nur das Delta anwenden – die Ereignisse, die nach der Erstellung des Snapshots eingetroffen sind.

Dieses inkrementelle Verfahren hält die Aktualisierung von Snapshots auch bei großen Protokollen schnell.

-- Apply only new events since the last snapshot
UPDATE account_snapshots AS snap
SET
  current_balance = snap.current_balance + delta.net,
  as_of_event_id  = delta.max_event_id,
  updated_at      = NOW()
FROM (
  SELECT
    ae.account_id,
    SUM(CASE ae.event_type WHEN 'deposit' THEN ae.amount WHEN 'withdrawal' THEN -ae.amount ELSE 0 END) AS net,
    MAX(ae.event_id) AS max_event_id
  FROM account_events ae
  JOIN account_snapshots s ON s.account_id = ae.account_id
  WHERE ae.event_id > s.as_of_event_id
  GROUP BY ae.account_id
) AS delta
WHERE snap.account_id = delta.account_id;

Temporale Tabellen und Systemversionierung

SQL:2011 führte systemversionierte temporale Tabellen ein, die von der Datenbank selbst verwaltet werden. Jede Zeile erhält automatisch die Spalten valid_from und valid_to, die von der Engine verwaltet werden.

PostgreSQL unterstützt dies nicht nativ, aber Sie können es nachbilden. Andere Datenbanken wie MariaDB und SQL Server unterstützen WITH SYSTEM VERSIONING direkt.

-- Emulating a temporal table in PostgreSQL
CREATE TABLE account_state_history (
  account_id      INT NOT NULL,
  current_balance NUMERIC(12, 2) NOT NULL,
  valid_from      TIMESTAMPTZ NOT NULL,
  valid_to        TIMESTAMPTZ NOT NULL DEFAULT 'infinity'
);

-- Insert initial state
INSERT INTO account_state_history (account_id, current_balance, valid_from)
VALUES (1, 1000.00, '2024-01-01 09:00:00+00');

-- On update: close old row, insert new row
UPDATE account_state_history
  SET valid_to = '2024-01-03 14:00:00+00'
WHERE account_id = 1 AND valid_to = 'infinity';

INSERT INTO account_state_history (account_id, current_balance, valid_from)
VALUES (1, 1500.00, '2024-01-03 14:00:00+00');

Temporale Historie abfragen

Wenn die nachgebildete temporale Tabelle eingerichtet ist, können Sie durch Filtern nach dem Gültigkeitsbereich ermitteln, wie hoch der Saldo zu einem beliebigen vergangenen Zeitpunkt war. Die Zeile, deren Bereich den Zielzeitstempel enthält, stellt den Zustand zu diesem Zeitpunkt dar.

Dieses Verfahren entkoppelt die Abfragelogik von der Ereigniswiedergabe – die Zustandshistorientabelle ist bereits zusammengeführt.

-- What was the account balance on January 4th?
SELECT
  account_id,
  current_balance,
  valid_from,
  valid_to
FROM account_state_history
WHERE account_id = 1
  AND valid_from <= '2024-01-04 00:00:00+00'
  AND valid_to   >  '2024-01-04 00:00:00+00';

Event Sourcing mit mehreren Entitäten

In realen Systemen werden Ereignisse für viele Entitäten gleichzeitig erfasst. Ein gemeinsames Ereignisprotokoll mit den Spalten entity_id und entity_type ermöglicht es Ihnen, den Zustand jedes Objekts aus einer einzigen Tabelle wiederherzustellen.

Hier erfassen wir Lagerbewegungen für mehrere Produkte. Den aktuellen Bestand für jedes Produkt wiederherzustellen, ist erneut einfach eine gruppierte Aggregation.

CREATE TABLE inventory_events (
  event_id    SERIAL PRIMARY KEY,
  product_id  INT NOT NULL,
  event_type  VARCHAR(20) NOT NULL,  -- 'received', 'shipped', 'adjusted'
  quantity    INT NOT NULL,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

INSERT INTO inventory_events (product_id, event_type, quantity, created_at) VALUES
  (101, 'received',  200, '2024-03-01 08:00:00+00'),
  (101, 'shipped',    50, '2024-03-02 12:00:00+00'),
  (101, 'shipped',    30, '2024-03-04 15:00:00+00'),
  (102, 'received',  150, '2024-03-01 08:00:00+00'),
  (102, 'adjusted',  -10, '2024-03-03 09:00:00+00');

-- Rebuild current stock for all products
SELECT
  product_id,
  SUM(CASE event_type WHEN 'received' THEN quantity WHEN 'shipped' THEN -quantity ELSE quantity END) AS stock_on_hand
FROM inventory_events
GROUP BY product_id
ORDER BY product_id;

CTEs für mehr Übersichtlichkeit verwenden

Abfragen zur Zustandswiederherstellung können komplex werden. Wenn Sie den Zusammenführungsschritt in eine CTE einschließen, wird die Abfrage übersichtlicher, und Sie können den wiederhergestellten Zustand sauber mit anderen Tabellen verknüpfen.

Hier stellen wir die Kontostände wieder her und verknüpfen sie anschließend mit einer Kontenreferenztabelle, um die Namen der Kontoinhaber in die Ausgabe aufzunehmen.

CREATE TABLE accounts (
  account_id INT PRIMARY KEY,
  owner_name VARCHAR(100) NOT NULL
);

INSERT INTO accounts (account_id, owner_name) VALUES
  (1, 'Alice'),
  (2, 'Bob');

INSERT INTO account_events (account_id, event_type, amount, created_at) VALUES
  (2, 'deposit',   2000.00, '2024-01-02 10:00:00+00'),
  (2, 'withdrawal', 400.00, '2024-01-06 11:00:00+00');

WITH rebuilt_balances AS (
  SELECT
    account_id,
    SUM(CASE event_type WHEN 'deposit' THEN amount WHEN 'withdrawal' THEN -amount ELSE 0 END) AS balance
  FROM account_events
  GROUP BY account_id
)
SELECT
  a.account_id,
  a.owner_name,
  rb.balance
FROM accounts a
JOIN rebuilt_balances rb USING (account_id)
ORDER BY a.account_id;

Wissenscheck

Testen Sie Ihr Verständnis der Zustandswiederherstellung aus Ereignissen in SQL.

Zusammenfassung der Lektion

In dieser Lektion haben Sie gelernt, wie Sie den aktuellen und historischen Zustand mithilfe von SQL aus einem unveränderlichen Ereignisprotokoll wiederherstellen.

Wichtige Erkenntnisse:

  • Der Zustand wird durch das Zusammenführen (Aggregieren) von Ereignissen mit einem vorzeichenbehafteten CASE-Ausdruck innerhalb von SUM abgeleitet.
  • Ein Zeitstempelfilter ermöglicht kostenlos Abfragen zu einem bestimmten Zeitpunkt.
  • Fensterfunktionen erzeugen nach jedem Ereignis einen laufenden Zustand.
  • Snapshot-Tabellen materialisieren das zusammengeführte Ergebnis für eine bessere Leistung; inkrementelle Aktualisierungen wenden nur neue Ereignisse an.
  • Nachgebildete temporale Tabellen speichern vorab zusammengeführte Zustandszeilen mit Gültigkeitsbereichen für schnelle historische Abfragen.
  • CTEs halten Abfragen zur Zustandswiederherstellung übersichtlich, wenn Sie den abgeleiteten Zustand mit anderen Tabellen verknüpfen müssen.

Diese Verfahren bilden die Grundlage für ereignisbasierte und auditfreundliche Datenbankdesigns.

Häufig gestellte Fragen

Ist die Lektion „Zustand aus Events rekonstruieren“ kostenlos?

Ja — der vollständige Text von „Zustand aus Events rekonstruieren“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des SQL Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der SQL Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Zustand aus Events rekonstruieren“?

Führen Sie Events zum aktuellen Zustand zusammen Du übst SQL Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um SQL Academy zu starten?

Keine Vorkenntnisse erforderlich. SQL Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.

Wie lange dauert die Lektion „Zustand aus Events rekonstruieren“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser SQL Academy-Lektion Code schreiben und ausführen?

Ja. Jede SQL Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Warum Sie den Verlauf speichern sollten
  2. Eventtabellen mit ausschließlichem Anhängen
  3. Temporale und versionierte Zeilen
  4. Zustand aus Events rekonstruieren
← Zurück zu SQL Academy