skip to content

What does a QUALIFY clause do in a top-N-per-group query, in dialects that offer it?

level: middleimportance: nice to knowfreq 26%

answer

  1. A filter for a stage that had none
  2. Same relationship as HAVING has to aggregates
  3. Removes the wrapper query from top-N per group
  4. Not in the standard; warehouse engines mostly

basics

~20 s

QUALIFY filters on window function results the way HAVING filters on aggregates, so you can write the rank predicate in the same query instead of wrapping it. It is a dialect extension, not part of the ISO SQL standard.

solid answer

~50 s

`QUALIFY` is a non-standard clause that filters rows using window function results, evaluated after the window functions have been computed — logically after `WHERE`, `GROUP BY` and `HAVING`, and before `ORDER BY` and any row limit. It removes the boilerplate wrapper from the top-N-per-group pattern: instead of ranking in a CTE and filtering `rn <= 3` outside, you write `SELECT ... FROM employees QUALIFY ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) <= 3` in a single query level. It originated in Teradata and is available in Snowflake, BigQuery and DuckDB among others; it is not in the SQL standard and is not accepted by PostgreSQL, MySQL, SQL Server, Oracle or SQLite, so treat it as a convenience you give up when the query has to be portable. The portable rewrite is always the same: move the window function into a CTE or derived table and filter it with an ordinary `WHERE`.

code

sql · 5 lines
sql
-- Snowflake / BigQuery / Teradata / DuckDB
SELECT employee_id, name, department_id, salary
FROM employees
QUALIFY ROW_NUMBER() OVER (PARTITION BY department_id
                           ORDER BY salary DESC) <= 3;

go deeper

for a junior

Just recognise the keyword if you meet it in warehouse SQL, and know it filters on a window function result. Write the portable CTE version yourself.

for a middle

Explain where QUALIFY sits in evaluation order relative to WHERE and the window functions, and translate a QUALIFY query into the standard two-level form without hesitation.

for a senior

Have a position on using it: convenient and readable on an engine that supports it, but a portability commitment. Be able to say which rows the window sees when WHERE and QUALIFY appear together.

for a principal

Own the house rule — whether the team's SQL targets a single warehouse and may use dialect extensions freely, or must stay portable across engines, and what that choice costs in readability.

## What the clause is for SQL has one filter per processing stage: `WHERE` filters input rows, `HAVING` filters groups after aggregation. Window functions arrived without a filter of their own, which is why the top-N-per-group pattern needs a second query level — you must materialise the rank as a column before any predicate can reach it. `QUALIFY` fills that gap. It is a clause that filters rows on the results of window functions, evaluated after those functions have been computed. Conceptually it is to window functions what `HAVING` is to aggregates. ```sql -- Snowflake / BigQuery / Teradata / DuckDB style SELECT employee_id, name, department_id, salary FROM employees QUALIFY ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) <= 3; ``` One query level, no CTE, and the intent is on a single line. ## Where it sits in evaluation order In dialects that implement it, `QUALIFY` runs after `FROM`, `WHERE`, `GROUP BY` and `HAVING` — those have already produced the rows the windows are computed over — and before `ORDER BY` and any row-limiting clause. That placement matters: a `WHERE` predicate in the same statement is applied *before* ranking, so `WHERE status = 'ACTIVE' QUALIFY ROW_NUMBER() OVER (...) <= 3` means "rank only the active employees, keep three", not "rank everyone, keep three, then discard inactive ones". That is usually the behaviour you want, and it is the same behaviour the two-level rewrite gives when the filter lives in the inner query. ## Portability `QUALIFY` is not part of the ISO SQL standard. It began in Teradata and is supported by Snowflake, Google BigQuery and DuckDB, among other analytical engines; other products may or may not have added it, so check your engine's documentation rather than assuming. It is not accepted by PostgreSQL, MySQL, SQL Server, Oracle or SQLite. Writing it in a codebase that might move between engines buys brevity at the cost of a rewrite later. ## The portable rewrite The translation is mechanical in both directions. Take the window function out of `QUALIFY`, give it an alias in the select list of a CTE or derived table, and filter that alias with `WHERE` in an enclosing query: ```sql WITH ranked AS ( SELECT employee_id, name, department_id, salary, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn FROM employees ) SELECT employee_id, name, department_id, salary FROM ranked WHERE rn <= 3; ``` One wrinkle: in the `QUALIFY` form the rank never appears in the output, while the rewrite must either project `rn` in the inner query and then re-list the wanted columns outside, or accept `rn` in the final result. Listing the columns explicitly in the outer `SELECT`, as above, keeps the two forms output-identical. ## How to handle it in an interview If you are asked for top-N-per-group, write the portable two-level version first — it is correct everywhere. Mentioning `QUALIFY` afterwards as a shortcut available in some analytical engines shows range without risking a query the interviewer's engine would reject. If the interview is explicitly for a warehouse that supports it, using it is fine, but still be able to state the standard rewrite on request, because that is the version that proves you understand *why* the extra level exists at all.

  • In a statement with both WHERE and QUALIFY, which rows does the window function see?
    Only the rows that survived `WHERE`. `QUALIFY` is evaluated after the window functions, which are themselves computed over the post-`WHERE` (and post-`HAVING`) row set. So `WHERE status = 'ACTIVE'` means inactive employees are excluded before ranking, and the top three are the top three active employees — the same semantics as putting that filter in the inner query of the portable rewrite.
  • Why does standard SQL not simply allow a window function in WHERE instead?
    Because `WHERE` is defined to run before window functions are computed — it produces the very row set the windows operate on. Allowing a window result there would be circular. The standard's answer is the extra query level; `QUALIFY` is a vendor shortcut that adds a later filtering stage rather than changing what `WHERE` means.

saying these in an interview costs you the question

  • Calls QUALIFY standard SQL available everywhere
  • Thinks QUALIFY can filter plain aggregates like HAVING
  • Believes QUALIFY runs before WHERE on the raw rows
  • Cannot produce the portable CTE rewrite when asked

context