服务端阅读 05月31日 23:58
SQLite 窗口函数怎么用?排名、累计和移动平均怎么写?
SQLite 窗口函数适合做“保留明细行的统计”。普通 GROUP BY 会把多行压成一行,而窗口函数会在每一行旁边补一个计算结果。比如每个员工仍然是一行,但旁边多出部门排名、部门平均工资、累计销售额。SQLite 从 3.25.0 开始支持窗口函数。它常用于报表、排行榜、增长率、移动平均、每组 Top N。只要你发现自己为了“既要明细又要汇总”写了很多自连接,窗口函数就值得考虑。它能把原本拆成多条 SQL 的报表逻辑收回到一条查询里,既减少应用层循环,也更容易让数据库统一排序和过滤。基本语法怎么看?窗口函数写在 OVER (...) 后面,里面通常有三件事:按什么分组、按什么排序、窗口范围有多大。SELECT department, name, salary, RANK() OVER ( PARTITION BY department ORDER BY salary DESC ) AS dept_rankFROM employees;PARTITION BY department 表示每个部门单独排名,ORDER BY salary DESC 表示按工资从高到低。它不会减少行数,所以很适合列表页和分析报表。ROWNUMBER、RANK、DENSERANK 怎么选?三个排名函数最容易混。ROW_NUMBER 不管分数是否相同,都给连续序号;RANK 遇到并列会跳号;DENSE_RANK 遇到并列不跳号。SELECT name, score, ROW_NUMBER() OVER (ORDER BY score DESC) AS row_no, RANK() OVER (ORDER BY score DESC) AS rank_no, DENSE_RANK() OVER (ORDER BY score DESC) AS dense_noFROM students;如果你要“取每个分类前 3 条记录”,通常用 ROW_NUMBER,因为它能保证每组最多 3 行。如果业务要求并列第三都算第三名,那就用 RANK 或 DENSE_RANK,但结果行数可能超过 3。LAG 和 LEAD 怎么算环比?LAG 取前一行,LEAD 取后一行。它们常用来计算增长额、增长率、状态变化。SELECT month, revenue, LAG(revenue) OVER (ORDER BY month) AS prev_revenue, ROUND( (revenue - LAG(revenue) OVER (ORDER BY month)) * 100.0 / NULLIF(LAG(revenue) OVER (ORDER BY month), 0), 2 ) AS growth_rateFROM monthly_revenue;这里用了 NULLIF(..., 0),否则上一期收入为 0 时会出现除零问题。第一行没有上一期,结果是 NULL,这是正常边界,不要随手填 0,除非业务明确要求。窗口帧决定了累计还是移动窗口帧用来控制当前行能看到哪些行。累计求和通常从第一行到当前行,移动平均则看当前行和前几行。SELECT date, sales, SUM(sales) OVER ( ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS running_total, AVG(sales) OVER ( ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ) AS moving_avg_3FROM sales_data;ROWS 按物理行数算,RANGE 按排序值范围算。多数日常报表用 ROWS 更直观,尤其日期可能缺天、金额可能重复时,RANGE 的结果容易和预期不一样。性能优化要从排序和分区下手窗口函数通常离不开排序,排序就是成本。优先给 PARTITION BY 和 ORDER BY 中的列设计复合索引。比如部门内按工资排名,可以考虑 (department, salary DESC)。CREATE INDEX idx_emp_dept_salary ON employees(department, salary DESC);但索引不是万能的。窗口函数如果要扫大量行,仍然会消耗内存和临时排序空间。报表类 SQL 可以先用 CTE 缩小数据范围,再做窗口计算;实时接口则要谨慎,别把复杂分析直接放在高并发路径上。追问窗口函数和 GROUP BY 最大区别是什么?GROUP BY 会改变结果粒度,把多行聚合成一行;窗口函数保留原始行,只是在每行旁边加计算结果。要看部门平均工资和员工明细,用窗口函数更自然。要只看每个部门一个汇总值,用 GROUP BY 更简单。踩坑点是把窗口函数当 GROUP BY 用,结果重复行很多,还以为数据错了。每组 Top N 应该用 RANK 还是 ROW_NUMBER?如果业务要求每组严格返回 N 条,用 ROW_NUMBER。如果并列名次都要保留,用 RANK 或 DENSE_RANK。取舍在于“行数稳定”还是“排名语义准确”。排行榜页面经常要保留并列,后台批处理取样通常更需要固定行数。为什么窗口函数里 ORDER BY 很重要?没有稳定排序,排名、前后行、累计值都可能不稳定。即使按日期排序,也要考虑同一天多条记录的情况,必要时加上主键做第二排序字段。边界是 LAG 和 LEAD:排序不唯一时,上一行到底是哪一行可能每次执行都不一样。生产 SQL 里不要依赖数据库“刚好按插入顺序返回”。移动平均为什么推荐 ROWS 而不是 RANGE?ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 明确表示当前行和前两行,共最多三行。RANGE 按排序值范围算,遇到重复日期、重复金额时会把同值行一起纳入,结果可能突然变大。取舍上,按最近 N 条记录算就用 ROWS,按最近 N 天或金额区间算才考虑 RANGE。很多报表误差就来自把这两个概念混在一起。窗口函数慢了怎么排查?先用 EXPLAIN QUERY PLAN 看是否出现大范围扫描和临时排序。再检查过滤条件能否提前放进 CTE 或子查询,减少参与窗口计算的行数。必要时给分区和排序列加复合索引,但别为了一个低频报表加太多索引拖慢写入。边界是超大数据分析,SQLite 可以做,但不一定应该在 App 或接口请求里实时做。