|
| 1 | +--- |
| 2 | +layout: global |
| 3 | +title: ASOF JOIN |
| 4 | +displayTitle: ASOF JOIN |
| 5 | +license: | |
| 6 | + Licensed to the Apache Software Foundation (ASF) under one or more |
| 7 | + contributor license agreements. See the NOTICE file distributed with |
| 8 | + this work for additional information regarding copyright ownership. |
| 9 | + The ASF licenses this file to You under the Apache License, Version 2.0 |
| 10 | + (the "License"); you may not use this file except in compliance with |
| 11 | + the License. You may obtain a copy of the License at |
| 12 | +
|
| 13 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 14 | +
|
| 15 | + Unless required by applicable law or agreed to in writing, software |
| 16 | + distributed under the License is distributed on an "AS IS" BASIS, |
| 17 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 18 | + See the License for the specific language governing permissions and |
| 19 | + limitations under the License. |
| 20 | +--- |
| 21 | + |
| 22 | +### Description |
| 23 | + |
| 24 | +`ASOF JOIN` combines each row from the left relation with at most one row from the |
| 25 | +right relation. The match is the closest row on the right that satisfies a required |
| 26 | +`MATCH_CONDITION` comparison and an optional `ON` or `USING` filter. |
| 27 | + |
| 28 | +`ASOF JOIN` is asymmetric: the left relation drives per-row lookups into the right, |
| 29 | +and reversing the operands changes the result. |
| 30 | + |
| 31 | +`ASOF JOIN` is disabled by default. Set `spark.sql.join.asofJoin.enabled` to `true` |
| 32 | +to enable the syntax. When disabled, `ASOF JOIN` fails at parse time with |
| 33 | +`UNSUPPORTED_FEATURE.ASOF_JOIN`. |
| 34 | + |
| 35 | +Only `INNER` (the default) and `LEFT OUTER` join types are supported. |
| 36 | + |
| 37 | +### Syntax |
| 38 | + |
| 39 | +```sql |
| 40 | +[ asof_join_type ] ASOF JOIN right_table_reference join_criteria |
| 41 | + |
| 42 | +asof_join_type |
| 43 | + { [ INNER ] | |
| 44 | + LEFT [ OUTER ] } |
| 45 | + |
| 46 | +join_criteria |
| 47 | + MATCH_CONDITION ( expr1 comparison_operator expr2 ) |
| 48 | + [ ON boolean_expression | USING ( column_name [, ...] ) ] |
| 49 | + |
| 50 | +comparison_operator |
| 51 | + { >= | > | <= | < } |
| 52 | +``` |
| 53 | + |
| 54 | +### Parameters |
| 55 | + |
| 56 | +* **right_table_reference** |
| 57 | + |
| 58 | + The table reference on the right of the join; searched once per left row. |
| 59 | + |
| 60 | +* **asof_join_type** |
| 61 | + |
| 62 | + The outer form of the `ASOF JOIN`. |
| 63 | + * `INNER` (default): drops left rows with no qualifying match. |
| 64 | + * `LEFT [ OUTER ]`: retains such rows with `NULL` right-side columns. |
| 65 | + |
| 66 | +* **join_criteria** |
| 67 | + |
| 68 | + A required `MATCH_CONDITION` clause and an optional `ON` or `USING` clause that |
| 69 | + filters the join input before the closest-match selection. When both `ON` and |
| 70 | + `USING` are omitted, every left row is matched against the entire right table on |
| 71 | + the `MATCH_CONDITION` alone. |
| 72 | + |
| 73 | + * **MATCH_CONDITION ( expr1 comparison_operator expr2 )** |
| 74 | + |
| 75 | + Specifies the ordered comparison that determines the closest match. One of |
| 76 | + `expr1` and `expr2` must reference only the left table; the other must |
| 77 | + reference only the right table. |
| 78 | + |
| 79 | + * **expr1**, **expr2** |
| 80 | + |
| 81 | + Scalar expressions of an orderable, mutually comparable type. Each must be |
| 82 | + deterministic; subqueries, aggregate functions, window functions, and |
| 83 | + generator functions are not permitted. `STRUCT` operands compare |
| 84 | + lexicographically field-by-field, enabling multi-column match conditions |
| 85 | + (see Examples). |
| 86 | + |
| 87 | + * **comparison_operator** |
| 88 | + |
| 89 | + One of `>=`, `>`, `<=`, `<`. Equality operators (`=` and `<>`) are not |
| 90 | + permitted. |
| 91 | + |
| 92 | + * **ON boolean_expression** |
| 93 | + |
| 94 | + An expression with a return type of `BOOLEAN` that specifies how rows from the |
| 95 | + two relations are matched before the closest-match selection. If the result is |
| 96 | + `true`, the rows are considered a match. See [JOIN](sql-ref-syntax-qry-select-join.html). |
| 97 | + |
| 98 | + * **USING ( column_name [, ...] )** |
| 99 | + |
| 100 | + Matches rows by comparing equality for the list of columns, which must exist in |
| 101 | + both relations. See [JOIN](sql-ref-syntax-qry-select-join.html). |
| 102 | + |
| 103 | +### Notes |
| 104 | + |
| 105 | +* **Direction of match.** Let *L* be the operand of `MATCH_CONDITION` that references |
| 106 | + the left table and *R* the operand that references the right table. The operator |
| 107 | + determines which row on the right is closest: |
| 108 | + * *L* `>=` *R* (equivalently *R* `<=` *L*): the right row with the largest *R* not |
| 109 | + greater than *L* (last-preceding). |
| 110 | + * *L* `>` *R*: the largest *R* strictly less than *L* (last strictly-preceding). |
| 111 | + * *L* `<=` *R*: the smallest *R* not less than *L* (first-following). |
| 112 | + * *L* `<` *R*: the smallest *R* strictly greater than *L* (first strictly-following). |
| 113 | + |
| 114 | +* **NULL.** A `NULL` in either operand never satisfies the `MATCH_CONDITION` |
| 115 | + comparison. Left rows whose operand is `NULL` are dropped under `INNER ASOF` and |
| 116 | + retained with `NULL` right-side columns under `LEFT ASOF`. |
| 117 | + |
| 118 | +* **Ties.** If multiple right rows share the same *R* value that is closest to *L* |
| 119 | + after applying `ON`/`USING`, the choice among tied rows is not deterministic. Add a |
| 120 | + tie-breaker either as an equi-key in `ON`/`USING` or as a trailing field of a |
| 121 | + `STRUCT`-valued `MATCH_CONDITION` (see Examples). |
| 122 | + |
| 123 | +### Examples |
| 124 | + |
| 125 | +```sql |
| 126 | +SET spark.sql.join.asofJoin.enabled=true; |
| 127 | + |
| 128 | +CREATE OR REPLACE TEMP VIEW trades(trade_time, symbol, quantity) AS |
| 129 | + VALUES (TIMESTAMP '2026-06-29 10:00:05', 'AAPL', 100), |
| 130 | + (TIMESTAMP '2026-06-29 10:00:11', 'AAPL', 200), |
| 131 | + (TIMESTAMP '2026-06-29 10:00:12', 'MSFT', 50), |
| 132 | + (TIMESTAMP '2026-06-29 09:59:59', 'GOOG', 30); |
| 133 | + |
| 134 | +CREATE OR REPLACE TEMP VIEW quotes(quote_time, symbol, bid_price) AS |
| 135 | + VALUES (TIMESTAMP '2026-06-29 10:00:00', 'AAPL', 180.10), |
| 136 | + (TIMESTAMP '2026-06-29 10:00:07', 'AAPL', 180.15), |
| 137 | + (TIMESTAMP '2026-06-29 10:00:10', 'AAPL', 180.20), |
| 138 | + (TIMESTAMP '2026-06-29 10:00:08', 'MSFT', 420.50); |
| 139 | + |
| 140 | +-- Attach the most recent (last-preceding) quote to each trade. |
| 141 | +-- The GOOG trade has no matching quote and is dropped by INNER ASOF JOIN. |
| 142 | +SELECT t.trade_time, t.symbol, t.quantity, q.bid_price |
| 143 | +FROM trades t |
| 144 | +ASOF JOIN quotes q |
| 145 | + MATCH_CONDITION (t.trade_time >= q.quote_time) |
| 146 | + ON t.symbol = q.symbol; |
| 147 | ++-------------------+------+--------+---------+ |
| 148 | +| trade_time|symbol|quantity|bid_price| |
| 149 | ++-------------------+------+--------+---------+ |
| 150 | +|2026-06-29 10:00:05| AAPL| 100| 180.10| |
| 151 | +|2026-06-29 10:00:11| AAPL| 200| 180.20| |
| 152 | +|2026-06-29 10:00:12| MSFT| 50| 420.50| |
| 153 | ++-------------------+------+--------+---------+ |
| 154 | + |
| 155 | +-- Preserve unmatched left rows with LEFT ASOF JOIN. |
| 156 | +SELECT t.trade_time, t.symbol, q.bid_price |
| 157 | +FROM trades t |
| 158 | +LEFT ASOF JOIN quotes q |
| 159 | + MATCH_CONDITION (t.trade_time >= q.quote_time) |
| 160 | + ON t.symbol = q.symbol; |
| 161 | ++-------------------+------+---------+ |
| 162 | +| trade_time|symbol|bid_price| |
| 163 | ++-------------------+------+---------+ |
| 164 | +|2026-06-29 09:59:59| GOOG| NULL| |
| 165 | +|2026-06-29 10:00:05| AAPL| 180.10| |
| 166 | +|2026-06-29 10:00:11| AAPL| 180.20| |
| 167 | +|2026-06-29 10:00:12| MSFT| 420.50| |
| 168 | ++-------------------+------+---------+ |
| 169 | + |
| 170 | +-- USING is equivalent to ON symbol equality. |
| 171 | +SELECT trade_time, symbol, bid_price |
| 172 | +FROM trades |
| 173 | +ASOF JOIN quotes |
| 174 | + MATCH_CONDITION (trades.trade_time >= quotes.quote_time) |
| 175 | + USING (symbol); |
| 176 | + |
| 177 | +-- Find the next scheduled maintenance window for each alert. |
| 178 | +-- A <= operator selects the first-following right row. |
| 179 | +CREATE OR REPLACE TEMP VIEW alerts(alert_time, host) AS |
| 180 | + VALUES (TIMESTAMP '2026-06-29 10:00:00', 'db-01'); |
| 181 | + |
| 182 | +CREATE OR REPLACE TEMP VIEW maintenance(window_start, host) AS |
| 183 | + VALUES (TIMESTAMP '2026-06-29 08:00:00', 'db-01'), |
| 184 | + (TIMESTAMP '2026-06-29 12:00:00', 'db-01'); |
| 185 | + |
| 186 | +SELECT a.alert_time, a.host, m.window_start |
| 187 | +FROM alerts a |
| 188 | +ASOF JOIN maintenance m |
| 189 | + MATCH_CONDITION (a.alert_time <= m.window_start) |
| 190 | + ON a.host = m.host; |
| 191 | ++-------------------+-----+-------------------+ |
| 192 | +| alert_time| host| window_start| |
| 193 | ++-------------------+-----+-------------------+ |
| 194 | +|2026-06-29 10:00:00|db-01|2026-06-29 12:00:00| |
| 195 | ++-------------------+-----+-------------------+ |
| 196 | + |
| 197 | +-- Multi-column MATCH_CONDITION using tuple comparison. Both sides carry a |
| 198 | +-- (time, seq) pair; ties on time are broken lexicographically by seq. |
| 199 | +CREATE OR REPLACE TEMP VIEW deploys(deploy_ts, seq, service, version) AS |
| 200 | + VALUES (TIMESTAMP '2026-06-29 10:00:00', 1, 'api', 'v1.0'), |
| 201 | + (TIMESTAMP '2026-06-29 10:00:00', 2, 'api', 'v1.1'), |
| 202 | + (TIMESTAMP '2026-06-29 10:05:00', 1, 'api', 'v1.2'); |
| 203 | + |
| 204 | +CREATE OR REPLACE TEMP VIEW requests(req_ts, seq, service) AS |
| 205 | + VALUES (TIMESTAMP '2026-06-29 10:00:00', 5, 'api'), |
| 206 | + (TIMESTAMP '2026-06-29 10:03:00', 1, 'api'); |
| 207 | + |
| 208 | +SELECT r.req_ts, r.seq, d.version |
| 209 | +FROM requests r |
| 210 | +ASOF JOIN deploys d |
| 211 | + MATCH_CONDITION ((r.req_ts, r.seq) >= (d.deploy_ts, d.seq)) |
| 212 | + ON r.service = d.service; |
| 213 | ++-------------------+---+-------+ |
| 214 | +| req_ts|seq|version| |
| 215 | ++-------------------+---+-------+ |
| 216 | +|2026-06-29 10:00:00| 5| v1.1| |
| 217 | +|2026-06-29 10:03:00| 1| v1.1| |
| 218 | ++-------------------+---+-------+ |
| 219 | +``` |
| 220 | + |
| 221 | +### Related Statements |
| 222 | + |
| 223 | +* [SELECT](sql-ref-syntax-qry-select.html) |
| 224 | +* [JOIN](sql-ref-syntax-qry-select-join.html) |
0 commit comments