窗口函数可保留原始行并为每行附加计算值,而group by和子查询会压缩或单值化结果;如计算客户订单累计金额,窗口函数直接高效,子查询则嵌套复杂。

需要保留原始行数据时,必须用窗口函数
聚合函数(如 GROUP BY)会把多行压成一行,子查询若嵌在 SELECT 或 WHERE 里,也常导致逻辑上“每行算一次”,但结果仍是单值;而窗口函数天然保留所有原始行,并为每行附加计算值。比如查“每个订单的金额,以及它在客户订单中的累计金额”,用子查询得写成 (SELECT SUM(amount) FROM orders o2 WHERE o2.customer_id = o1.customer_id AND o2.order_date ,性能差且难读;直接写 <code>SUM(amount) OVER (PARTITION BY customer_id ORDER BY order_date) 就行——只要需求里有“每行后面加一个统计值”,窗口函数就是唯一合理选择。
要筛排名前N或分组Top N时,优先套窗口函数+外层过滤
子查询实现 Top N 容易陷入“先分组、再排序、再 LIMIT”的陷阱,尤其跨分组时无法直接用 LIMIT。常见错误是写 SELECT * FROM t WHERE id IN (SELECT id FROM t ORDER BY score DESC LIMIT 3),这只能取全局 Top 3,不是“每组 Top 3”。正确做法是:用 ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY score DESC) 算出序号,再在外层 WHERE rn 过滤。注意 <code>RANK() 和 DENSE_RANK() 在并列时行为不同,选错会导致漏行或重复——例如销售榜要求“两个并列第1,下一个算第3”,就得用 RANK()。
涉及顺序依赖计算(环比、移动平均、LAG/LEAD)时,子查询基本不可行
LAG()、LEAD()、AVG() OVER (ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) 这类操作本质依赖行序和滑动范围,子查询无法自然表达。强行用自连接模拟,SQL 会爆炸式膨胀,执行计划里出现大量 DEPENDENT SUBQUERY 或 Using temporary; Using filesort。更隐蔽的问题是:子查询无法保证“前一行”到底指哪条——没显式 ORDER BY 时,数据库不承诺顺序;而窗口函数强制要求 ORDER BY,否则报错或结果不确定。所以只要看到需求里有“上个月”“前一天”“前3笔订单”,别犹豫,窗口函数是事实标准解法。
子查询只适合极轻量、单值、无排序依赖的判断
比如查“这个用户是否开通了 VIP”,对应 EXISTS (SELECT 1 FROM vip_log WHERE user_id = u.id AND status = 'active');或者查“当前配置项的值”,写成 (SELECT value FROM config WHERE key = 'timeout')。这类场景数据集极小、不涉及排序、不依赖行间关系,优化器通常能转成半连接(semi-join),性能尚可。一旦子查询里出现 GROUP BY、ORDER BY、JOIN 或嵌套两层以上,就该警觉——这不是语法问题,是模型错配。窗口函数不是万能,但它覆盖了报表、BI、风控、运营分析中 80% 的“复杂衍生值”场景;子查询退回到它原本的位置:做存在性判断或静态值注入。
真正容易被忽略的是:窗口函数的 ORDER BY 不只是“让结果好看”,它是物理执行顺序的契约。哪怕业务上觉得“部门内谁先谁后无所谓”,只要用了 SUM() OVER (PARTITION BY dept) 而没写 ORDER BY,MySQL 和 PostgreSQL 都可能每次返回不同累计值——因为底层没有定义扫描顺序,优化器自由选择。这点比语法细节更致命。










