What you will be able to do
- Choose the join type that keeps or drops unmatched rows, and recognise an accidental Cartesian product
- Aggregate with GROUP BY and GROUP BY ALL without falling into the alias-precedence trap
- Replace NULLs with COALESCE and remove duplicate rows with DISTINCT
- Sample with SAMPLE/TABLESAMPLE, choosing BERNOULLI or SYSTEM, fraction or fixed size, and with or without a seed
1.Joins: which rows survive, and where NULLs come from
Data for a model is often split across tables, and the join type you pick decides which rows reach the training set. In Snowflake SQL, a plain JOIN is an INNER JOIN. Each left row is paired with every right row that matches the ON condition, and rows without a match disappear. Outer joins keep the unmatched rows and fill the missing side with NULL. This is a common source of the missing values you will clean later.
| Join | Result |
|---|---|
| o1 INNER JOIN o2 (or plain JOIN) | Only matched pairs; unmatched rows on either side are dropped |
| o1 LEFT OUTER JOIN o2 | Inner result plus each unmatched o1 row; o2 columns are NULL |
| o1 RIGHT OUTER JOIN o2 | Inner result plus each unmatched o2 row; o1 columns are NULL |
| o1 FULL OUTER JOIN o2 | Inner result plus unmatched rows from both sides, padded with NULLs |
| o1 CROSS JOIN o2 | Every combination of rows (Cartesian product); ON is not allowed |
| o1 NATURAL JOIN o2 | Joins on same-named columns, which appear once in the output; ON is not allowed |
For most joins the ON clause is optional, which makes it easy to get wrong. Without it, the result is a Cartesian product. The documentation warns that this uses a lot of resources and is often a user error. USING(key_column) is a shorthand for an equality join on columns that share a name. With SELECT *, the shared key appears only once in the output.
A comma between tables in the FROM clause is another way to write an inner join, with the join condition in the WHERE clause. The aggregation example in the next section uses this form. A comma without a WHERE clause behaves like CROSS JOIN, so it is a Cartesian product too.
In Snowpark Python you join DataFrames by calling the join method. Pass a column expression, using each DataFrame's col method to say which side a column comes from. When both DataFrames share the join column, you can pass its name in a list instead.
df_lhs.join(df_rhs, df_lhs.col("key") == df_rhs.col("key")).select(df_lhs["key"].as_("key"), "value1", "value2").show()The sources show the Snowpark join with a column-equality condition and the shorthand df_lhs.join(df_rhs, ["key"]). They do not show how to choose outer join types from Snowpark. The outer-join semantics above are given in SQL, which you can also run from Snowpark through session.sql().
Checkpoint 1 of 7· Match them up
Match each join to how it treats rows without a match.
Tap a term, then the definition that fits it.
Only outer joins keep unmatched rows, and they fill the missing side with NULL. Leaving out ON turns a join into a Cartesian product.
“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
2.Aggregating with GROUP BY
Aggregation turns many rows into one row per entity, such as one row per product or per customer. GROUP BY puts rows with the same group-by values together and computes aggregate functions for each group. A group-by item can be a column name, a position in the SELECT list, or a general expression. The example below does a join and an aggregation in one statement: it joins sales to products and sums profit per product.
SELECT p.product_ID, SUM((s.retail_price - p.wholesale_price) * s.quantity) AS profit
FROM products AS p, sales AS s
WHERE s.product_ID = p.product_ID
GROUP BY p.product_ID;GROUP BY ALL groups by every SELECT item that does not use an aggregate function, so you don't have to repeat those columns. GROUP BY ROLLUP adds subtotal rows for hierarchical data, GROUP BY CUBE adds subtotals for every combination of dimensions, and GROUPING SETS computes several GROUP BY clauses in one statement.
Watch for names that clash. If a SELECT alias has the same name as a real column and you GROUP BY that name, Snowflake groups by the column, not the alias. In the documented example, grouping by state when state is also an alias for ANY_VALUE(employment_state) groups the salaries by the table's state column (California, Oregon), not by employment status.
On the Python side, the Snowpark pandas API (pandas on Snowflake) aggregates with the familiar pandas groupby. The documented example computes the average daily snowfall per location after loading a table with pd.read_snowflake:
df.groupby("LOCATION").mean()["SNOWFALL"]Checkpoint 2 of 7· Check yourself
SELECT SUM(salary), ANY_VALUE(employment_state) AS state FROM employees GROUP BY state; The employees table also has a column named state. What does Snowflake group by?
When a name matches both a column and an alias, the column wins. The query groups by the state column even though the output shows employment_state values.
“If a clause contains a name that matches both a column name and an alias, then the clause uses the column name.”Source: docs.snowflake.com
3.Filling NULLs with COALESCE and removing duplicates with DISTINCT
NULLs reach your data from the source system, from outer joins, and from TRY_CAST on values it could not convert. In SQL, COALESCE replaces them: it returns the first non-NULL argument, or NULL if every argument is NULL. COALESCE(discount, 0) gives a default, and COALESCE(col_a, col_b, col_c) takes the first column that has a value. NVL(expr1, expr2) is the two-argument form: it returns expr2 when expr1 is NULL.
COALESCE has one sharp edge. Snowflake implicitly converts the arguments to a compatible type, and that conversion can fail. COALESCE('17', 1) converts '17' to the number 17, but COALESCE('foo', 1) raises an error. Snowflake recommends passing arguments of the same type, or converting them explicitly, for example with the TRY_CAST you met earlier.
In Python, the Snowpark pandas API drops rows that contain NULLs with df.dropna(). The sources for this lesson do not document the Snowpark Python DataFrame methods for filling NULLs or for removing duplicates, so check the Snowpark API reference for those.
Checkpoint 3 of 7· Check yourself
A cleaning query runs SELECT COALESCE(amount_text, 0) where amount_text is VARCHAR and some rows hold 'n/a'. What is the risk?
With a numeric argument, COALESCE tries to convert the string argument to a number, and a value like 'n/a' cannot be converted. Convert with TRY_CAST first, then COALESCE.
“SELECT COALESCE('foo', 1); returns an error because the VARCHAR value 'foo' can’t be converted to a NUMBER value.”Source: docs.snowflake.com
Duplicates are handled in the SELECT list. SELECT ALL, the default, keeps every row of the result. SELECT DISTINCT removes duplicates, so two rows that are identical across all selected columns become one. Because DISTINCT compares only the columns you select, drop irrelevant columns such as load timestamps before deduplicating. Otherwise rows that differ only in those columns will survive as separate rows.
DISTINCT cannot tell you which duplicate survives. When you need the entire row for each key, use QUALIFY with ROW_NUMBER: QUALIFY ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY updated_at DESC) = 1 keeps the latest row per customer. The documentation calls this version explicit about which duplicate to keep.
To measure data quality, Snowflake provides system data metric functions. NULL_COUNT returns the total number of NULL values in a column, and DUPLICATE_COUNT returns the count of column values that have duplicates, including NULL values.
Checkpoint 4 of 7· Exam question
A data steward wants to quantify data-quality problems in `RAW.CLAIMS` on an ongoing basis before any cleaning code is written. They need repeated measurements of missing values in `claim_amount` and of repeated `claim_id` values. Select TWO actions that achieve this.(Select 2)
Correct answers: C, D — Set a `DATA_METRIC_SCHEDULE` on the table and associate the system DMF `SNOWFLAKE.CORE.NULL_COUNT` with `claim_amount` to track missing values per run.; Associate `SNOWFLAKE.CORE.DUPLICATE_COUNT` with the `claim_id` column so that each scheduled evaluation reports how many values of that key occur more than once.
- A. Incorrect. `FRESHNESS` reports how recently data in a timestamp column was updated; it says nothing about repeated key values.
- B. Incorrect. `IS_NULLABLE` is a schema constraint flag, not a count of stored NULLs, so it cannot measure missing data.
- C. Correct. System DMFs run on the schedule defined for the table, and `NULL_COUNT` reports how many NULLs a column holds at each measurement.
- D. Correct. `DUPLICATE_COUNT` is the system metric that measures repeated values, which is exactly what a key-uniqueness check needs.
- E. Incorrect. A stream captures row changes and action metadata only, and it holds no per-column quality statistics.
4.Sampling with SAMPLE / TABLESAMPLE
When a table is too big to explore or to prototype on, take a sample of it. SAMPLE and TABLESAMPLE mean the same thing in Snowflake. You can sample a fraction of the table, where each row or block is included with a given probability, or a fixed number of rows.
| Option | Behaviour | Constraint |
|---|---|---|
| BERNOULLI / ROW (default) | Includes each row with probability p/100; expected rows about (p/100)*n | Supports fraction-based and fixed-size sampling |
| SYSTEM / BLOCK | Includes each block of rows with probability p/100; often faster | Fraction-based only; may be biased on small tables |
| num ROWS | Returns exactly num rows, or the whole table if it is smaller | num up to 1,000,000; no SYSTEM, BLOCK or SEED |
| SEED / REPEATABLE (seed) | Same table, seed and probability give the same sample | Applies to SYSTEM/BLOCK; not supported on views or subqueries |
| LIMIT (not sampling) | Returns rows in the fastest way possible | Not a random sample |
Checkpoint 5 of 7· Fill the gap
This query should give the same 3% block sample every time it runs against an unchanged table. Which sampling method completes it?
SELECT * FROM testtable SAMPLE ? (3) SEED (82);The seed parameter applies only to SYSTEM and BLOCK sampling, and SYSTEM samples blocks of rows, which is what the query asks for.
Source: docs.snowflake.comWhere you put SAMPLE matters in a join. A SAMPLE clause applies only to the table right before it. table1 JOIN table2 SAMPLE (50) joins all of table1 to half of table2. To sample the join result, run the join as a subquery and put SAMPLE after it. That works only with row-based (Bernoulli) sampling and no seed, and it does not save any join work. To make the join itself smaller, sample each table before joining it.
On speed: SYSTEM is often faster than BERNOULLI, and sampling without a seed is often faster than sampling with one. Fixed-size sampling can be slower than the equivalent fraction, because it prevents some query optimization. These sources do not cover a Snowpark Python sampling method, but you can run any of these SAMPLE queries through session.sql().
Checkpoint 6 of 7· Check yourself
Which sampling query does Snowflake reject?
SYSTEM (block) sampling cannot return a fixed number of rows. The other three queries appear as valid examples in the documentation.
“SYSTEM, BLOCK, and SEED (seed) aren’t supported for fixed-size sampling.”Source: docs.snowflake.com
Checkpoint 7 of 7· Exam question
A feature pipeline joins `ORDERS` (one row per order) to `PAYMENTS` (several installment rows per order) and then sums `order_total` per customer. The resulting customer spend values are roughly double what finance reports. Select TWO changes that fix the problem.(Select 2)
Correct answers: C, D — Aggregate `PAYMENTS` to one row per `order_id` in a subquery or intermediate DataFrame, and only then join that result to `ORDERS`.; Calculate the `order_total` sum from `ORDERS` alone at customer grain, and join the payment-level aggregates to that result afterwards.
- A. Incorrect. A different join type does not change the one-to-many multiplication; matched orders are still repeated once per installment.
- B. Incorrect. Distinct sums remove legitimately equal totals from different orders, which understates spend and hides the real fan-out bug.
- C. Correct. Collapsing the many side to the order grain first removes the row multiplication that was repeating each `order_total`.
- D. Correct. Summing before the join means installment rows can no longer multiply the order amounts.
- E. Incorrect. Installment rows differ in their payment columns, so `distinct()` finds no duplicates and the multiplied totals remain.
Sources10
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.If a SELECT alias and a table column share a name, GROUP BY uses the alias you just defined.Why is that wrong?
Snowflake resolves the name to the table column first, so the query can group by a completely different field from the one it displays.
Covered in Aggregating with GROUP BY
2.Putting SAMPLE after a join, or sampling a join result in a subquery, cuts the cost of the join.Why is that wrong?
A SAMPLE clause applies to one table only, and sampling a join result happens after the full join has run. To make the join smaller, sample the input tables.
Covered in Sampling with SAMPLE / TABLESAMPLE
3.Leaving out the ON clause just returns the matching rows, like a natural join.Why is that wrong?
Except for NATURAL JOIN, which infers its join columns, a join with no ON clause is a Cartesian product: every left row paired with every right row.
Covered in Joins: which rows survive, and where NULLs come from
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“The result columns referencing o2 contain null.”
↩︎ Joins: which rows survive, and where NULLs come from“If the word JOIN is used without specifying INNER or OUTER, then the JOIN is an inner join.”
↩︎ Joins: which rows survive, and where NULLs come from“This causes the query to return the key_column exactly once.”
↩︎ Joins: which rows survive, and where NULLs come from“if you use a comma without a WHERE clause, the result is the same as using CROSS JOIN”
↩︎ Joins: which rows survive, and where NULLs come from“omitting the ON clause results in a Cartesian product; every row of object_ref1 paired with every row of object_ref2.”
↩︎ Exam trap 3“omitting the ON clause results in a Cartesian product; every row of object_ref1 paired with every row of object_ref2.”
↩︎ Checkpoint - 2.
“To join DataFrame objects, call the join method:”
↩︎ Joins: which rows survive, and where NULLs come from“If both dataframes have the same column "key", the following is more convenient.”
↩︎ Joins: which rows survive, and where NULLs come from - 3.
“Groups rows with the same group-by-item expressions and computes aggregate functions for the resulting group.”
↩︎ Aggregating with GROUP BY“Specifies that all items in the SELECT list that do not use aggregate functions should be used for grouping.”
↩︎ Aggregating with GROUP BY“If a clause contains a name that matches both a column name and an alias, then the clause uses the column name.”
↩︎ Exam trap 1“If a clause contains a name that matches both a column name and an alias, then the clause uses the column name.”
↩︎ Checkpoint - 4.
“# Compute the average daily snowfall across locations.”
↩︎ Aggregating with GROUP BY“# Drop rows with null values.”
↩︎ Filling NULLs with COALESCE and removing duplicates with DISTINCT - 5.
“Returns the first non-NULL expression among its arguments, or NULL if all its arguments are NULL.”
↩︎ Filling NULLs with COALESCE and removing duplicates with DISTINCT“We recommend passing in arguments of the same type or explicitly converting arguments if needed.”
↩︎ Filling NULLs with COALESCE and removing duplicates with DISTINCT“SELECT COALESCE('foo', 1); returns an error because the VARCHAR value 'foo' can’t be converted to a NUMBER value.”
↩︎ Checkpoint - 6.
“If expr1 is NULL, returns expr2, otherwise returns expr1.”
↩︎ Filling NULLs with COALESCE and removing duplicates with DISTINCT - 7.
“DISTINCT eliminates duplicate values from the result set. Default: ALL”
↩︎ Filling NULLs with COALESCE and removing duplicates with DISTINCT - 8.
“The QUALIFY version is explicit about which duplicate to keep.”
↩︎ Filling NULLs with COALESCE and removing duplicates with DISTINCT - 9.
“Returns the total number of NULL values for the specified column in a table.”
↩︎ Filling NULLs with COALESCE and removing duplicates with DISTINCT“Returns the count of column values that have duplicates, including NULL values.”
↩︎ Filling NULLs with COALESCE and removing duplicates with DISTINCT - 10.
“SAMPLE and TABLESAMPLE are synonymous and can be used interchangeably.”
↩︎ Sampling with SAMPLE / TABLESAMPLE“This parameter only applies to SYSTEM and BLOCK sampling.”
↩︎ Sampling with SAMPLE / TABLESAMPLE“Sampling with SEED (seed) isn’t supported on views or subqueries.”
↩︎ Sampling with SAMPLE / TABLESAMPLE“The SAMPLE clause applies to only one table, not all preceding tables or the entire expression prior to the SAMPLE clause.”
↩︎ Exam trap 2“Therefore, sampling doesn’t reduce the number of rows joined and doesn’t reduce the cost of the join.”
↩︎ Prediction“SYSTEM, BLOCK, and SEED (seed) aren’t supported for fixed-size sampling.”
↩︎ Checkpoint