Impara  /  Guide

SQL ASOF JOIN spiegato: abbinare ogni riga al valore più recente, in ogni motore

Qual era il prezzo quando è avvenuta la transazione? Quale cambio valeva alla data della fattura? Cosa sapeva il modello al momento della previsione? Sono domande "ASOF", e nel 2026 quasi ogni motore ha un join apposta. Ecco come funziona, con esempi che abbiamo eseguito.

Guida9 min di letturaAggiornato il 9 novembre 2026
SQLPythonAnalisi dei dati
Tutte le guide e i cheat sheet

Un join normale abbina le righe quando le chiavi sono uguali. Molte domande reali chiedono altro: la riga con il timestamp più recente uguale o precedente a un altro timestamp. Il prezzo di un'azione al momento di un acquisto. Il cambio valido il giorno di una fattura. Il piano del cliente quando ha aperto un ticket di assistenza. È un ASOF JOIN ("as of", cioè "alla data di"). Snowflake, DuckDB, ClickHouse e Polars lo hanno, e Apache Spark lo ha aggiunto nella versione 4.3. Questa guida spiega l'idea una volta, poi mostra la sintassi in ogni strumento.

L'idea, con un esempio

Useremo sempre le stesse due piccole tabelle. Ogni transazione deve ricevere l'ultima quotazione dello stesso titolo uguale o precedente all'ora della transazione.

trade_idsymora transazioneUltima quotazione uguale o precedenteprezzo
1A10:00:0510:00:0010.0
2A10:00:2010:00:1010.5
3B10:00:01nessuna ancora (la prima quotazione di B è alle 10:00:15)NULL
4B10:00:3010:00:1520.0

Guarda la transazione 2: c'è anche una quotazione alle 10:00:25, ma è arrivata dopo la transazione, quindi non va usata. E la transazione 3 non ha nessuna quotazione precedente. Cosa le succede dipende dal tipo di join, che è la fonte di errori più comune.

Come ragiona un ASOF JOIN
Chiavi ugualistesso titolo, cliente o conto
Disuguaglianzaora a destra uguale o precedente a quella a sinistra
Una solasolo la corrispondenza più vicina
Tenere o scartareLEFT tiene le righe senza corrispondenza

La sintassi in ogni motore

Ogni esempio qui sotto restituisce i prezzi 10.0, 10.5, NULL e 20.0. Le versioni per DuckDB 2.0, Spark 4.3, Polars 2.1 e pandas 3.1 le abbiamo eseguite noi.

SQL
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 contiene l'unica disuguaglianza (>=, >, <= o <); ON contiene le chiavi uguali. Le righe senza corrispondenza tornano con NULL, come in un left join.

Niente ASOF JOIN? Usa questa alternativa

BigQuery e PostgreSQL non hanno la parola chiave ASOF, ma due schemi danno la stessa risposta. Nei nostri test entrambi hanno restituito risultati identici alle versioni ASOF.

QUALIFY

BigQuery e quasi tutti
Idea
unisci tutte le quotazioni precedenti, poi tieni l'ultima per transazione
  • Breve e leggibile
  • Costruisce prima tutte le corrispondenze precedenti, quindi può essere lento su tabelle grandi

Va bene da migliaia a milioni di righe.

LATERAL

Il migliore in PostgreSQL
Idea
per ogni transazione, cerca una riga con ORDER BY e LIMIT 1
  • In PostgreSQL usa un indice su (sym, ts), quindi scala bene
  • Non supportato in BigQuery

La scelta migliore in PostgreSQL.

SQL
-- 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;

Quattro errori da evitare

  1. 1

    Perdere righe per sbaglio

    Verifica se il semplice ASOF JOIN del tuo motore è inner. In DuckDB, ClickHouse e Spark scrivi LEFT se ogni riga di sinistra deve restare.

  2. 2

    Sbagliare direzione

    ">=" significa l'ultimo valore uguale o precedente; "<=" il prossimo valore uguale o successivo. pandas li chiama backward e forward; Polars li chiama strategie.

  3. 3

    Abbinare valori troppo vecchi

    Una quotazione di ieri resta "l'ultima". Aggiungi una tolleranza in pandas o Polars, o un filtro come t.ts - q.ts <= INTERVAL 1 HOUR in SQL.

  4. 4

    Dimenticare le chiavi uguali

    Senza ON t.sym = q.sym, una transazione su A può prendere una quotazione di B.

Corretto nel tempo
Usare solo i dati che esistevano nel momento analizzato.
Abbinamento all'indietro
L'ultima riga di destra uguale o precedente alla riga di sinistra; il comportamento predefinito nella maggior parte degli strumenti.
Abbinamento in avanti
La prima riga di destra uguale o successiva alla riga di sinistra, per esempio la prossima consegna dopo un ordine.
Tolleranza
Lo scarto massimo ammesso tra i due tempi; le corrispondenze più vecchie restituiscono NULL.

Verifica cosa hai capito

1/3Cosa restituisce un ASOF JOIN per ogni riga di sinistra?

2/3In DuckDB, cosa succede alle transazioni senza quotazione precedente se scrivi ASOF JOIN senza LEFT?

3/3BigQuery non ha ASOF JOIN. Qual è un'alternativa che funziona?

Altro SQL: i join SQL spiegati, le funzioni finestra spiegate e la guida rapida a SQL. Sugli strumenti: le novità di Polars 2.1 e DuckDB 2.0.

Fonti: documentazione di Snowflake su ASOF JOIN; documentazione di DuckDB, ClickHouse, pandas (merge_asof) e Polars (join_asof); note di rilascio di Apache Spark 4.3.0 (30 ottobre 2026). Esempi provati con DuckDB 2.0.0, PySpark 4.3.0, Polars 2.1.0 e pandas 3.1.0; la sintassi di Snowflake, ClickHouse, BigQuery e PostgreSQL segue la loro documentazione.

Alessandro Ecclesie AgazziScritto da Alessandro Ecclesie Agazzi, analytics engineer a Londra. Aggiornato il 9 novembre 2026. Chi sono

Ti è stato utile?

Qui escono regolarmente nuove guide e articoli sulle novità di dati e AI. Seguimi su LinkedIn o condividi questo articolo con un collega.