Ein normaler Join ordnet Zeilen zu, wenn die Schlüssel gleich sind. Viele echte Fragen brauchen etwas anderes: die Zeile mit dem neuesten Zeitstempel zum oder vor einem anderen Zeitstempel. Den Aktienkurs im Moment eines Kaufs. Den Wechselkurs am Tag einer Rechnung. Den Tarif des Kunden, als er ein Support-Ticket eröffnete. Das ist ein ASOF JOIN ("as of", also "zum Stand von"). Snowflake, DuckDB, ClickHouse und Polars haben ihn, und Apache Spark hat ihn in Version 4.3 ergänzt. Dieser Leitfaden erklärt die Idee einmal und zeigt dann die Syntax in jedem Tool.
Die Idee an einem Beispiel
Wir nutzen überall dieselben zwei kleinen Tabellen. Jeder Handel soll die letzte Notierung desselben Symbols zum oder vor dem Handelszeitpunkt bekommen.
| trade_id | sym | Handelszeit | Letzte Notierung zum oder davor | Preis |
|---|---|---|---|---|
| 1 | A | 10:00:05 | 10:00:00 | 10.0 |
| 2 | A | 10:00:20 | 10:00:10 | 10.5 |
| 3 | B | 10:00:01 | noch keine (erste B-Notierung um 10:00:15) | NULL |
| 4 | B | 10:00:30 | 10:00:15 | 20.0 |
Beachten Sie Handel 2: Es gibt auch eine Notierung um 10:00:25, aber sie kam nach dem Handel und darf nicht verwendet werden. Und Handel 3 hat überhaupt keine frühere Notierung. Was mit ihm passiert, hängt vom Join-Typ ab, der häufigsten Fehlerquelle.
Die Syntax in jeder Engine
Jedes Beispiel unten liefert die Preise 10.0, 10.5, NULL und 20.0. Die Versionen für DuckDB 2.0, Spark 4.3, Polars 2.1 und pandas 3.1 haben wir selbst ausgeführt.
SELECT t.trade_id, t.sym, t.ts, q.price
FROM trades t
ASOF JOIN quotes q
MATCH_CONDITION (t.ts >= q.ts)
ON t.sym = q.sym;MATCH_CONDITION enthält die eine Ungleichung (>=, >, <= oder <); ON enthält die gleichen Schlüssel. Zeilen ohne Treffer kommen mit NULL zurück, wie bei einem Left Join.
SELECT t.trade_id, t.sym, t.ts, q.price
FROM trades t
ASOF LEFT JOIN quotes q
ON t.sym = q.sym AND t.ts >= q.ts;DuckDB schreibt die Ungleichung in ON. Ein einfacher ASOF JOIN ist ein Inner Join und verwirft Handel 3; das haben wir geprüft.
SET spark.sql.join.asofJoin.enabled = true;
SELECT t.trade_id, q.price
FROM trades AS t
LEFT ASOF JOIN quotes AS q
MATCH_CONDITION (t.ts >= q.ts)
ON t.sym = q.sym;Eine Vorschau, standardmäßig aus. Ohne LEFT verwirft auch Spark Zeilen ohne Treffer.
import polars as pl
# DataFrame API: both frames sorted by the time column
result = trades.sort("ts").join_asof(
quotes.sort("ts"), on="ts", by="sym", strategy="backward"
)
# SQL, Snowflake style (Polars 2.1+)
pl.SQLContext(trades=trades, quotes=quotes).execute("""
SELECT trades.trade_id, price
FROM trades
ASOF JOIN quotes MATCH_CONDITION (trades.ts >= quotes.ts)
ON trades.sym = quotes.sym
""").collect()In unserem Test behielten beide Handel 3 mit einem leeren Preis.
import pandas as pd
result = pd.merge_asof(
trades.sort_values("ts"),
quotes.sort_values("ts"),
on="ts", by="sym",
direction="backward", # latest at or before
tolerance=pd.Timedelta("10s"), # optional: ignore stale quotes
)Beide Eingaben müssen nach der on-Spalte sortiert sein. Mit der Toleranz von 10 Sekunden bekommt Handel 4 NaN, weil seine Notierung 15 Sekunden alt ist.
SELECT t.trade_id, q.price
FROM trades AS t
ASOF LEFT JOIN quotes AS q
ON t.sym = q.sym AND t.ts >= q.ts;Dieselbe Form wie bei DuckDB: eine Ungleichung in ON und LEFT, um Zeilen ohne Treffer zu behalten.
Kein ASOF JOIN? Nutzen Sie diese Alternative
BigQuery und PostgreSQL haben kein ASOF-Schlüsselwort, aber zwei Muster liefern dieselbe Antwort. In unseren Tests lieferten beide identische Ergebnisse wie die ASOF-Versionen.
QUALIFY
BigQuery und die meisten- Idee
- alle früheren Notierungen joinen, dann die letzte pro Handel behalten
- Kurz und gut lesbar
- Baut zuerst alle früheren Treffer auf und kann bei großen Tabellen langsam sein
Gut für Tausende bis Millionen Zeilen.
LATERAL
Am besten in PostgreSQL- Idee
- für jeden Handel eine Zeile mit ORDER BY und LIMIT 1 nachschlagen
- Nutzt in PostgreSQL einen Index auf (sym, ts) und skaliert gut
- In BigQuery nicht unterstützt
Die beste Option in PostgreSQL.
-- QUALIFY version (WHERE TRUE is harmless and keeps BigQuery happy,
-- which has long asked for WHERE, GROUP BY or HAVING next to QUALIFY)
SELECT t.trade_id, q.price
FROM trades t
LEFT JOIN quotes q
ON q.sym = t.sym AND q.ts <= t.ts
WHERE TRUE
QUALIFY ROW_NUMBER() OVER (PARTITION BY t.trade_id ORDER BY q.ts DESC) = 1;
-- LATERAL version (PostgreSQL)
SELECT t.trade_id, q.price
FROM trades t
LEFT JOIN LATERAL (
SELECT price FROM quotes q
WHERE q.sym = t.sym AND q.ts <= t.ts
ORDER BY q.ts DESC
LIMIT 1
) q ON TRUE;Vier Fehler, die Sie vermeiden sollten
- 1
Versehentlich Zeilen verlieren
Prüfen Sie, ob der einfache ASOF JOIN Ihrer Engine ein Inner Join ist. In DuckDB, ClickHouse und Spark schreiben Sie LEFT, wenn jede linke Zeile erhalten bleiben muss.
- 2
Die falsche Richtung
">=" bedeutet den letzten Wert zum oder davor; "<=" den nächsten Wert zum oder danach. pandas nennt das backward und forward; Polars spricht von Strategien.
- 3
Zu alte Werte zuordnen
Eine Notierung von gestern ist immer noch "die letzte". Fügen Sie in pandas oder Polars eine Toleranz hinzu oder in SQL einen Filter wie t.ts - q.ts <= INTERVAL 1 HOUR.
- 4
Die gleichen Schlüssel vergessen
Ohne ON t.sym = q.sym kann ein Handel in A eine Notierung von B bekommen.
- Zeitlich korrekt
- Nur Daten verwenden, die zum betrachteten Zeitpunkt existierten.
- Rückwärtszuordnung
- Die letzte rechte Zeile zum oder vor der linken Zeile; der Standard in den meisten Tools.
- Vorwärtszuordnung
- Die erste rechte Zeile zum oder nach der linken Zeile, etwa die nächste Lieferung nach einer Bestellung.
- Toleranz
- Der größte erlaubte Abstand zwischen den beiden Zeiten; ältere Treffer liefern NULL.
Testen Sie Ihr Wissen
1/3Was liefert ein ASOF JOIN für jede linke Zeile?
Er wählt pro linker Zeile genau einen, den nächsten Treffer.
2/3Was passiert in DuckDB mit Trades ohne frühere Notierung, wenn Sie ASOF JOIN ohne LEFT schreiben?
In DuckDB ist der einfache ASOF JOIN ein Inner Join; nutzen Sie ASOF LEFT JOIN, um sie zu behalten.
3/3BigQuery hat keinen ASOF JOIN. Was ist eine funktionierende Alternative?
Das QUALIFY-Muster behält für jede Zeile den letzten früheren Treffer.
Sie haben 0 von 3 richtig.
Mehr SQL: SQL-Joins erklärt, Fensterfunktionen erklärt und der SQL-Spickzettel. Zu den Tools: Neu in Polars 2.1 und DuckDB 2.0.
Quellen: Snowflake-Dokumentation zu ASOF JOIN; Dokumentation von DuckDB, ClickHouse, pandas (merge_asof) und Polars (join_asof); Release Notes zu Apache Spark 4.3.0 (30. Oktober 2026). Beispiele getestet mit DuckDB 2.0.0, PySpark 4.3.0, Polars 2.1.0 und pandas 3.1.0; die Syntax für Snowflake, ClickHouse, BigQuery und PostgreSQL folgt deren Dokumentation.
