An ASOF JOIN operation combines rows from two tables based on timestamp values that follow each
other, precede each other, or match exactly. For each row in the first (or left) table, the join finds a single
row in the second (or right) table that has the closest timestamp value. The qualifying row on the right side
is the closest match, which could be equal in time, earlier in time, or later in time, depending on the specified
comparison operator.
This topic describes how to use the ASOF JOIN construct in the FROM clause. For a more detailed conceptual
explanation of ASOF joins, see Analyzing time-series data.
See also JOIN, which covers the syntax for other standard join types, such as
inner and outer joins.
The first (or left) table in the FROM clause is assumed to contain records that either follow (in time),
precede, or are exactly synchronized with, the records in the second (or right) table. When there is no
match for a row in the left table, the columns from the right table are null-padded.
In addition to regular tables and views, any object reference can be used in an ASOF JOIN.
See FROM.
ASOF JOIN can be used in most contexts where joins are supported. For information about some restrictions, see Usage Notes.
This condition names the specific timestamp columns to be compared in each table.
The order of tables is important in the condition. The left table must be named first.
The parentheses are required.
The comparison operator must be one of the following: >=, <=, >, <. The equals operator (=) is
not supported.
All of the following data types are supported: DATE, TIME, DATETIME, TIMESTAMP, TIMESTAMP_LTZ, TIMESTAMP_NTZ, TIMESTAMP_TZ.
You can also use NUMBER columns in the match condition. For example, you might have NUMBER columns that contain UNIX
timestamps (which define the number of seconds that have elapsed since January 1st, 1970).
The data types of the two matched columns don’t have to be exactly the same, but they must be
compatible.
ON table.col = table.col [ AND ... ] | USING (column_list)
The optional ON or USING clause defines one or more equality conditions on columns in the two tables, for the purpose of
logically grouping the results of the query.
For general information about ON and USING, see JOIN. Note that a join specified with USING
projects one of the joining columns in its intermediate result set, not both. A join specified with an ON clause projects both
joining columns.
The following notes are specific to ASOF JOIN:
The comparison operator in the ON clause must be the equal sign (=).
The ON clause cannot contain disjuncts (conditions connected with OR). Conditions connected with AND are supported.
Each side of a condition must refer to only one of the two tables in the join. However, the order of the table references doesn’t matter.
Each condition can be enclosed in parentheses, but they aren’t required.
If no match is found in the right table for a given row, the result is null-padded for the selected columns from the right table. (ASOF joins are similar to left outer joins in this respect.)
If you use TIME columns in the match condition (as opposed to one of the timestamp types), you might need to set the TIME_OUTPUT_FORMAT parameter in order to see the exact TIME values that are being compared when you look at ASOF JOIN query results. By default, the display of a TIME column truncates milliseconds. See TIME columns in the match condition.
You can use more than one ASOF join in the same query as long as all of the syntax rules are followed for each join. Each join must be immediately followed by its own MATCH_CONDITION. You cannot apply a single MATCH_CONDITION to multiple ASOF joins. See Multiple ASOF joins in a query.
ASOF joins are not supported for joins with LATERAL table functions or LATERAL inline views. For more information about lateral joins, see LATERAL.
An ASOF join with a self-reference is not allowed in a RECURSIVE common table expression (CTE). For information about CTEs, see WITH.
The EXPLAIN output for ASOF JOIN queries identifies the ON (or USING) conditions and the MATCH_CONDITION. For example, in text or tabular format, output similar to the following text appears above the table scans in the plan:
Query profiles also clearly identify the ASOF JOIN operation in the plan. In this example, you can see that the table scan reads 22M rows from the left table, which are all preserved by the join. The profile also shows the match condition for the join.
The optional ON (or USING) conditions for ASOF JOIN provide a way of grouping or partitioning table rows before the final matching rows
are singled out by the required match condition. If you want the rows from the joined tables to be grouped on one or more dimensions
that the tables share (stock symbol, location, city, state, company name, etc.), use an ON condition.
If you don’t use an ON condition, each row from the left table may be matched (by time) with any row from the right table
in the final result set.
In the following example, tables left_table and right_table have values A, B, etc.
in column c1, and values 1, 2, etc. in column c2. Column c3 is a TIME column, and c4 is a numeric value (column of interest).
+----+----+----------+------+| C1 | C2 | C3 | C4 ||----+----+----------+------|| A |1|09:15:00|3.21|| A |2|09:16:00|3.22|| B |1|09:17:00|3.23|| B |2|09:18:00|4.23|+----+----+----------+------+
SELECT*FROM right_table ORDERBY c1, c2;
+----+----+----------+------+| C1 | C2 | C3 | C4 ||----+----+----------+------|| A |1|09:14:00|3.19|| B |1|09:16:00|3.04|+----+----+----------+------+
If c1 and c2 are both ON condition columns in the query, a row in the left table only matches a row in the right table
when A and 1, A and 2, B and 1, or B and 2 are found in both tables.
If no match is found for such values, the right table columns are null-padded.
SELECT*FROM left_table l ASOFJOIN right_table r
MATCH_CONDITION(l.c3>=r.c3)ON(l.c1=r.c1 and l.c2=r.c2)ORDERBY l.c1, l.c2;
+----+----+----------+------+------+------+----------+------+| C1 | C2 | C3 | C4 | C1 | C2 | C3 | C4 ||----+----+----------+------+------+------+----------+------|| A |1|09:15:00|3.21| A |1|09:14:00|3.19|| A |2|09:16:00|3.22|NULL|NULL|NULL|NULL|| B |1|09:17:00|3.23| B |1|09:16:00|3.04|| B |2|09:18:00|4.23|NULL|NULL|NULL|NULL|+----+----+----------+------+------+------+----------+------+
If the ON conditions are removed, any combination of values in c1 and c2 may be matched in the final result.
Only the match condition determines the results.
SELECT*FROM left_table l ASOFJOIN right_table r
MATCH_CONDITION(l.c3>=r.c3)ORDERBY l.c1, l.c2;
+----+----+----------+------+----+----+----------+------+| C1 | C2 | C3 | C4 | C1 | C2 | C3 | C4 ||----+----+----------+------+----+----+----------+------|| A |1|09:15:00|3.21| A |1|09:14:00|3.19|| A |2|09:16:00|3.22| B |1|09:16:00|3.04|| B |1|09:17:00|3.23| B |1|09:16:00|3.04|| B |2|09:18:00|4.23| B |1|09:16:00|3.04|+----+----+----------+------+----+----+----------+------+
Expected behavior when “ties” exist in the right table¶
ASOF JOIN queries always attempt to match a single row in the left table with a single row in the right table.
This behavior is true even if two (or more) rows in the right table are identical and qualify for the join. When
such ties exist and you run the same join query multiple times, you might get different results. The results are
non-deterministic because any one of the tying rows might be returned. If you’re unsure about the results of ASOF JOIN
queries, check for exact matches in the timestamp values for rows in the right table.
For example, using the same tables from the examples in the previous section, add a right_id column to right_table
and insert the following rows:
+----+----+----------+------+----------+| C1 | C2 | C3 | C4 | RIGHT_ID ||----+----+----------+------+----------|| A |1|09:14:00|3.19| A1 || A |1|09:14:00|3.19| A2 || B |1|09:16:00|3.04| B1 |+----+----+----------+------+----------+
Two of the rows are identical except for their right_id values. Now run the following ASOF JOIN query:
SELECT*FROM left_table l ASOFJOIN right_table r
MATCH_CONDITION(l.c3>=r.c3)ORDERBY l.c1, l.c2;
+----+----+----------+------+----+----+----------+------+----------+| C1 | C2 | C3 | C4 | C1 | C2 | C3 | C4 | RIGHT_ID ||----+----+----------+------+----+----+----------+------+----------|| A |1|09:15:00|3.21| A |1|09:14:00|3.19| A2 || A |2|09:16:00|3.22| B |1|09:16:00|3.04| B1 || B |1|09:17:00|3.23| B |1|09:16:00|3.04| B1 || B |2|09:18:00|4.23| B |1|09:16:00|3.04| B1 |+----+----+----------+------+----+----+----------+------+----------+
Note that rows A1 and A2 from right_table both qualify for the join, but only A2 is returned. On a
subsequent run of the same query, A1 could be returned instead.
Rewriting ASOF JOIN queries to reduce scans on the right table¶
When the cardinality of the ON or USING join column in the left table is lower than the cardinality of the
join column in the right table, the optimizer does not prune
the unmatched rows from the right table. Therefore, more rows than are needed for the join will be scanned
from the right table. This behavior typically occurs when the query includes a highly selective filter on a
non-join column from the left table, and the filter reduces the cardinality of the join column.
You can work around this problem by manually reducing the rows that qualify for the join. For example, the
original query has the following pattern, and t1.c1 has lower cardinality than t2.c1:
If a SELECT statement uses ASOF or MATCH_CONDITION as an alias, you must use AS before the alias or double-quote the
alias. For example, the following statements are no longer allowed and return errors:
Now run the first example query again. Note that the query returns four rows, but the new row is null-padded.
There is no row in the quotes table that qualifies for the match condition.
The columns from trades are returned, and the corresponding columns from quotes are null-padded.
SELECT t.stock_symbol, t.trade_time, t.quantity, q.quote_time, q.price
FROM trades t ASOFJOIN quotes q
MATCH_CONDITION(t.trade_time >= quote_time)ON t.stock_symbol=q.stock_symbol
ORDERBY t.stock_symbol;
Using a different comparison operator in the match condition¶
Following on from the previous example, the results of the query change again when the comparison operator in the
match condition is changed. The following query specifies the <= operator (instead of >=):
SELECT t.stock_symbol, t.trade_time, t.quantity, q.quote_time, q.price
FROM trades t ASOFJOIN quotes q
MATCH_CONDITION(t.trade_time <= quote_time)ON t.stock_symbol=q.stock_symbol
ORDERBY t.stock_symbol;
Specifying a USING condition instead of an ON condition¶
You can use an ON condition or a USING condition with ASOF JOIN queries. The following query is equivalent to the
previous query, but it replaces ON with USING. The syntax USING(stock_symbol) implies the condition
t.stock_symbol=q.stock_symbol.
SELECT t.stock_symbol, t.trade_time, t.quantity, q.quote_time, q.price
FROM trades t ASOFJOIN quotes q
MATCH_CONDITION(t.trade_time <= quote_time)USING(stock_symbol)ORDERBY t.stock_symbol;
The following example adds a third companies table to the join in order to pick the company name for each stock symbol.
You can use a regular INNER JOIN with an ON condition (or some other standard join syntax) to add the third table.
However, note that USING(stock_symbol) would not work here because the reference to stock_symbol would be ambiguous.
The following example demonstrates that the match condition can compare numeric values.
In this case, the tables have UNIX timestamp values stored in NUMBER(38,0) columns. 1696150805
is equivalent to 2023-10-30 10:20:05.000 (three seconds later than 1696150802).
The following examples join tables that contain weather observations. The observations in these tables are recorded in TIME columns.
You can create and load the tables as follows:
+----------------------------------+| status ||----------------------------------||Statement executed successfully.|+----------------------------------+
SELECT*FROM preciptime p ASOFJOIN snowtime s MATCH_CONDITION(p.observed>=s.observed)ORDERBY p.observed;
+--------------+-----------+-------+-------------+--------------+-----------+-------+-------------+| OBSERVED |LOCATION| STATE | OBSERVATION | OBSERVED |LOCATION| STATE | OBSERVATION ||--------------+-----------+-------+-------------+--------------+-----------+-------+-------------||14:40:34.000| Bozeman | MT |1.11|NULL|NULL|NULL|NULL||14:41:44.435| Las Vegas | NV |0.01|NULL|NULL|NULL|NULL||14:42:44.435| Reno | NV |0.01|14:42:44.435| Reno | NV |3.00||14:42:59.001| Oakhurst | CA |0.51|14:42:45.000| Bozeman | MT |1.80||14:42:59.230| Ahwahnee | CA |0.91|14:42:59.199| Fish Camp | CA |3.20|+--------------+-----------+-------+-------------+--------------+-----------+-------+-------------+
The following example shows how to connect a sequence of two or more ASOF joins in a single query block.
The three tables (snowtime, raintime, preciptime) all contain weather observations that were recorded in
specific locations at specific times. The column of interest is the observation column. The rows are logically grouped by state.
ALTERSESSIONSETTIME_OUTPUT_FORMAT='HH24:MI:SS.FF3';SELECT*FROM snowtime s
ASOFJOIN raintime r
MATCH_CONDITION(s.observed>=r.observed)ON s.state=r.state
ASOFJOIN preciptime p
MATCH_CONDITION(s.observed>=p.observed)ON s.state=p.state
ORDERBY s.observed;
+--------------+------------+-------+-------------+--------------+----------+-------+-------------+--------------+----------+-------+-------------+| OBSERVED |LOCATION| STATE | OBSERVATION | OBSERVED |LOCATION| STATE | OBSERVATION | OBSERVED |LOCATION| STATE | OBSERVATION ||--------------+------------+-------+-------------+--------------+----------+-------+-------------+--------------+----------+-------+-------------||14:42:44.435| Reno | NV |3.00|14:42:44.435| Reno | NV |0.00|14:42:44.435| Reno | NV |0.01||14:42:45.000| Bozeman | MT |1.80|NULL|NULL|NULL|NULL|14:40:34.000| Bozeman | MT |1.11||14:42:59.199| Fish Camp | CA |3.20|14:42:59.001| Oakhurst | CA |0.50|14:42:59.001| Oakhurst | CA |0.51||14:43:01.000| Lake Tahoe | CA |4.20|14:42:59.230| Ahwahnee | CA |0.90|14:42:59.230| Ahwahnee | CA |0.91|+--------------+------------+-------+-------------+--------------+----------+-------+-------------+--------------+----------+-------+-------------+
Following on from the previous example, two ASOF joins are specified, but this time the first match condition uses the >
operator and the second uses the < operator. The result is a single row that returns data from all three tables, and three rows
that return data from two of the tables. Many of the columns in the result set are null-padded.
Logically, the query finds only one row where the observed time from the snowtime table was later than the observed time from the
raintime table but earlier than the observed time from the preciptime table.
SELECT*FROM snowtime s
ASOFJOIN raintime r
MATCH_CONDITION(s.observed>r.observed)ON s.state=r.state
ASOFJOIN preciptime p
MATCH_CONDITION(s.observed<p.observed)ON s.state=p.state
ORDERBY s.observed;
+--------------+------------+-------+-------------+--------------+-----------+-------+-------------+--------------+----------+-------+-------------+| OBSERVED |LOCATION| STATE | OBSERVATION | OBSERVED |LOCATION| STATE | OBSERVATION | OBSERVED |LOCATION| STATE | OBSERVATION ||--------------+------------+-------+-------------+--------------+-----------+-------+-------------+--------------+----------+-------+-------------||14:42:44.435| Reno | NV |3.00|14:41:44.435| Las Vegas | NV |0.00|NULL|NULL|NULL|NULL||14:42:45.000| Bozeman | MT |1.80|NULL|NULL|NULL|NULL|NULL|NULL|NULL|NULL||14:42:59.199| Fish Camp | CA |3.20|14:42:59.001| Oakhurst | CA |0.50|14:42:59.230| Ahwahnee | CA |0.91||14:43:01.000| Lake Tahoe | CA |4.20|14:42:59.230| Ahwahnee | CA |0.90|NULL|NULL|NULL|NULL|+--------------+------------+-------+-------------+--------------+-----------+-------+-------------+--------------+----------+-------+-------------+
The following examples show queries that return expected syntax errors.
Having declared that snowtime s is the left table, you cannot begin the match condition with a reference to the right table, preciptime p:
SELECT*FROM snowtime s ASOFJOIN preciptime p MATCH_CONDITION(p.observed>=s.observed);
010002(42601): SQL compilation error:
MATCH_CONDITION clause is invalid: Theleft side allows onlycolumnreferencesfrom the left side table,and the right side allows onlycolumnreferencesfrom the right side table.
Only the >=, <=, >, and < operators are allowed in match conditions:
SELECT*FROM preciptime p ASOFJOIN snowtime s MATCH_CONDITION(p.observed=s.observed);
010001(42601): SQL compilation error:
MATCH_CONDITION clause is invalid: Only comparison operators '>=','>','<='and'<' are allowed. Keywords such asANDandOR are not allowed.
The ON clause for ASOF JOIN must contain equality conditions:
SELECT*FROM preciptime p ASOFJOIN snowtime s
MATCH_CONDITION(p.observed>=s.observed)ON s.state>=p.state;
010010(42601): SQL compilation error:
ON clause forASOFJOIN must contain conjunctions ofequality conditions only. Disjunctions are not allowed. Each side of an equality condition must only refer to either the lefttableor the righttable. S.STATE >= P.STATE is invalid.
An ON clause equality condition cannot contain disjunctions:
SELECT*FROM preciptime p ASOFJOIN snowtime s
MATCH_CONDITION(p.observed>=s.observed)ON s.state=p.state OR s.location=p.location;
010010(42601): SQL compilation error:
ON clause forASOFJOIN must contain conjunctions ofequality conditions only. Disjunctions are not allowed. Each side of an equality condition must only refer to either the lefttableor the righttable.(S.STATE = P.STATE)OR(S.LOCATION= P.LOCATION)is invalid.
ASOF joins cannot be used with LATERAL inline views:
SELECT t1.a "t1a", t2.a "t2a"FROM t1 ASOFJOINLATERAL(SELECT a FROM t2 WHERE t1.b = t2.b) t2
MATCH_CONDITION(t1.a >= t2.a)ORDERBY1,2;
010004(42601): SQL compilation error:
ASOFJOINisnot supported for joins withLATERALtablefunctionsorLATERALviews.