
SQL für Finanz-Datawarehouses
Strukturierte Finanzdaten abfragen, aggregieren und analysieren
Warum SQL die Sprache der Finanzdaten ist
Jeder Trade, jeder Tick, jedes Orderbuch-Update — Finanzmärkte erzeugen gewaltige Mengen strukturierter Daten. Und seit über vier Jahrzehnten ist SQL (Structured Query Language) das dominante Werkzeug, um diese Daten abzufragen, umzuformen und zu analysieren. Es ist nicht glamourös. Es ist nicht trendig. Aber wenn ein Portfoliomanager fragt „Was war mein durchschnittlicher Fill-Preis bei ETH-Trades letzten Dienstag zwischen 14 und 15 Uhr UTC?“, kommt die Antwort aus SQL.
Finanzdaten sind von Natur aus relational. Ein Trade referenziert eine Order, die einen Account referenziert, der einem Nutzer gehört. Positionen beziehen sich auf Instrumente; Instrumente beziehen sich auf Märkte. Das relationale Modell — Tabellen mit Zeilen und Spalten, verbunden durch Foreign Keys — bildet diese Beziehungen natürlich ab. Deshalb bleiben relationale Datenbanken wie PostgreSQL, MySQL und SQL Server das Rückgrat der Finanzinfrastruktur, von Goldman Sachs bis GaiaEx.
SQLs Kraft liegt in seiner deklarativen Natur: Du beschreibst was du willst, nicht wie man es bekommt. Die Datenbank-Engine findet den optimalen Ausführungsplan. Das zählt enorm, wenn deine Trade-History-Tabelle 500 Millionen Zeilen hat und du Ergebnisse in unter einer Sekunde brauchst. Schreibe die richtige Query, baue den richtigen Index, und SQL liefert — egal, ob du es gegen eine lokale PostgreSQL-Instanz oder ein Cloud-Scale-Data-Warehouse laufen lässt.
SELECT, JOIN und WHERE: Trade-Daten abfragen
Beginnen wir mit einem konkreten Schema. Stell dir eine trades-Tabelle auf einer Plattform wie GaiaEx vor:
CREATE TABLE trades (
trade_id BIGINT PRIMARY KEY,
user_id INT NOT NULL,
symbol VARCHAR(20) NOT NULL,
side VARCHAR(4) NOT NULL, -- 'buy' oder 'sell'
price NUMERIC(18,8) NOT NULL,
quantity NUMERIC(18,8) NOT NULL,
fee NUMERIC(18,8) DEFAULT 0,
executed_at TIMESTAMPTZ NOT NULL
);
Die fundamentalste Operation ist das SELECT-Statement mit Filterung. Um alle ETH-USD-Kauf-Trades über $3.000 in den letzten 24 Stunden zu finden:
SELECT trade_id, price, quantity, executed_at
FROM trades
WHERE symbol = 'ETH-USD'
AND side = 'buy'
AND price > 3000
AND executed_at > NOW() - INTERVAL '24 hours'
ORDER BY executed_at DESC;
JOINs verbinden verwandte Tabellen. Angenommen, du hast eine accounts-Tabelle und willst das Handelsvolumen pro Account-Tier sehen:
SELECT a.tier, COUNT(*) AS trade_count,
SUM(t.price * t.quantity) AS total_volume
FROM trades t
JOIN accounts a ON t.user_id = a.user_id
WHERE t.executed_at > NOW() - INTERVAL '30 days'
GROUP BY a.tier
ORDER BY total_volume DESC;
Diese Queries sind das tägliche Brot von Finanzanalysten, Risikoteams und Compliance-Beauftragten. Meistere sie, und du kannst jede Frage beantworten, die deine Daten enthalten.
Window Functions: Die Geheimwaffe des Analysten
Window Functions sind das, was SQL-Anfänger von SQL-Praktikern in Finance unterscheidet. Sie lassen dich Berechnungen über eine Menge von Zeilen durchführen, die zur aktuellen Zeile in Beziehung stehen — ohne das Ergebnis in einen einzigen aggregierten Wert zu komprimieren. Denk an sie als „laufende Berechnungen“ über deine Daten.
Die am häufigsten genutzten Window Functions in der Finanzanalyse:
- ROW_NUMBER() — Weist jeder Zeile innerhalb einer Partition eine sequenzielle Ganzzahl zu. Nützlich für Deduplizierung oder die Auswahl des N-ten Trades pro Symbol.
- LAG() und LEAD() — Greifen auf den Wert der vorherigen oder nächsten Zeile zu. Essenziell, um Trade-zu-Trade-Renditen zu berechnen oder Lücken in Zeitreihendaten zu erkennen.
- SUM() OVER () — Laufende Summen. Berechne kumulatives Volumen, kumulativen PnL oder laufende Positionsgröße.
- AVG() OVER (ROWS BETWEEN) — Gleitende Durchschnitte direkt in SQL. Kein Python nötig.
Hier ist ein praktisches Beispiel — die Berechnung der Trade-zu-Trade-Preisänderung und einer laufenden Position für BTC:
SELECT executed_at, price, quantity, side,
price - LAG(price) OVER (ORDER BY executed_at) AS price_change,
SUM(CASE WHEN side = 'buy' THEN quantity ELSE -quantity END)
OVER (ORDER BY executed_at) AS running_position
FROM trades
WHERE symbol = 'BTC-USD'
ORDER BY executed_at;
Window Functions reduzieren deine Zeilenanzahl nicht — im Gegensatz zu GROUP BY erzeugt jede Eingabezeile eine Ausgabezeile. Das macht sie perfekt, um analytische Spalten zu detaillierten Trade-Logs hinzuzufügen, ohne die Granularität zu verlieren. Denk an sie als „Anreicherung“ deiner Daten statt „Zusammenfassung“.
Für OHLCV-Aggregation — die Grundlage von Candlestick-Charts — kannst du GROUP BY mit Time-Bucketing und Standard-Aggregaten kombinieren:
SELECT date_trunc('hour', executed_at) AS bucket,
(ARRAY_AGG(price ORDER BY executed_at))[1] AS open,
MAX(price) AS high,
MIN(price) AS low,
(ARRAY_AGG(price ORDER BY executed_at DESC))[1] AS close,
SUM(quantity) AS volume
FROM trades
WHERE symbol = 'ETH-USD'
GROUP BY bucket
ORDER BY bucket;CTEs: Komplexe analytische Queries komponieren
Common Table Expressions (CTEs) lassen dich eine komplexe Query in benannte, lesbare Stufen zerlegen — wie Funktionen in der Programmierung. Sie werden mit dem WITH-Keyword eingeführt und können sich sequenziell aufeinander beziehen.
Angenommen, du willst deine 10 profitabelsten Handelstage identifizieren, aber Profitabilität erfordert die Berechnung des Netto-PnL pro Tag inklusive Gebühren. Mit CTEs kannst du das Schritt für Schritt aufbauen:
WITH daily_trades AS (
SELECT DATE(executed_at) AS trade_date,
SUM(CASE WHEN side = 'sell' THEN price * quantity
ELSE -price * quantity END) AS gross_pnl,
SUM(fee) AS total_fees
FROM trades
WHERE user_id = 42
GROUP BY DATE(executed_at)
),
daily_pnl AS (
SELECT trade_date,
gross_pnl - total_fees AS net_pnl,
SUM(gross_pnl - total_fees) OVER (ORDER BY trade_date) AS cumulative_pnl
FROM daily_trades
)
SELECT trade_date, net_pnl, cumulative_pnl
FROM daily_pnl
ORDER BY net_pnl DESC
LIMIT 10;
Jede CTE liest sich wie ein Absatz. daily_trades aggregiert rohe Trades zu Tageszusammenfassungen. daily_pnl berechnet den Netto-PnL und eine laufende Summe. Das finale SELECT wählt die Top-10-Tage aus. Vergleiche das mit einer einzigen monolithischen Subquery — die CTE-Version ist wartbar, testbar und selbstdokumentierend.
In produktiven Finanzsystemen werden CTEs für alles genutzt, von regulatorischem Reporting (Aggregation von Margin-Anforderungen über Accounts) bis zu Echtzeit-Dashboard-Queries (Berechnung des rollierenden 24-Stunden-Volumens auf GaiaEx). Sie komponieren auf dieselbe Weise, wie gut faktorisierter Code komponiert — jede Ebene baut auf der vorherigen auf.
Indexierung und Partitionierung für Zeitreihen-Performance
Eine Query ist nur so schnell wie der Index, der sie unterstützt. Ohne den richtigen Index erzwingt selbst eine einfache WHERE-Klausel einen sequenziellen Scan — das Lesen jeder Zeile in der Tabelle. Bei 500 Millionen Zeilen sind das Minuten statt Millisekunden.
Für Finanz-Zeitreihendaten ist das kritischste Index-Muster ein zusammengesetzter B-Baum-Index auf (symbol, executed_at):
CREATE INDEX idx_trades_symbol_time
ON trades (symbol, executed_at DESC);
Dieser einzelne Index beschleunigt die Mehrheit der analytischen Queries: „alle ETH-Trades in der letzten Stunde“, „BTC-Trades zwischen zwei Zeitstempeln“ oder „letzter Trade pro Symbol“. Die Spaltenreihenfolge zählt — symbol zuerst ermöglicht Gleichheitsfilterung, dann ermöglicht executed_at effiziente Range-Scans innerhalb dieser Partition des Index.
Für Tabellen, die über Hunderte von Millionen Zeilen wachsen, ist Tabellenpartitionierung essenziell. PostgreSQL unterstützt deklarative Partitionierung nach Bereich — perfekt für Zeitreihen:
CREATE TABLE trades (
trade_id BIGINT, symbol VARCHAR(20),
price NUMERIC(18,8), executed_at TIMESTAMPTZ
) PARTITION BY RANGE (executed_at);
CREATE TABLE trades_2026_q1 PARTITION OF trades
FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');
CREATE TABLE trades_2026_q2 PARTITION OF trades
FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');
Mit Partitionierung scannt eine Query für Daten aus März 2026 nur die Q1-Partition — die Engine überspringt Q2, Q3 und Q4 komplett. Diese Technik, genannt Partition Pruning, liefert Beschleunigungen um Größenordnungen auf großen historischen Datensätzen.
PostgreSQL versus ClickHouse versus BigQuery: die richtige Engine wählen
Nicht alle Datenbanken handhaben Finanzanalytik gleich gut. Die Wahl hängt von deinen Query-Mustern, Datenvolumen und Latenzanforderungen ab.
PostgreSQL ist das Arbeitstier. Es exzelliert bei transaktionalen Workloads (Trades in Echtzeit aufzeichnen), unterstützt ACID-Garantien, handhabt komplexe JOINs elegant und skaliert auf Hunderte Millionen Zeilen mit richtiger Indexierung und Partitionierung. Es ist die richtige Wahl für operative Datenbanken — das System of Record. GaiaEx zum Beispiel verlässt sich auf PostgreSQL-kompatible Infrastruktur für seine Kern-Trading-Daten mit den Zuverlässigkeitsgarantien, die Finanzsysteme fordern.
ClickHouse ist eine spaltenorientierte Datenbank, die für analytische Queries auf Milliarden von Zeilen ausgelegt ist. Wo PostgreSQL Daten zeilenweise speichert (großartig für das Einfügen einzelner Trades), speichert ClickHouse Daten spaltenweise (großartig für das Aggregieren einer einzelnen Spalte über Millionen von Zeilen). Eine Query wie „durchschnittliches Stundenvolumen für BTC über 3 Jahre“, die in PostgreSQL 30 Sekunden braucht, könnte in ClickHouse in 200 Millisekunden fertig sein. Der Trade-off: ClickHouse unterstützt UPDATE oder DELETE nicht effizient — es ist per Design append-only.
BigQuery (Google Cloud) ist ein serverloses spaltenorientiertes Warehouse. Keine zu verwaltende Infrastruktur, Pay-per-Query-Preise und Petabyte-Skalenkapazität. Es ist ideal für Ad-hoc-Analytik, Research und Exploration — aber die Query-Latenz wird in Sekunden gemessen, nicht Millisekunden, was es für Echtzeit-Anwendungen ungeeignet macht.
- Echtzeit-Trade-Aufzeichnung und Order-Management → PostgreSQL
- Historische Analytik und Backtesting über Milliarden Zeilen → ClickHouse
- Ad-hoc-Exploration und teamübergreifendes Daten-Sharing → BigQuery
Viele professionelle Trading-Firmen nutzen alle drei in einer gestuften Architektur: PostgreSQL als heißen operativen Speicher, ClickHouse als warme analytische Ebene und BigQuery als kaltes Archiv. Die Fähigkeit, gutes SQL zu schreiben, übertragt sich über alle drei — die Syntax ist zu 90% identisch, und das analytische Denken ist zu 100% dasselbe.