ASOF JOIN

Description

ASOF JOIN combines each row from the left relation with at most one row from the right relation. The match is the closest row on the right that satisfies a required MATCH_CONDITION comparison and an optional ON or USING filter.

ASOF JOIN is asymmetric: the left relation drives per-row lookups into the right, and reversing the operands changes the result.

ASOF JOIN is disabled by default. Set spark.sql.join.asofJoin.enabled to true to enable the syntax. When disabled, ASOF JOIN fails at parse time with UNSUPPORTED_FEATURE.ASOF_JOIN.

Only INNER (the default) and LEFT OUTER join types are supported.

Syntax

[ asof_join_type ] ASOF JOIN right_table_reference join_criteria

asof_join_type
  { [ INNER ] |
    LEFT [ OUTER ] }

join_criteria
  MATCH_CONDITION ( expr1 comparison_operator expr2 )
  [ ON boolean_expression | USING ( column_name [, ...] ) ]

comparison_operator
  { >= | > | <= | < }

Parameters

Notes

Examples

SET spark.sql.join.asofJoin.enabled=true;

CREATE OR REPLACE TEMP VIEW trades(trade_time, symbol, quantity) AS
  VALUES (TIMESTAMP '2026-06-29 10:00:05', 'AAPL', 100),
         (TIMESTAMP '2026-06-29 10:00:11', 'AAPL', 200),
         (TIMESTAMP '2026-06-29 10:00:12', 'MSFT',  50),
         (TIMESTAMP '2026-06-29 09:59:59', 'GOOG',  30);

CREATE OR REPLACE TEMP VIEW quotes(quote_time, symbol, bid_price) AS
  VALUES (TIMESTAMP '2026-06-29 10:00:00', 'AAPL', 180.10),
         (TIMESTAMP '2026-06-29 10:00:07', 'AAPL', 180.15),
         (TIMESTAMP '2026-06-29 10:00:10', 'AAPL', 180.20),
         (TIMESTAMP '2026-06-29 10:00:08', 'MSFT', 420.50);

-- Attach the most recent (last-preceding) quote to each trade.
-- The GOOG trade has no matching quote and is dropped by INNER ASOF JOIN.
SELECT t.trade_time, t.symbol, t.quantity, q.bid_price
FROM trades t
ASOF JOIN quotes q
  MATCH_CONDITION (t.trade_time >= q.quote_time)
  ON t.symbol = q.symbol;
+-------------------+------+--------+---------+
|         trade_time|symbol|quantity|bid_price|
+-------------------+------+--------+---------+
|2026-06-29 10:00:05|  AAPL|     100|   180.10|
|2026-06-29 10:00:11|  AAPL|     200|   180.20|
|2026-06-29 10:00:12|  MSFT|      50|   420.50|
+-------------------+------+--------+---------+

-- Preserve unmatched left rows with LEFT ASOF JOIN.
SELECT t.trade_time, t.symbol, q.bid_price
FROM trades t
LEFT ASOF JOIN quotes q
  MATCH_CONDITION (t.trade_time >= q.quote_time)
  ON t.symbol = q.symbol;
+-------------------+------+---------+
|         trade_time|symbol|bid_price|
+-------------------+------+---------+
|2026-06-29 09:59:59|  GOOG|     NULL|
|2026-06-29 10:00:05|  AAPL|   180.10|
|2026-06-29 10:00:11|  AAPL|   180.20|
|2026-06-29 10:00:12|  MSFT|   420.50|
+-------------------+------+---------+

-- USING is equivalent to ON symbol equality.
SELECT trade_time, symbol, bid_price
FROM trades
ASOF JOIN quotes
  MATCH_CONDITION (trades.trade_time >= quotes.quote_time)
  USING (symbol);

-- Find the next scheduled maintenance window for each alert.
-- A <= operator selects the first-following right row.
CREATE OR REPLACE TEMP VIEW alerts(alert_time, host) AS
  VALUES (TIMESTAMP '2026-06-29 10:00:00', 'db-01');

CREATE OR REPLACE TEMP VIEW maintenance(window_start, host) AS
  VALUES (TIMESTAMP '2026-06-29 08:00:00', 'db-01'),
         (TIMESTAMP '2026-06-29 12:00:00', 'db-01');

SELECT a.alert_time, a.host, m.window_start
FROM alerts a
ASOF JOIN maintenance m
  MATCH_CONDITION (a.alert_time <= m.window_start)
  ON a.host = m.host;
+-------------------+-----+-------------------+
|         alert_time| host|       window_start|
+-------------------+-----+-------------------+
|2026-06-29 10:00:00|db-01|2026-06-29 12:00:00|
+-------------------+-----+-------------------+

-- Multi-column MATCH_CONDITION using tuple comparison. Both sides carry a
-- (time, seq) pair; ties on time are broken lexicographically by seq.
CREATE OR REPLACE TEMP VIEW deploys(deploy_ts, seq, service, version) AS
  VALUES (TIMESTAMP '2026-06-29 10:00:00', 1, 'api', 'v1.0'),
         (TIMESTAMP '2026-06-29 10:00:00', 2, 'api', 'v1.1'),
         (TIMESTAMP '2026-06-29 10:05:00', 1, 'api', 'v1.2');

CREATE OR REPLACE TEMP VIEW requests(req_ts, seq, service) AS
  VALUES (TIMESTAMP '2026-06-29 10:00:00', 5, 'api'),
         (TIMESTAMP '2026-06-29 10:03:00', 1, 'api');

SELECT r.req_ts, r.seq, d.version
FROM requests r
ASOF JOIN deploys d
  MATCH_CONDITION ((r.req_ts, r.seq) >= (d.deploy_ts, d.seq))
  ON r.service = d.service;
+-------------------+---+-------+
|             req_ts|seq|version|
+-------------------+---+-------+
|2026-06-29 10:00:00|  5|   v1.1|
|2026-06-29 10:03:00|  1|   v1.1|
+-------------------+---+-------+