尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SQL窗口函数ROWS与RANGE区别详解:从原理到实战避坑指南

SQL窗口函数ROWS与RANGE区别详解:从原理到实战避坑指南 开窗函数这东西你在数据开发、数据分析的日常SQL里几乎天天碰得到。尤其是做累计、移动平均、同比环比的时候OVER(PARTITION BY ... ORDER BY ...)一写结果一跑有时候对、有时候不对最后发现根源基本都落在ROWS和RANGE这两个窗口范围关键词上。很多人用了几年的SUM() OVER()也不见得能把它们分清楚。老实说我刚入行那会儿也被这俩折腾得不轻后来花了一下午拿业务数据实测才彻底搞明白ROWS 是按行号圈窗口RANGE 是按排序键的数值范围圈窗口。这篇文章会用一份简单的销售数据把二者的区别、语法、实战场景和常见的坑一次性讲透。不管你是数据开发、数据分析师还是天天要跟 SQL 打交道的后端开发照着跑一遍基本就不会再弄混了。1. 先搞清楚窗口函数的基本盘窗口到底在哪里1.1 窗口函数和 GROUP BY 聚合函数别搞混了窗口函数也叫分析函数Window Function做的事情从名字上看像是在给每一行数据“开一个窗口”。它与 GROUP BY 最大的不同在于GROUP BY 会把符合条件的多行合并成一行窗口函数却不会改变原始表的行数它只是在每一行旁边额外多算一个值。比如你想知道每个部门的工资总额同时还想保留每个员工自己的姓名和工资明细用 GROUP BY 就只能得到部门汇总员工明细就消失了但用SUM(salary) OVER(PARTITION BY dept_id)每一行都会多出一列“部门总工资”明细行原地不动。这个特性非常重要尤其是做报表、做数据分析时你经常需要在明细数据上叠加汇总值去做占比、累计、对比。窗口函数就是为此设计的。而且它还有一个优势聚合计算的范围不是固定的一整张表而是可以动态定义的“分区窗口”。分区由 PARTITION BY 控制窗口则由 ORDER BY 配合 ROWS/RANGE 进一步收缩范围。很多初学者会把 MAX()、SUM() 这种聚合函数和开窗函数混在一起其实真正的“开窗”动作发生在 OVER 子句里。OVER 括号里的内容才决定这个窗口函数在哪些行上计算。窗口函数可以是常见的 SUM、AVG、COUNT、MIN、MAX也可以是 ROW_NUMBER、RANK、DENSE_RANK、LAG、LEAD 这类专用的分析函数。前者配合 OVER 使用时就是对“窗口内的行”做聚合后者配合 OVER 使用时只能读取窗口内的某些行但一般不会受到 ROWS/RANGE 的精细控制后面我会专门讲这一点。1.2 一个最容易被忽略的语法部件窗口子句窗口函数的标准语法长这样函数名() OVER ( [PARTITION BY 列1, 列2, ...] [ORDER BY 排序列 [ASC|DESC]] [窗口子句] )很多人在工作中只写前面两行比如SUM(amount) OVER(PARTITION BY store_id ORDER BY sale_date)觉得已经够了。其实这里还隐藏着一个“窗口子句”Frame Clause没写。窗口子句就是用来定义“窗口范围”的也就是我们这篇文章的主角ROWS 和 RANGE 所在的区域。窗口子句的完整格式是ROWS | RANGE BETWEEN 下边界 AND 上边界常见的边界写法有CURRENT ROW当前行UNBOUNDED PRECEDING分区第一行UNBOUNDED FOLLOWING分区最后一行n PRECEDING往前 n 行ROWS或 n 个单位值RANGEn FOLLOWING往后 n 行ROWS或 n 个单位值RANGE如果你只写了 ORDER BY但没有写窗口子句SQL 标准规定默认使用RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW也就是“从分区第一行到当前行的所有行按排序键值范围计算”。这就是为什么很多人写SUM(amount) OVER(ORDER BY date)想求累计结果遇上有重复日期时累计结果会突然“跳”一下因为默认的 RANGE 把所有相同日期的行看成了一个整体。反过来说如果没有写 ORDER BY那么整个分区就是默认窗口RANGE 和 ROWS 都没有意义因为窗口是“整个分区所有行”。这个细节很多人没注意等后面遇到坑再回来查就会很痛苦。这里还要提醒一句不同数据库对默认窗口的实现虽然大体一致但细节上可能有差异。所以最安全的写法是当你对窗口范围有明确要求时一定不要省略窗口子句把 ROWS/RANGE 写出来让执行计划跟着你的意图走。2. 窗口范围的两大主角ROWS 和 RANGE 到底有什么区别2.1 ROWS按“物理行数”圈地ROWS 是最直观的窗口定义方式它根据“当前行”在分区中的物理位置往前或往后数多少行作为窗口的边界。所谓物理位置就是结果集排序后每行所在的行号。比如ROWS BETWEEN 2 PRECEDING AND CURRENT ROW意思是“当前行往前数 2 行一直到当前行”一共最多 3 行。如果你对生活场景类比ROWS 相当于排队时你说“往前数三个人”。不管是高矮胖瘦、什么身份只要站在第几个位置就会被划进窗口。所以 ROWS 完全不关心排序字段的值是多少只关心这些行在排序后的物理位置。ROWS 的边界用数字来表达比如 1 PRECEDING、2 FOLLOWING。它要求 ORDER BY 后的排序结果稳定否则“位置”会随着排序键的微小变化而变化。但在同一个查询中只要你 ORDER BY 写清楚行号就是确定的。MySQL 8.0、PostgreSQL、SQL Server、Oracle 都支持这种写法。使用 ROWS 的典型场景是移动平均、滑动求和。比如要算每笔订单过去 3 笔订单的平均金额用ROWS BETWEEN 3 PRECEDING AND CURRENT ROW非常干净。因为你需要的就是“物理上前 3 行”而不是“某个值范围的若干行”。2.2 RANGE按“排序键值”圈地RANGE 就不一样了。它不是按行数数格子而是按“排序键的值范围”来决定窗口。什么叫值范围比如你按日期排序RANGE BETWEEN INTERVAL 1 DAY PRECEDING AND CURRENT ROW意思就是“从当前日期往前推 1 天到今天这个日期区间里的所有行”不管区间里有多少行哪怕只有一行或者有几百行都被包含进来。再举个例子按分数排序RANGE BETWEEN 5 PRECEDING AND CURRENT ROW意思是“分数在当前值减去 5 到当前值之间的所有行”。如果有两个学生都是 90 分那么在计算某个 85 分的学生的窗口时两个 90 分的学生不会进来因为 90 超过了 85但如果当前行是 90 分那么所有 85~90 之间的学生都会被算进来包括其他 90 分的学生。这就是 RANGE 和 ROWS 最核心的差异ROWS 是“物理行号连续”RANGE 是“逻辑值连续”。相同排序键的行在 RANGE 中会被当成一个整体要么同时进窗口要么同时出窗口中间不会被切开。所以当你看到默认窗口下累计求和遇到重复日期会一次性并入多行时就是因为底层是 RANGE。使用 RANGE 的典型场景是时间序列分析比如近 7 天销量、近 30 天用户活跃数、按价格区间累计。这类需求本质上和“值”有关和“行数”无关用 RANGE 才符合业务直觉。2.3 一张表把区别说透我整理了 ROWS 和 RANGE 的对比方便你以后快速查阅对比维度ROWSRANGE边界依据物理行号偏移排序键的值偏移是否关心排序键重复不关心按行独立处理关心相同键值的行会捆绑进出窗口支持 ORDER BY 列数基本无限制多数数据库只允许单列且需数值/日期/时间类型典型语法ROWS BETWEEN 2 PRECEDING AND CURRENT ROWRANGE BETWEEN INTERVAL 2 DAY PRECEDING AND CURRENT ROW适合场景移动平均、滑动N行近N天、按数值范围聚合默认行为需要显式声明很多数据库默认未写窗口子句时使用它性能开销相对轻对排序键值做范围判断可能更重这张表不是最终真理但能覆盖 90% 的实际场景。你只要记住看到 ROWS 想“行数”看到 RANGE 想“值域”后面再遇到问题就都能顺着这个思路排查。3. 图文详解用一份销售数据把 ROWS 和 RANGE 跑一遍3.1 准备一份测试数据空讲概念没有用我拿一份很简单的销售订单表来跑。表结构就三个字段一个日期、一个金额再加一个自增序号方便观察。CREATE TABLE sales ( id INT, order_date DATE, amount DECIMAL(10,2) ); INSERT INTO sales VALUES (1, 2024-01-01, 100.00), (2, 2024-01-01, 150.00), (3, 2024-01-02, 200.00), (4, 2024-01-04, 300.00), (5, 2024-01-05, 250.00), (6, 2024-01-05, 400.00);你注意到没有1月1日有两条记录1月5日也有两条。这种重复日期正是区分 ROWS 和 RANGE 最好的测试数据。如果你的业务表里日期连续、唯一ROWS 和 RANGE 的结果往往一样那你就永远发现不了区别遇到重复值真相一下子就浮出水面了。接下来我们分别用不同的窗口子句跑查询我会把结果显示成表格你自己也可以在本地数据库里复现。3.2 实战一累计求和默认窗口和 ROWS 窗口的差异先跑一个最常见的累计求和。为了让每行的物理位置看得清清楚楚我在 SELECT 里加了一列 ROW_NUMBER()它的作用是按 order_date 排序后给每行打一个 1、2、3 这样的序号。SELECT order_date, amount, ROW_NUMBER() OVER (ORDER BY order_date) AS rn, SUM(amount) OVER (ORDER BY order_date) AS default_sum, SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS rows_cum FROM sales;把这条查询在 MySQL 8.0 里跑完得到的结果是这样的我加上了 rn 列作为行号参考。order_dateamountrndefault_sumrows_cum2024-01-01100.001250.00100.002024-01-01150.002250.00250.002024-01-02200.003450.00450.002024-01-04300.004750.00750.002024-01-05250.0051400.001000.002024-01-05400.0061400.001400.00注意看第一行和第二行rn1 的那行 default_sum 为什么是 250因为它用了默认的 RANGE 窗口而 1月1日这个日期有两行RANGE 窗口会把相同日期的两行打包成一个整体。于是第一行计算时第二行虽然还没轮到但已经因为“日期键值相同”被拉进了窗口。第二行计算时还是这两行结果也是 250。你可以把 RANGE 理解成“按日期值分组看累计”相同日期必须同时处理。而 rows_cum 这一列用 ROWS 窗口严格按物理行号累计第一行只有自己所以是 100第二行才是 100150250。从第三行开始日期没有重复两列结果一致。到了第五、六行又是重复日期default_sum 在第五行直接跳到 1400因为它把第五、第六行两笔 250 和 400 合并进窗口了而 rows_cum 先到 1000再到 1400。如果你原本只想要“逐行累计”这里默认的 RANGE 结果会给你带来不少困扰。所以我的第一建议就是累计求和时如果想按物理行累计一定要显式写ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。3.3 实战二近两日统计ROWS 和 RANGE 结果差在哪接着看一个更像业务场景的需求我想统计“当前日期往前推 1 天含当天的销售额累计”。这种需求你用 ROWS 就不太对了因为我们缺失了 1月3日的数据物理上往前推 1 行并不代表“前一天”。我们对比一下SELECT order_date, amount, SUM(amount) OVER (ORDER BY order_date RANGE BETWEEN INTERVAL 1 DAY PRECEDING AND CURRENT ROW) AS range_2d, SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS rows_2row FROM sales;执行后得到这样的结果order_dateamountrange_2drows_2row2024-01-01100.00250.00100.002024-01-01150.00250.00250.002024-01-02200.00450.00350.002024-01-04300.00300.00500.002024-01-05250.00950.00550.002024-01-05400.00950.00650.00逐行看 range_2d 这一列第一、二行还是 250因为窗口日期区间是 2023-12-31 到 2024-01-01只有 1月1日的两行第三行是 1月2日窗口从 1月1日到 1月2日于是 100150200450第四行是 1月4日窗口从 1月3日到 1月4日1月3日没有数据所以只有 300第五、六行都是 1月5日窗口从 1月4日到 1月5日包含 300、250、400合计 950。再看 rows_2row 这列第三行是第二行150加第三行200所以是 350第四行是第三行200加第四行300所以是 500。它根本不管日期是否连续数的是物理行。这就是 RANGE 在时间窗口上的价值它能真正做到“按时间值累计”把缺失日期自动跳过去。如果你用 ROWS 去算“近N日”数据一旦缺几天结果就开始失真。所以移动平均用 ROWS时间衰减和近N日统计用 RANGE这是最朴素的选型规则。3.4 实战三RANGE 按数值区间筛选行ROWS 做不到除了日期RANGE 也经常用在数值列上。我用一个学生成绩表来演示这个需求是“统计当前分数往低 5 分范围内所有学生的总分”。CREATE TABLE scores ( id INT, score INT ); INSERT INTO scores VALUES (1, 80), (2, 85), (3, 90), (4, 95), (5, 88), (6, 82);查询如下SELECT id, score, SUM(score) OVER (ORDER BY score RANGE BETWEEN 5 PRECEDING AND CURRENT ROW) AS range_sum, SUM(score) OVER (ORDER BY score ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS rows_sum FROM scores ORDER BY score;跑出来的结果idscorerange_sumrows_sum6808080182162162585247167288173173390263178495185185看 score85 这一行的 range_sum247窗口是分数 80~85包含 80、82、85 三行808285247。而 rows_sum 只取“物理上一行 当前行”8285167。两者的差异非常明显。如果你是做类似“按价格带累计”的需求RANGE 能非常自然地把同一价格区的记录囊括进来ROWS 则需要先知道物理行数逻辑上绕了一个大弯。4. 窗口范围的实际应用场景4.1 移动平均ROWS 滑动窗口最拿手移动平均是 ROWS 最典型的应用场景。在日常监控里我们经常想平滑一下每天的波动比如算 3 日移动平均就会写SELECT trade_date, amount, AVG(amount) OVER (PARTITION BY store_id ORDER BY trade_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg_3 FROM daily_sales;这里用 ROWS 是因为“移动平均 N 日”实际上说的是“最近有记录的 N 行”。如果你用RANGE BETWEEN INTERVAL 2 DAY PRECEDING AND CURRENT ROW遇到某个门店某天没有开门窗口里可能只有 1 行或 2 行平均值的分母就会忽大忽小。业务上如果你要的是“最近三个营业日”的均值那 ROWS 就是对的。如果业务上要“自然日近 3 天”哪怕没数据也要算 0 或者不参与平均那才需要考虑 RANGE 或者先用日期维表补数。我见过不少人在移动平均场景里误用 RANGE结果发现缺失日期的窗口内行数不稳定均线抖动特别厉害。其实不是函数错了是“移动平均”这个词在业务里被说得太模糊了。写 SQL 前建议先和业务确认到底是“最近 N 次交易”还是“最近 N 天”。前者用 ROWS后者用 RANGE。4.2 时间窗口累计RANGE 专治“天数”统计和移动平均相反时间窗口累计的核心词是“自然日”最典型的是计算近 7 天销售额、近 30 天活跃用户。这类需求天然适合 RANGESELECT stat_date, SUM(amount) OVER (PARTITION BY store_id ORDER BY stat_date RANGE BETWEEN INTERVAL 6 DAY PRECEDING AND CURRENT ROW) AS sum_7d FROM daily_sales;这里RANGE BETWEEN INTERVAL 6 DAY PRECEDING AND CURRENT ROW意思就是“从当前日期往前推 6 天到今天”一共 7 个自然日。不管中间有没有缺数据它都会把落在该日期区间内的所有行都算进来。如果你用ROWS BETWEEN 6 PRECEDING AND CURRENT ROW当你日表缺了几天数据时窗口就会包含 7 条物理行可能横跨了 10 个自然日结果自然不对。当然RANGE 的 INTERVAL 语法在不同数据库里有细微差别。MySQL 8.0 支持INTERVAL 6 DAY PRECEDING但要求 ORDER BY 是日期/时间类型PostgreSQL 的语法类似SQL Server 不支持 INTERVAL它要用RANGE BETWEEN DATEADD(DAY, -6, stat_date) AND CURRENT ROW但 SQL Server 对 RANGE 的限制很多常常干脆不让你用这种写法。所以生产环境真要实现“近N天滑动汇总”有两条路一是用 RANGE 且能用 INTERVAL二是用自关联或日期维度表补全后再用 ROWS。我的经验是如果数据库给力RANGE 最简洁如果限制太多老老实实用自关联别跟数据库死磕。4.3 累计占比与分组排名窗口范围怎么配合还有一个常见场景是算帕累托图也就是按金额从大到小累计占比。比如每个商品类目里累计前 20% 的商品贡献了 80% 的销售额。这时候窗口函数可以这样写SUM(amount) OVER ( PARTITION BY category ORDER BY amount DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS cumulative_amount, SUM(amount) OVER (PARTITION BY category) AS total_amount这里用 ROWS 就很合适因为你关心的是“排好序后的物理前 N 行累计”不是“和当前金额同样大小的行一起累计”。如果你换成默认的 RANGE遇到相同金额的行很多时累计一下子会吞掉好几行会导致“前 20%”完全乱套。我踩过一次坑库存分析表里大量商品金额恰好是 99、199 这种常见价位RANGE 把相同价格全并进同一个窗口累计曲线直接断崖。后来改成 ROWS 按行累计才算正常。再说排名函数像 RANK、DENSE_RANK、ROW_NUMBER 其实并不受 ROWS/RANGE 的窗口范围控制。你可以在它们后面写OVER(ORDER BY score)但如果在函数里写窗口子句很多数据库会直接报错或忽略。这个概念容易混淆但你只要记住求“第几行”“第几名”这类需求不需要窗口范围窗口范围是给 SUM、AVG、COUNT 这类聚合型窗口函数用的。4.4 小心 LAG/LEAD 和窗口子句的配合边界LAG 和 LEAD 这两个函数用来访问分区中相对当前行偏移 N 行的值比如上一笔订单金额、下一个用户注册时间。它们本身是在“行偏移”的逻辑上工作的和 ROWS/RANGE 的窗口范围不是一个维度。绝大部分数据库里LAG(amount, 1)后面的 OVER 子句即使写了RANGE BETWEEN ...也会被忽略函数仍然按物理行偏移来取值。所以如果你想实现“跟上一条日期最近的数据比”不能指望 LAG 加 RANGE 自动跳过缺失日期。常见做法是先用窗口函数把日期错位或者用自关联取最近日期。这在实时数仓里算是一个高频摸坑点明明 LAG 用了 RANGE 子句结果数据一缺上一条就变成了“物理上一行”给业务方解释半天。如果你必须用“近N天内的上一笔金额”可以考虑先用 ROW_NUMBER 对每个自然日窗口编号再用 LAG 结合行号差做关联或者更粗暴一点用关联子查询取max(order_date) 当前日期的那一行。别一上来就把 LAG 和 RANGE 组合容易给自己挖坑。5. 那些年我在生产环境踩过的坑ROWS/RANGE 问题速查5.1 坑一默认窗口是 RANGE结果经常“多算”前面已经演示过不写窗口子句时默认 RANGE 在排序键重复时会一次框进多行。很多业务 SQL 里写SUM(amount) OVER(ORDER BY order_date)原本以为是逐行累计结果遇到同一天多笔订单第一行累计就变成了“当天所有订单之和”这在“当前行累计值”的意义上已经是错的。最稳妥的写法是显式写出窗口子句。我的规矩是开窗函数只要涉及 SUM/AVG/COUNT 且带 ORDER BY一律把 ROWS 或 RANGE 写完整绝不依赖默认值。短期看起来代码啰嗦长期维护真的能少一大半事故。5.2 坑二RANGE 只支持单列 ORDER BY 和特定数据类型RANGE 的偏移量是基于单个排序键的所以大多数数据库不允许在 RANGE 窗口下写ORDER BY 列1, 列2这种多列排序。比如 Oracle 的 RANGE 窗口如果 ORDER BY 是复合条件通常会报 ORA-00907 这类语法错误。而且边界里的n PRECEDING对于 RANGE 来说必须是数值单位、日期时间间隔或 INTERVAL不能随意写其他类型。如果你需求里就是要按两个字段切窗口比如“先按地区再按日期”其实应该用 PARTITION BY 把地区分到不同分区而不是在 ORDER BY 里堆列。5.3 坑三NULL 排序值让 RANGE 窗口变成一个“大锅”这个坑更隐蔽。ORDER BY 排序列如果存在 NULL不同数据库对 NULL 的排序位置处理不同MySQL 里 NULL 默认排在最前Oracle 里 NULL 默认排在最后。而 RANGE 窗口以排序键的值范围为依据遇到 NULL 时就会把整个分区里所有 NULL 行都拉进同一个窗口。比如你统计每个用户的累计消费但一部分用户的 create_time 是 NULL那么这些 NULL 行的“窗口”可能会共享所有 NULL 行导致累计值一次暴涨。我的解法是如果排序列有业务意义且可能为 NULL先COALESCE成一个默认值比如COALESCE(create_time, 1900-01-01)把 NULL 合理归到一个边界值上再开窗避免窗口边界被 NULL 污染。5.4 坑四RANGE 比 ROWS 慢大表慎用从计算原理上看ROWS 只需要知道每个分区的物理行偏移实现起来更像一个计数器RANGE 需要根据排序键的值来判断每行是否落在窗口区间往往要做额外的范围查找或排序。在几百万行的大表上跑这种差距会被放大。我在做离线数仓任务时遇到过同一份 2000 万行数据改成 ROWS 后执行时间从 5 分钟降到 40 秒。虽然影响指标还有很多但窗口类型绝对是一个重要变量。所以优化时有一条经验能用 ROWS 表达的窗口尽量用 ROWS如果业务强依赖值域窗口近N天先确认数据量和分区粒度必要时通过“补齐日期 物理窗口”的折中方案提升性能。总之别把 RANGE 当成默认选项除非你真的需要它。5.5 问题速查表症状可能原因解决方向累计值一次跳了很多默认 RANGE 把重复排序键的多行合并显式写ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW近N天统计结果偏小/偏大ROWS 按物理行数不是按自然天数改用RANGE BETWEEN INTERVAL ... DAY PRECEDINGRANGE 窗口语法报错排序键多列/类型不支持改为单列排序或先补日期/预处理窗口内出现大量 NULL 行ORDER BY 列有 NULLRANGE 把 NULL 值合并用 COALESCE 填充默认排序键查看执行计划发现扫了很多行RANGE 范围判断开销大尽量改成 ROWS或先缩小分区LAG/LEAD 结果和预期不符函数本质是物理行偏移别用 RANGE 限制改用自关联或扩展逻辑6. 调试窗口函数的独门技巧6.1 给每行标行号窗口边界一目了然我调试 ROWS/RANGE 时第一件事就是给结果集加一列ROW_NUMBER() OVER(ORDER BY 排序键)把物理位置可视化。然后在自己写的窗口函数旁边再临时加一列COUNT(*) OVER(窗口子句)看窗口内到底有几行。这样一旦结果不对就能快速判断是窗口框大了还是框小了不需要对着原始数据瞎猜。说到底ROWS 和 RANGE 的区别就是“行号区间”和“值区间”的区别只要能打印出行号和值一切都能对上。6.2 先用 CTE/临时表把数据缩到最小生产环境动辄几百万行直接在线上库调试窗口函数很危险也很慢。我的习惯是先写一个 CTE把数据过滤到只包含那些“能触发异常”的样本行比如重复日期、NULL、缺失日期。然后在样本上把多种窗口子句并列对比像前面 3.2、3.3 那样把 default_sum、rows_cum、range_2d 全部列出来一对比问题就能定位。别相信文档上的理论拿自己的数据跑一遍比什么都靠谱。6.3 把默认行为“显式化”生产 SQL 少踩雷以后写任何带 ORDER BY 的聚合型窗口函数都养成显式写全窗口子句的习惯。我自己的 SQL 模板一般是SUM(amount) OVER ( PARTITION BY store_id ORDER BY trade_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS cumulative_amount如果不想写也要心里清楚系统默认是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。这个显式化的过程就是逼自己确认“到底要按行还是按值”把业务语义说清楚再落成代码。最后再分享一个我踩过多次后总结的习惯任何带 ROWS/RANGE 的窗口查询上线前我都会把窗口边界打印出来逐行核对一遍。别嫌麻烦窗口函数的坑十个有九个都出在“我以为的窗口”和“实际算出来的窗口”不一致上。把这个核对流程养成肌肉记忆能帮你省下大量排查时间。
返回列表