窗口函数不能直接行转列,需配合group by和case when:先用row_number()等打序号标签,再通过聚合将多行压为一行;pivot与窗口函数适用场景不同,不可混用。

窗口函数本身不能直接行转列,必须配合GROUP BY和CASE WHEN
窗口函数(如 ROW_NUMBER()、DENSE_RANK())只在现有行上生成计算值,不改变行数、不合并行、也不创建新列。所谓“用窗口函数实现行转列”,真实意思是:先用窗口函数标出每行在分组内的序号或排名,再用 GROUP BY + CASE WHEN + 聚合函数(如 MAX())把多行压成一行——窗口函数只是“打标签”,真正“压行”的是聚合逻辑。
- 常见误操作:写完
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at)就停了,没套外层GROUP BY,结果还是明细行,根本没转列 - 必须确保
PARTITION BY与最终GROUP BY的字段完全一致,否则序号分组错乱,CASE WHEN rn = 1可能匹配到错误分组的行 -
ORDER BY在窗口函数中必须明确(不能依赖默认),否则ROW_NUMBER()结果不可复现,尤其涉及NULL或相同排序键时
SQL Server中固定列数行转列的标准三步写法
以“每个用户前3笔订单时间转为 order_time_1 / order_time_2 / order_time_3”为例,这是最典型的可控场景:
- 第一步:子查询中用
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at)标序号,注意用created_at而非id,避免插入顺序干扰业务语义 - 第二步:外层
GROUP BY user_id,对每个用户聚合成一行 - 第三步:用
MAX(CASE WHEN rn = 1 THEN created_at END)提取对应值;MAX()是为了吞掉CASE对非匹配行返回的NULL,也可用MIN(),效果一致
SELECT user_id,
MAX(CASE WHEN rn = 1 THEN created_at END) AS order_time_1,
MAX(CASE WHEN rn = 2 THEN created_at END) AS order_time_2,
MAX(CASE WHEN rn = 3 THEN created_at END) AS order_time_3
FROM (
SELECT user_id, created_at,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS rn
FROM orders
) t
GROUP BY user_id;
别用 RANK() 或 ROW_NUMBER() 替代 DENSE_RANK() 做 Top N 行转列
当你要取“每个地区销量前3的产品名”这类带业务去重含义的需求时,序号函数选错会导致结果漏项或重复:
-
ROW_NUMBER():同销量产品强行编号不同 → 可能只取到一个第3名,漏掉并列者 -
RANK():同销量同名次但跳号 → “前3”实际可能返回4行(如两个第2名 + 一个第4名) -
DENSE_RANK():同销量同名次且不跳号 → 真正符合“前N个唯一排名”的业务预期
例如取各地区销量前3产品,必须用 DENSE_RANK() OVER (PARTITION BY region ORDER BY amount DESC),再配合 MAX(CASE WHEN drnk = 1 THEN product END),否则聚合后列值对不齐。
PIVOT 和窗口函数不是替代关系,而是适用场景完全不同
有人试图在 PIVOT 里嵌套窗口函数,这是无效的:PIVOT 的输入源必须是普通结果集,不能含未聚合的窗口表达式;而窗口函数又不能出现在 PIVOT 的 FOR 子句或 IN 列表中。
-
PIVOT适合类别值已知、数量稳定、且需硬编码列名的场景(如固定科目:Math/English/Science) - 窗口函数 +
CASE WHEN适合需要动态排序、Top N、或序号依赖业务逻辑(如“最早一笔”“最新三笔”)的场景 - 二者混用唯一合理方式:窗口函数在
PIVOT的子查询里预计算辅助字段(如加一列is_top3 = IIF(DENSE_RANK()... ),但 <code>PIVOT本身仍不处理序号逻辑
真正容易被忽略的是:窗口函数的执行时机早于 GROUP BY,但晚于 WHERE;如果你在子查询里过滤了数据,窗口函数只在过滤后结果上计算——这意味着,想取“每个用户最近3笔”,必须在窗口函数前就完成时间范围筛选,而不是指望 GROUP BY 后再裁剪。










