0Pricing
SQL Interview Prep · 课时

按窗口结果筛选

了解为什么必须将窗口函数包装在子查询或 CTE 中,才能按其结果筛选

按窗口结果筛选 是 CoddyKit 上的免费 SQL Interview Prep 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 SQL Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 SQL Interview Prep 课程共包含 4 节课。

为什么不能在 WHERE 中筛选窗口结果

这是面试中常见的“陷阱”:编写 WHERE ROW_NUMBER() OVER (...) = 1 会报错。窗口函数不允许出现在 WHERE、GROUP BY 或 HAVING 中。

原因在于逻辑执行顺序。WHERE 会在窗口函数计算之前运行,用于选择行。此时窗口甚至还没有计算出来,因此不能在筛选条件中引用它。

执行顺序的解释

窗口函数在一个专门的阶段计算:这个阶段位于 FROM、WHERE、GROUP BY 和 HAVING 之后,但位于最终的 ORDER BY 和 LIMIT 之前。

因此,在 WHERE 运行时,排名或行号还不存在。若要根据它进行筛选,您必须先让窗口计算完成,再在外层查询中筛选生成的列。

子查询包装模式

标准解决方法是:在内层查询(派生表)中计算窗口函数,为结果指定别名,然后在外层 WHERE 中筛选这个别名。

派生表必须有别名(这里是 t)——面试官会特别注意候选人是否忘记这一点。现在,rn 就是外层查询可以进行比较的普通列。

SELECT *
FROM (
  SELECT
    name, department, salary,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
  FROM employees
) t
WHERE rn = 1;

CTE 模式(通常更清晰)

公用表表达式可以用更易读的结构完成同样的工作。在 WITH 步骤中定义排名,然后在主查询中筛选。

它在功能上与子查询完全相同,但在现场编码中,面试官通常更偏好 CTE,因为其意图从上到下阅读起来更清晰。

WITH ranked AS (
  SELECT
    name, department, salary,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
  FROM employees
)
SELECT name, department, salary
FROM ranked
WHERE rn = 1;

示例:每组前 N 名

最常见的窗口问题是:“每个部门薪资最高的前 3 名员工。”在 CTE 中排名,然后在外层保留 rn <= 3。

根据并列处理方式选择排名函数:ROW_NUMBER 会将每个部门严格限制为 3 行;如果必须包含边界处的并列项,请改用 RANK/DENSE_RANK。

WITH ranked AS (
  SELECT department, name, salary,
         ROW_NUMBER() OVER (
           PARTITION BY department ORDER BY salary DESC
         ) AS rn
  FROM employees
)
SELECT department, name, salary
FROM ranked
WHERE rn <= 3
ORDER BY department, rn;

示例:筛选累计总额

包装模式并不只适用于排名。任何窗口结果——累计总额、移动平均值、LAG 差值——都必须用相同的方式筛选。

这里我们先计算累计余额,然后只保留累计余额首次超过 1000 的行。筛选条件位于窗口层之外。

WITH balances AS (
  SELECT
    account_id, txn_date, amount,
    SUM(amount) OVER (
      PARTITION BY account_id ORDER BY txn_date
    ) AS running_balance
  FROM transactions
)
SELECT *
FROM balances
WHERE running_balance > 1000;

QUALIFY:某些数据库中的快捷方式

雪花数据库、BigQuery、特拉代塔数据库和 DuckDB 提供了 QUALIFY 子句,可以直接筛选窗口结果,无需包装查询。它会在窗口函数之后运行,正好位于所需的位置。

提到 QUALIFY 可以展示您的知识广度,但请注意,它不是标准结构化查询语言的一部分;PostgreSQL、MySQL 和微软数据库服务器都不支持它,在这些数据库中仍然需要使用子查询/CTE。

-- Snowflake / BigQuery only:
SELECT department, name, salary
FROM employees
QUALIFY ROW_NUMBER() OVER (
  PARTITION BY department ORDER BY salary DESC
) = 1;

不要混淆 HAVING 与窗口筛选

候选人有时会尝试用 HAVING 筛选排名。HAVING 会在 GROUP BY 聚合之后筛选分组,但仍然在窗口函数之前运行,因此同样不能引用窗口列。

  • WHERE → 在分组和窗口计算之前筛选行。
  • HAVING → 筛选聚合后的分组,但仍然在窗口计算之前。
  • 筛选窗口结果 → 需要使用外层查询(或 QUALIFY)。

结合预筛选与窗口筛选

您经常需要在窗口计算之前和之后都进行筛选。请在内层 WHERE 中应用普通的行筛选条件(这样窗口只会看到相关行),然后在外层查询中筛选窗口结果。

在这个示例中,我们先限制为活跃员工,然后从他们当中选出每个部门的最高收入者。将 WHERE active 放在内层会改变参与排名的行。

WITH ranked AS (
  SELECT department, name, salary,
         ROW_NUMBER() OVER (
           PARTITION BY department ORDER BY salary DESC
         ) AS rn
  FROM employees
  WHERE is_active = true        -- pre-filter before ranking
)
SELECT * FROM ranked
WHERE rn = 1;                   -- post-filter on the window

性能说明

面试官可能会问,包装查询是否会影响性能。通常不会:优化器会将子查询/CTE 视为同一个执行计划的一部分,并且只计算一次窗口。仅仅因为进行了包装,并不会产生额外扫描。

但有一个例外:在某些引擎中,CTE 可能成为优化屏障而被物化,因此对于热点路径,派生表或 QUALIFY 可能生成更好的执行计划。如果这很重要,请使用 EXPLAIN 进行分析。

常见错误

最后检查:

  • 绝不要将窗口函数放入 WHERE/HAVING——这样会报错。
  • 始终为派生表指定别名;FROM 中没有名称的子查询会被拒绝。
  • 根据问题所需的并列处理方式选择排名函数。
  • 只在支持 QUALIFY 的数据库中使用它;否则请回退到 CTE/子查询包装模式。

快速检查

为什么筛选窗口函数必须使用包装查询?

回顾:筛选窗口结果

您已经完整掌握了排名窗口函数的使用流程:

  • 窗口函数在 WHERE/GROUP BY/HAVING 之后运行,因此不能在那里筛选它们。
  • 将窗口函数放入子查询或CTE中(始终指定别名),然后在外层查询中筛选结果。
  • 这可以实现每组前 N 名、每个键的最新行以及累计总额阈值等需求。
  • QUALIFY 是一种方便但非标准的快捷方式,仅适用于雪花数据库/BigQuery。

现在,您已经掌握了面试官最常考查的完整排名工具集。

常见问题解答

「按窗口结果筛选」课时是免费的吗?

是的 — 「按窗口结果筛选」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 SQL Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 SQL Interview Prep 课程共包含 4 节课。

「按窗口结果筛选」这节课中我会学到什么?

了解为什么必须将窗口函数包装在子查询或 CTE 中,才能按其结果筛选 你通过在浏览器中直接运行的动手代码来练习 SQL Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 SQL Interview Prep 需要有经验吗?

无需任何先前经验。CoddyKit 上的 SQL Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「按窗口结果筛选」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 SQL Interview Prep 课中编写并运行代码吗?

能。每节 SQL Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. OVER、PARTITION BY 与 ORDER BY
  2. 使用 ROW_NUMBER 生成唯一序列
  3. 值相同时比较 RANK 与 DENSE_RANK
  4. 按窗口结果筛选
← 返回 SQL Interview Prep