What you will be able to do
- Pick the join type whose row-preservation behaviour matches the requirement
- Combine or compare query results with UNION, UNION ALL, INTERSECT and MINUS
- Write an ASOF JOIN with a valid MATCH_CONDITION and predict where it null-pads
- Place WHERE, HAVING and QUALIFY correctly in a query's evaluation order
1.Joins: which rows survive
Once discovery shows that a required element lives in another table, you need a join. The main choice is what happens to rows that have no match. Snowflake's recommended form is JOIN with an ON subclause. A bare JOIN, with no INNER or OUTER keyword, is an inner join.
| Join type | Result |
|---|---|
| o1 INNER JOIN o2 | For each row of o1, one row for each matching row of o2 |
| o1 LEFT OUTER JOIN o2 | Inner-join rows plus each unmatched o1 row, with o2 columns null |
| o1 RIGHT OUTER JOIN o2 | Inner-join rows plus each unmatched o2 row, with o1 columns null |
| o1 FULL OUTER JOIN o2 | Joined rows plus unmatched rows from both sides, null-extended |
| o1 CROSS JOIN o2 | Cartesian product; cannot take an ON clause |
| o1 NATURAL JOIN o2 | Join on the common columns, which appear once in the output; cannot take an ON clause |
USING (key_column) is shorthand for an equality on a column that has the same name in both tables. With SELECT *, that column appears only once. The most expensive mistake is to leave out the join condition. An INNER JOIN with no ON, or a comma join with no WHERE, produces a Cartesian product. Almost all of that output pairs rows that are not related.
Checkpoint 1 of 6· Check yourself
A colleague writes SELECT * FROM orders JOIN customers; with no ON or USING clause. What does Snowflake return?
ON is optional for these joins, but leaving it out produces a Cartesian product. Matching columns by name only happens with NATURAL JOIN.
“omitting the ON clause results in a Cartesian product; every row of object_ref1 paired with every row of object_ref2.”Source: docs.snowflake.com
Sources1
2.Set operators: stacking and comparing result sets
Joins add columns side by side. Set operators stack rows from separate queries. During discovery they answer questions such as "which keys appear in both sources?" or "which ones are missing from the target?" The plain forms match columns by position, so each query must select the same number of columns, with compatible types and the same meaning. The output column names come from the first query. If you write SELECT * on two tables whose columns are in a different order, you get wrong results without any error. UNION BY NAME and UNION ALL BY NAME match columns by name instead, and fill a column that is missing from one input with NULL.
| Operator | Returns | Duplicates |
|---|---|---|
| UNION (same as UNION DISTINCT) | Rows from both queries, matched by position | Eliminated |
| UNION ALL | Rows from both queries, matched by position | Kept |
| UNION [ALL] BY NAME | Rows from both queries, matched by column name | Eliminated (BY NAME) or kept (ALL BY NAME) |
| INTERSECT | Rows that appear in both result sets | Eliminated |
| MINUS / EXCEPT | Rows from the first query that are not in the second | n/a |
SELECT postal_code FROM sales_office_postal_example
INTERSECT
SELECT postal_code FROM customer_postal_example
ORDER BY postal_code;
+-------------+
| POSTAL_CODE |
|-------------|
| 94061 |
| 98005 |
+-------------+With MINUS, the order of the queries matters. Swapping them answers the opposite question. Precedence follows ANSI: INTERSECT binds tighter than UNION [ALL] and MINUS, which have equal precedence and are evaluated from left to right. Snowflake recommends adding parentheses whenever you mix operators.
Checkpoint 2 of 6· Fill the gap
Sales offices sit in 94061, 94070, 98116 and 98005; customers are in 94066, 94061, 98444 and 98005. Which operator produces this output?
SELECT postal_code FROM sales_office_postal_example
?
SELECT postal_code FROM customer_postal_example
ORDER BY postal_code;
+-------------+
| POSTAL_CODE |
|-------------|
| 94070 |
| 98116 |
+-------------+94070 and 98116 are the office postal codes that do not appear among the customers. That is the first query minus the second.
Source: docs.snowflake.comCheckpoint 3 of 6· Exam question
During discovery, an analyst must list customer IDs that exist in the CRM system but have no matching record in billing. Which query returns exactly those IDs?
Correct answer: C — `SELECT customer_id FROM crm_customers\nMINUS\nSELECT customer_id FROM billing_accounts`
- A. With the operands reversed, this returns billing accounts missing from the CRM, which is the opposite gap. Operand order matters for MINUS.
- B. UNION returns the distinct combined list of customers from both systems, so it does not isolate customers missing from billing.
- C. MINUS returns distinct rows from the first query that do not appear in the second, so placing the CRM query first yields CRM-only customers.
- D. INTERSECT returns customers present in both systems, which is the matched set rather than the unmatched CRM-only customers requested.
Sources2
3.ASOF JOIN: the closest match in time
Time-series sources rarely line up exactly. A trade at 09:00:05 has no quote stamped at exactly 09:00:05, so an equality join finds nothing. ASOF JOIN solves this. For each left-hand row it returns one right-hand row: the closest one in time, in the direction set by the comparison operator. The required MATCH_CONDITION names the left table's time column first, and its parentheses are mandatory. The supported types are DATE, TIME and the timestamp types, as well as NUMBER (for example UNIX epoch seconds). An optional ON or USING clause, which allows only equality conditions joined with AND, partitions the match. Use it so that a stock is matched only with quotes for the same stock.
SELECT *
FROM left_table l ASOF JOIN right_table r
MATCH_CONDITION(l.c3>=r.c3)
ON(l.c1=r.c1 and l.c2=r.c2)
ORDER BY 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 |
+----+----+----------+------+------+------+----------+------+Remove the ON clause and only time decides the match, so A-rows can pair with B-rows. Changing >= to <= reverses the direction and finds the next quote instead of the previous one. If two right-table rows tie on the timestamp, Snowflake still returns only one of them, and which one can differ between runs. A few further rules: you can use several ASOF joins in one query, but each needs its own MATCH_CONDITION. ASOF does not work with LATERAL table functions or LATERAL inline views. You can add a regular INNER JOIN, for example to look up company names, after the ASOF join.
Checkpoint 4 of 6· Check yourself
Which MATCH_CONDITION is invalid?
The match condition accepts only >=, <=, > and <. Equality conditions belong in the optional ON clause, and NUMBER columns are allowed.
“The comparison operator must be one of the following: >=, <=, >, <. The equals operator (=) is not supported.”Source: docs.snowflake.com
Checkpoint 5 of 6· Exam question
A trading analyst has `trades(symbol, trade_ts, qty)` and `quotes(symbol, quote_ts, bid)`. Each trade must be paired with the latest quote for the same symbol at or before the trade time. This query fails with a syntax error: ``` SELECT t.symbol, t.trade_ts, q.bid FROM trades t ASOF JOIN quotes q ON t.symbol = q.symbol AND t.trade_ts >= q.quote_ts ``` What is the correct fix?
Correct answer: C — Move the time comparison into `MATCH_CONDITION (t.trade_ts >= q.quote_ts)` and keep only the equality on `symbol` in the `ON` clause
- A. ASOF JOIN does not accept a range in `ON`, and `quotes` has no `next_quote_ts` column anyway. The closest-preceding-row logic comes from `MATCH_CONDITION`.
- B. ASOF JOIN does not support `USING` to pick a nearest timestamp. The time matching must be expressed in `MATCH_CONDITION`.
- C. ASOF JOIN requires the inequality on the time columns in a separate `MATCH_CONDITION` clause, with the `ON` clause carrying only equality keys such as `symbol`. With `>=`, each trade gets the closest quote at or before its timestamp.
- D. Exact equality on the timestamp would only match trades and quotes with identical times, which defeats the point-in-time lookup. ASOF JOIN exists specifically to match the closest earlier or later value.
Sources3
4.Filtering at the right stage: WHERE, HAVING, QUALIFY
After joining and combining, you filter down to what the business goal needs. Snowflake has a different filter for each stage of evaluation. WHERE filters source rows before grouping. HAVING filters the groups that GROUP BY produces, so its predicate can refer only to constants, GROUP BY expressions and aggregates. QUALIFY filters on window-function results, much as HAVING filters on aggregates. A common use is keeping the latest row per key. A query that uses QUALIFY must include at least one window function, in either the SELECT list or the QUALIFY predicate.
Checkpoint 6 of 6· Put it in order
Put these clauses in the order Snowflake typically evaluates them
- 1.GROUP BY
- 2.WHERE
- 3.HAVING
- 4.QUALIFY
- 5.ORDER BY
QUALIFY runs after window functions are computed, which is after HAVING. ORDER BY comes near the end, followed only by LIMIT.
“FROM WHERE GROUP BY HAVING WINDOW QUALIFY DISTINCT ORDER BY LIMIT”Source: docs.snowflake.com
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.UNION keeps every row from both queries, so it is the right choice for appending two extracts.Why is that wrong?
Plain UNION means UNION DISTINCT and removes duplicates. Use UNION ALL to keep them.
Covered in Set operators: stacking and comparing result sets
2.An ASOF JOIN always returns the same row when several right-table rows share the closest timestamp.Why is that wrong?
ASOF returns exactly one matching row. When rows tie, which one is returned can change from run to run.
Covered in ASOF JOIN: the closest match in time
3.An ASOF JOIN drops left-table rows that have no earlier (or later) match, like an inner join.Why is that wrong?
Unmatched left rows are kept, and the right-table columns are filled with NULLs.
Covered in ASOF JOIN: the closest match in time
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“If the word JOIN is used without specifying INNER or OUTER, then the JOIN is an inner join.”
↩︎ Joins: which rows survive“omitting the ON clause results in a Cartesian product; every row of object_ref1 paired with every row of object_ref2.”
↩︎ Checkpoint - 2.
“UNION ALL combines rows by column position without duplicate elimination.”
↩︎ Set operators: stacking and comparing result sets“The INTERSECT operator has higher precedence than UNION [ALL] and MINUS (EXCEPT).”
↩︎ Set operators: stacking and comparing result sets“If a column exists in one input but not the other, it is filled with NULL values in the combined result set”
↩︎ Set operators: stacking and comparing result sets“The default is UNION DISTINCT (that is, combine rows by column position with duplicate elimination).”
↩︎ Exam trap 1 - 3.
“the join finds a single row in the second (or right) table that has the closest timestamp value.”
↩︎ ASOF JOIN: the closest match in time“ASOF joins are not supported for joins with LATERAL table functions or LATERAL inline views.”
↩︎ ASOF JOIN: the closest match in time“The results are non-deterministic because any one of the tying rows might be returned.”
↩︎ Exam trap 2“When there is no match for a row in the left table, the columns from the right table are null-padded.”
↩︎ Exam trap 3“If no match is found for such values, the right table columns are null-padded.”
↩︎ Prediction“The comparison operator must be one of the following: >=, <=, >, <. The equals operator (=) is not supported.”
↩︎ Checkpoint - 4.
“QUALIFY does with window functions what HAVING does with aggregate functions and GROUP BY clauses.”
↩︎ Filtering at the right stage: WHERE, HAVING, QUALIFY“FROM WHERE GROUP BY HAVING WINDOW QUALIFY DISTINCT ORDER BY LIMIT”
↩︎ Checkpoint - 5.
“Filters rows produced by GROUP BY that do not satisfy a predicate.”
↩︎ Filtering at the right stage: WHERE, HAVING, QUALIFY