Windows I — ranking & offsets
Concept  ·  SQL & Querying

A window function can't be filtered in WHERE — wrap it in a subquery or use QUALIFY to keep rn <= N.

Logical query order is FROM → WHERE → GROUP → HAVING → SELECT/windows → ORDER — the window value doesn't exist yet when WHERE runs, which is why the error is a binder error, not a runtime one. The honest shape is two steps: compute the rank in an inner query, filter rn = 1 outside; QUALIFY is that same two-step with the scaffolding hidden. Top-N inherits the ranking atom's ties lesson too.

See it break
sales
branchdrinkunits
northkale30
northbeet10
southmango22
southlime8
The trap
SELECT
  branch,
  drink
FROM
  sales
WHERE
  ROW_NUMBER() OVER (
    PARTITION BY
      branch
    ORDER BY
      units DESC
  ) = 1
ORDER BY
  branch;
The fix
SELECT
  branch,
  drink
FROM
  sales
QUALIFY
  ROW_NUMBER() OVER (
    PARTITION BY
      branch
    ORDER BY
      units DESC
  ) = 1
ORDER BY
  branch;

Filtering ROW_NUMBER() in WHERE raised a binder error — the window value doesn't exist yet when WHERE runs, so the engine refused rather than guessed; QUALIFY (or a subquery) is the compute-then-filter fix.

The rule

Subquery + outer filter (portable), or QUALIFY (DuckDB / Snowflake / BigQuery; Postgres has no QUALIFY — subquery there).

QUALIFY exists in DuckDB, Snowflake, BigQuery; Postgres needs the subquery form.

Shows up elsewhere
ROW_NUMBER vs RANK vs DENSE_RANK'Latest' without a tiebreaker
Try one →Prove it in practice →