What you will be able to do
- Choose between a GROUP BY aggregate and the same function used as a window function, based on the shape of the output you need
- Write an OVER clause with PARTITION BY, ORDER BY and an explicit window frame, and add tiebreakers so the results are deterministic
- Generate pseudo-random values with UNIFORM and RANDOM for pre-math steps such as assigning rows to test groups
- Use TRY_CAST so that a string that won't convert becomes NULL and the query doesn't fail
Key concept
Window function (partition) — A window function calculates a value for every input row from a related group of rows, called a partition. It does not collapse each group into a single row the way GROUP BY does. The OVER clause defines the partition, the ordering and the frame.
1.Aggregating with GROUP BY versus OVER
Most work with a dataset starts with summarising it. In Snowflake, SUM, COUNT and AVG each come in two forms. The function name is the same in both, and the OVER clause tells them apart. With GROUP BY, each group of rows collapses to one output row. That is the right shape for a report of totals per category. With an OVER clause, the function still computes over a group of rows, but every input row stays in the result and carries the computed value next to it.
SELECT menu_category,
AVG(menu_cogs_usd) avg_cogs
FROM menu_items
GROUP BY 1
ORDER BY menu_category;That query returns four rows: Beverage 0.60, Dessert 1.79, Main 6.11 and Snack 3.10. When the same AVG is used as a window function, every row in the table is returned. Each Beverage row shows 0.60000, and each Dessert row shows 1.79166. This gives you a simple way to validate an aggregate. The two forms must agree for each group, and the window form lets you check each detail row against its group value, such as the category average, in the same result.
Checkpoint 1 of 8· Fill the gap
Complete the query so that AVG returns the category average on every row instead of grouping the rows.
SELECT menu_category,
AVG(menu_cogs_usd) OVER( ? BY menu_category) avg_cogs
FROM menu_items
ORDER BY menu_category
LIMIT 15;Inside OVER, PARTITION BY defines the group of related rows. GROUP BY is a clause of the query, not of OVER.
Source: docs.snowflake.comValidation also means knowing how aggregates treat NULLs. AVG and SUM work on the non-NULL records only. COUNT has two behaviours: given specific columns it counts the non-NULL records, and otherwise it counts all records. Comparing COUNT(*) with COUNT(column) therefore shows how many NULLs a column holds. COUNT_IF counts the records that satisfy a condition. For large inputs, APPROX_COUNT_DISTINCT (alias HLL) estimates COUNT(DISTINCT ...) using HyperLogLog. It is deterministic for the same input, but the two results do not always match exactly: in the documentation example, 1024 distinct values were estimated as 1007. As a window function it does not support an ORDER BY or an explicit window frame.
GROUPING SETS extends GROUP BY to compute several groupings in one statement. Grouping by (a) and (b) as separate sets is equivalent to a UNION ALL of two GROUP BY queries. The output contains NULLs in the columns that a given grouping does not use, and a NULL can also come from NULL data, so read those rows with care.
Checkpoint 2 of 8· Check yourself
A column has some NULL values. You want to find out how many rows have a NULL in it. Which comparison helps?
COUNT with specified columns counts non-NULL records, while the total count covers all records. The difference is the number of NULLs.
“Returns either the number of non-NULL records for the specified columns, or the total number of records.”Source: docs.snowflake.com
Checkpoint 3 of 8· Exam question
A dashboard query uses `SUM(amount) OVER (ORDER BY sale_date)` to build a running total. Several orders share the same `sale_date`, and every row of that day shows the same cumulative figure instead of growing row by row. Which change produces a true row-by-row running total?
Correct answer: D — Declare `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW` and add a unique tiebreaker column to the `ORDER BY` clause.
- A. Partitioning by sale_date restarts the sum on every date, so the result becomes a per-day total rather than a cumulative total across days.
- B. DISTINCT only removes duplicate output rows after the window is computed; it does not change the frame, and it would drop legitimate orders from the day.
- C. A one-row-back frame yields a sliding two-row sum, not a cumulative total from the first row, so later rows would not accumulate history.
- D. The default frame with ORDER BY is RANGE, which treats rows with the same date as peers. An explicit ROWS frame plus a tiebreaker makes each row accumulate individually.
2.Ordering, window frames and ranking
With a partition on its own, every row in a partition gets the same value. Adding ORDER BY and a window frame inside OVER turns the calculation into a rolling one. The query below computes a moving average over the current row and the two rows that follow it in the same category. The query has a second ORDER BY at the end, which sorts the output. The two ORDER BY clauses are independent.
SELECT menu_category, menu_price_usd, menu_cogs_usd,
AVG(menu_cogs_usd) OVER(PARTITION BY menu_category
ORDER BY menu_price_usd, menu_cogs_usd ROWS BETWEEN CURRENT ROW and 2 FOLLOWING) avg_cogs
FROM menu_items
ORDER BY menu_category, menu_price_usd, menu_cogs_usd
LIMIT 15;menu_cogs_usd appears in the window's ORDER BY as a tiebreaker. Several items share the same price, and without a tiebreaker the 'following' rows could change from one run to the next. Two kinds of window function require ORDER BY: functions with an explicit frame, such as running totals and moving averages, and ranking functions such as CUME_DIST, RANK and DENSE_RANK. For example, ranking stores by monthly profit in descending order gives the most profitable store rank 1. Some functions treat an ORDER BY as an implied frame, so Snowflake recommends declaring the frame explicitly.
Checkpoint 4 of 8· Check yourself
In the moving-average output, the last Beverage row has menu_cogs_usd 0.65 and avg_cogs 0.65000. Why?
The frame is calculated within each partition. The last row has no rows after it, so the average is just that row's own value.
“The last row in a partition has no following rows so the average for the last Beverage row, for example, is the same”Source: docs.snowflake.com
So when an ORDER BY appears inside OVER, always name the columns. The ordinal shorthand that works in a query-level ORDER BY or in GROUP BY 1 has a different meaning inside OVER.
Sources1
3.Pre-math: randomization, ranking and grouping
Pre-math calculations prepare values that later analysis uses: a rank for each row, a group label, or a random number for assigning rows to test cohorts. Ranking comes from the window functions above, and grouping comes from GROUP BY or PARTITION BY. Ranking functions need an ORDER BY inside OVER. DENSE_RANK returns the rank of a value within a group of values without gaps in the ranks, and ROW_NUMBER is the function to use when you need a gap-free numbering of rows. For random values, Snowflake provides two related functions. RANDOM returns raw pseudo-random 64-bit integers. UNIFORM converts a random source into a value within a range you specify.
| Function | What it returns | Notes |
|---|---|---|
| RANDOM | Pseudo-random 64-bit integers | Accepts an optional seed so that a sequence can be repeated |
| UNIFORM(min, max, gen) | A uniformly distributed number in the inclusive range [min, max] | gen is the raw random source, usually RANDOM(). Integer bounds give an integer result. |
SELECT UNIFORM(1, 10, RANDOM()) FROM TABLE(GENERATOR(ROWCOUNT => 5));Checkpoint 5 of 8· Check yourself
A developer writes UNIFORM(1, 10.0, RANDOM()) to assign each row a whole-number bucket from 1 to 10. What does it return?
If either bound is a floating-point number, UNIFORM returns a floating-point number. Use integers for both bounds to get integers. Both bounds are inclusive.
“If either or both of min or max is a floating-point number, UNIFORM returns a floating-point number.”Source: docs.snowflake.com
Checkpoint 6 of 8· Exam question
A `salaries` table contains four employees earning 90, 90, 80 and 70. The analyst must list every employee who earns the second-highest distinct salary, which is 80, and the ranking must have no gaps after ties. Which filter returns the correct employee?
Correct answer: C — `QUALIFY DENSE_RANK() OVER (ORDER BY salary DESC) = 2`, because DENSE_RANK gives tied salaries one position and never skips numbers.
- A. RANK leaves a gap after ties, so the two 90 earners both get 1 and the 80 earner gets 3; no row has rank 2 and the query returns nothing.
- B. ROW_NUMBER breaks ties arbitrarily, so row 2 is one of the two 90 earners rather than the 80 earner.
- C. DENSE_RANK gives both 90 earners rank 1 and the 80 earner rank 2 with no gap, so filtering on 2 returns exactly the second-highest distinct salary.
- D. NTILE(2) puts the lower half of the rows into bucket 2, which here returns both the 80 and the 70 earner rather than one salary tier.
4.Casting values to a consistent type
Values usually need to be converted to one data type before they display consistently, for example strings that hold dates or numbers. Converting a data type is called casting. An explicit cast uses the CAST function, the :: operator, or a TO_ function such as TO_DOUBLE or TO_DATE. Snowflake also converts some values automatically, which is called implicit casting or coercion. For example, an INTEGER is coerced to VARCHAR so that 17 || '76' gives '1776'. Not all contexts support coercion, so cast explicitly when the type matters.
SELECT CAST('2022-04-01' AS DATE); SELECT '2022-04-01'::DATE; SELECT TO_DATE('2022-04-01');The semantics of CAST are the same as those of the matching TO_ function, and the :: operator is alternative syntax for CAST. When you give a target type a precision and scale, the target controls the display: if the scale is too small the value is rounded, so '9.8765' cast to NUMBER(5,2) becomes 9.88. If the precision is too small, or a value exceeds a VARCHAR length such as VARCHAR(4), the cast raises an error. The date and time TO_ functions also accept an optional format argument, with elements such as YYYY, MM, DD and HH24, to describe how a string is parsed or produced.
Checkpoint 7 of 8· Check yourself
A string '9.8765' is converted with CAST(varchar_value AS NUMBER(5,2)). What is the result?
An insufficient scale causes rounding. An insufficient precision is what raises an error.
“If the scale is not sufficient to hold the input value, the function rounds the value.”Source: docs.snowflake.com
CAST and the :: operator raise an error if a value can't be converted. On a messy column, one bad value is enough to make the whole query fail. TRY_CAST performs the same conversion but returns NULL for values it can't convert. It has two limits: its input must be a string expression, and the target type must be VARCHAR, NUMBER, DOUBLE, BOOLEAN, DATE, an interval variation, TIME or one of the TIMESTAMP types. The TRY_TO_ functions, such as TRY_TO_DATE, are the error-handling versions of the TO_ functions. They are optimised for data with few conversion errors and can be much slower when many conversions fail.
SELECT TRY_CAST('ABCD' AS CHAR(2));'ABCD' doesn't fit in CHAR(2), so the result is NULL. TRY_CAST('ABCD' AS VARCHAR(10)) returns ABCD. In the same way, '05-Mar-2016' converts to a TIMESTAMP, but '05/16' returns NULL.
Checkpoint 8 of 8· Fill the gap
A load contains date strings in several formats. Which function completes this statement so that it returns NULL instead of failing?
SELECT ? ('05/16' AS TIMESTAMP);TRY_CAST performs the same conversion as CAST but returns NULL instead of raising an error. That is why this example returns NULL.
Source: docs.snowflake.comExam traps
Each one states something that sounds right. Open it to see what is actually true.
1.OVER (PARTITION BY 1 ORDER BY 2) orders the window by the second column of the SELECT list.Why is that wrong?
Inside a window's ORDER BY, 2 is treated as the constant 2. Name the columns explicitly.
Covered in Ordering, window frames and ranking
2.TRY_CAST can safely replace CAST for any input type, including numeric columns.Why is that wrong?
TRY_CAST accepts only string expressions, and only some target types. For other conversions, use CAST.
Covered in Casting values to a consistent type
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“For a window function, the input is each row within a partition, and the output is one row per input row.”
↩︎ Aggregating with GROUP BY versus OVER“For an aggregate function, the input is a group of rows, and the output is one row.”
↩︎ Aggregating with GROUP BY versus OVER“Ranking window functions, such as CUME_DIST, RANK, and DENSE_RANK, which return information based on the “rank” of a row.”
↩︎ Ordering, window frames and ranking“If multiple rows have the same value for the ORDER BY columns, add additional columns as tiebreakers”
↩︎ Ordering, window frames and ranking“Snowflake recommends declaring window frames explicitly.”
↩︎ Ordering, window frames and ranking“A window function is an analytic SQL function that operates on a group of related rows known as a partition.”
↩︎ Key concept“The ORDER BY clause for window functions does not support the use of an ordinal position”
↩︎ Exam trap 1“The last row in a partition has no following rows so the average for the last Beverage row, for example, is the same”
↩︎ Checkpoint“In this context, 2 is interpreted as the constant 2; it does not refer to the second column in the query.”
↩︎ Prediction - 2.
“Returns either the number of non-NULL records for the specified columns, or the total number of records.”
↩︎ Aggregating with GROUP BY versus OVER“Returns the rank of a value within a group of values, without gaps in the ranks.”
↩︎ Pre-math: randomization, ranking and grouping - 3.
“Although the computation is an approximation, it is deterministic.”
↩︎ Aggregating with GROUP BY versus OVER - 4.
“GROUP BY GROUPING SETS((a), (b)) is equivalent to GROUP BY a UNION ALL GROUP BY b.”
↩︎ Aggregating with GROUP BY versus OVER - 5.
“Generates a uniformly-distributed pseudo-random number in the inclusive range [min, max].”
↩︎ Pre-math: randomization, ranking and grouping“RANDOM generates pseudo-random 64-bit integers. It accepts an optional seed that allows sequences to be repeated.”
↩︎ Pre-math: randomization, ranking and grouping“If either or both of min or max is a floating-point number, UNIFORM returns a floating-point number.”
↩︎ Checkpoint - 6.
“For examples that generate sequences without gaps, refer to SEQ1 / SEQ2 / SEQ4 / SEQ8 and ROW_NUMBER.”
↩︎ Pre-math: randomization, ranking and grouping - 7.
“returns a NULL value instead of raising an error when the conversion can not be performed.”
↩︎ Casting values to a consistent type“A special version of CAST , :: that is available for a subset of data type conversions.”
↩︎ Casting values to a consistent type“Only works for string expressions.”
↩︎ Exam trap 2 - 8.
“In some situations, Snowflake converts a value to another data type automatically. This is called implicit casting or coercion.”
↩︎ Casting values to a consistent type - 9.
“If the scale is not sufficient to hold the input value, the function rounds the value.”
↩︎ Casting values to a consistent type