窗口函数用row_number()加序号最常用,但需确保排序依据唯一;非唯一时应选rank()或dense_rank();必须写order by,且常需partition by分区+多字段排序;查当前叫号位置须用cte或子查询先打标再过滤;混用窗口与聚合函数时,count(*)需group by而avg() over可直接用;性能优化关键在分区/排序字段索引、避免null及全表窗口。

窗口函数怎么给排队数据加序号
直接用 ROW_NUMBER() 最常用,但得注意排序依据是否唯一。比如按叫号时间排,如果同一秒有多个取号,ROW_NUMBER() 会强制分出先后,而实际业务中可能该算并列——这时得换 RANK() 或 DENSE_RANK()。
常见错误是漏写 ORDER BY 子句,比如写成 ROW_NUMBER() OVER (),数据库会报错或返回不可靠结果。真实场景里,排序字段通常要组合:先按窗口类型(如“VIP通道”“普通通道”)分区,再按取号时间排序。
-
ROW_NUMBER() OVER (PARTITION BY channel ORDER BY create_time, id)—— 每个通道内严格编号,id是兜底去重字段 -
RANK() OVER (PARTITION BY channel ORDER BY score DESC)—— 用于按评分排队,同分同名次、跳号 - 避免用
GETDATE()或NOW()作排序依据,因为写入时时间精度可能一致,导致窗口函数结果不稳定
如何查“当前叫到几号”和“前面还有几人”
核心是把当前状态(比如窗口屏显示的号码)作为参数,反查队列位置。不能只查 WHERE number = 'A105',那样拿不到排队进度信息。
正确做法是用子查询或 CTE 先算出完整序号,再过滤。例如:当前叫号是 A105,想知它排第几位、前面剩几人,就得先对整个队列打标:
WITH ranked AS (
SELECT number,
ROW_NUMBER() OVER (ORDER BY create_time) AS seq
FROM queue_log
WHERE status = 'waiting'
)
SELECT seq AS current_position,
seq - 1 AS people_before
FROM ranked
WHERE number = 'A105';
注意:status = 'waiting' 必须加,否则已过号或已过期的记录会干扰序号;另外,如果系统支持“插队”(如老人优先),得在 ORDER BY 里显式加权重字段,比如 ORDER BY priority DESC, create_time。
窗口函数和 GROUP BY 混用时为什么总报错
典型错误是写成 SELECT name, COUNT(*), AVG(score) OVER (PARTITION BY dept) —— 这里 COUNT(*) 是聚合函数,没配 GROUP BY 就直接和窗口函数混用,MySQL 8.0+ 和 PostgreSQL 会拒绝执行,SQL Server 可能返回意外结果。
真正想实现“每个部门平均分 + 每个人在部门内的排名”,必须明确区分层级:
- 先用窗口函数算排名:
RANK() OVER (PARTITION BY dept ORDER BY score DESC) - 再用聚合算部门均值:
AVG(score) OVER (PARTITION BY dept)—— 这不是聚合函数,是窗口版均值,不触发 GROUP BY 要求 - 如果真要聚合(如统计每部门排队人数),就另起一层:
SELECT dept, COUNT(*) FROM queue_log GROUP BY dept,别硬塞进同一 SELECT
性能差、查不动?检查这三个地方
窗口函数本身不索引,大数据量下慢,不是语法问题,是执行计划问题。重点看三处:
- 分区字段(
PARTITION BY后的列)有没有索引——比如按channel分区,就要有(channel, create_time)联合索引 - 排序字段是否可 null——
create_time IS NULL的记录会被集中排在最前或最后,破坏局部性,拖慢排序 - 是否误用了全表窗口:
OVER ()没分区也没排序,等于对整张表排序,千万级表基本卡死;生产环境务必带PARTITION BY和确定的ORDER BY
复杂点在于:窗口函数的结果无法被索引复用,每次查询都得重算。如果“当前排队人数”这类指标访问频繁,建议用物化视图(PostgreSQL)或定时刷新的汇总表(MySQL)缓存,而不是每次都跑一遍 COUNT() OVER。











