1. 面试里的行转列与列转行,到底在考什么
SQL 面试里有两类题,面试官特别爱出,一个是窗口函数排榜,另一个就是行转列和列转行。我这些年面试过不少候选人,自己也经历过好几轮技术面,发现这两类题最容易把“背过答案”和“真正理解”的人区分开。如果你只是背了一条 PIVOT 语法,遇到数据稍微换个形态、多加一个维度,很容易当场卡壳。
行转列的本质,是把行上的某些取值变成新的列名,通常伴随着分组和聚合;列转行则是反过来,把多个列拆成多行记录。这两件事合在一起,是数据仓库、报表开发、数据分析岗几乎每天都会遇到的操作。面试官拿这种题出来,真不是想考你记性好不好,而是想看两件事:第一,你能不能根据目标表的形态判断出正确方向;第二,你能不能选对实现方式,并且说出为什么这么选。
这篇文章我把行转列、列转行的核心套路、常见坑、面试追问、动态列实现都拆开来讲。不管你是准备跳槽,还是平时写报表、搭数仓,把这些套路吃透,至少能少走很多弯路。文章里的 SQL 不会绑定某一种数据库,我会把通用写法和可选的高级写法都拿出来对比,方便你在笔试环境里自由切换。
1.1 一个面试题就能看出水平差距
我面试时常用一道很简单的题开场:“有一张订单明细表,有订单日期、地区、销售额三个字段,请把每个地区每个月的销售额,变成一行一个地区、月份作为列的结果。”这道题做出来的不少,但思路差距很大。
第一种候选人会直接开始写固定代码,假设只有三个月、三个地区,写出来的 SQL 能跑,但一旦月份增加,脚本就得重改。第二种候选人会先问一句:“月份是固定的还是动态的?每个地区每月可能有多条订单,还是已经聚合好了?”这一句话就能让我判断出,他是真的处理过业务数据,还是只在练习题里转过。面试官出题,往往期待的是你具备“先确认业务规则,再写代码”的思维,而不是上来就默写语法。
1.2 两种变换的本质区别
我习惯给它们各贴一个标签。行转列:行数变少、列数变多,通常伴随聚合。列转行:行数变多、列数变少,通常伴随拆解。当你拿到需求,先别急着想函数,先看目标表的形状,方向就自然清楚了。
看一个具体的成绩表例子。原始数据是长表:
| 学生 | 课程 | 分数 |
|---|---|---|
| 张三 | 语文 | 90 |
| 张三 | 数学 | 85 |
| 李四 | 语文 | 88 |
| 李四 | 数学 | 92 |
行转列后的目标是宽表:
| 学生 | 语文 | 数学 |
|---|---|---|
| 张三 | 90 | 85 |
| 李四 | 88 | 92 |
列转行就是把这个宽表重新还原成上面那张长表。这个例子面试里出现频率极高,因为它简单明白,还能顺势衍生出很多变体,比如加一个“学期”字段,比如要求转完之后再算总平均分,每一个变体都在考察你对数据模型的理解。
1.3 面试中最常见的三类业务场景
面试题不会凭空出,行转列和列转行背后的业务场景其实非常固定。我把高频场景归成三类。
第一类是学生成绩或员工绩效,把多个科目或考核项的名字作为列,一行展示一个人的完整结果。第二类是销售汇总,把月份、季度、区域等维度展开成列,便于横向对比趋势。第三类是订单或流水明细,把某些“类别字段”提到列上,比如支付方式、渠道来源、商品类型。
这三个场景对应一个共同需求:把明细数据组织成适合人读的宽表,再喂给报表工具或 Excel 分析。反过来,报表平台里用户选择了多列指标导出,后台要落库时又得把宽表转换成长表。理解了这一点,你就能明白为什么这组问题会长期出现在 SQL 面试里,它不是概念游戏,而是切切实实的生产力技能。
2. 行转列:从 CASE WHEN 到 PIVOT 的完整拆解
行转列的核心套路,是先把某一列里的取值“提升”为列名,再对剩下的数据做去重或聚合。为什么一定要聚合?因为行转列后行数变少,原来多行里的数据被压缩到一行,就必须用 MAX、SUM、AVG 这类函数决定取什么值。这一步最关键,也最容易出错。
先给一套通用写法,它适合绝大多数数据库,包括那些没有原生透视函数的开源数据库:
SELECT 学生, MAX(CASE WHEN 课程 = '语文' THEN 分数 END) AS 语文, MAX(CASE WHEN 课程 = '数学' THEN 分数 END) AS 数学 FROM 成绩表 GROUP BY 学生;这段 SQL 的逻辑分三步走。第一步,GROUP BY 学生,把同一个学生的多条成绩记录合并成一行。第二步,CASE WHEN 在每一行里判断课程名,如果匹配,就把分数带出来,不匹配就是空值。第三步,MAX 把同一学生同一课程的非空分数提取出来。为什么用 MAX 而不是 SUM?美团外卖用户经常点两份同样的菜,你不会把它们加起来,而是取其中一份。成绩的场景类似,同一个学生同一门课正常情况下只有一条记录,取最大值和取这条记录本身效果一致。
2.1 通用写法背后的三个关键点
第一个关键点是,CASE WHEN 必须放在聚合函数里面,而不是单独写。很多人第一次写会写成“SELECT CASE WHEN 课程 = '语文' THEN MAX(分数) END”,这样查出来的结果是错的,因为聚合和条件判断的处理顺序不对。正确理解是把聚合函数当作外壳,CASE WHEN 在壳里先把需要的值挑出来。
第二个关键点是,AS 后面的列别名,其实就是要生成的新列名。科目列表有多少个,就写多少个这样的片段。在笔试现场,手写三个五个科目没问题,但如果面到“几十个科目怎么办”,就是动态列的问题,我在后面专门用一整节讲。
第三个关键点是,这个写法对数据库没有任何特殊要求,标准 SQL 就能跑。如果面试官没有指定具体环境,这套解法永远是安全的起手式,比一上来写某数据库专有关键词稳妥得多。
2.2 MAX 还是 SUM:选错了就是事故
面试里我最常追问的问题是:“你这里为什么用 MAX?什么时候用 SUM?”这问题看着简单,但很多人答不到点子上。
MAX 适用于“每个分组内目标列唯一”的场景。成绩表就是这样,一个学生一门课最多一条成绩,MAX 只是把唯一值捞出来。SUM 适用于“每个分组内目标列有多条明细”的场景。比如销售表,一个月内会有成百上千条订单,行转列后要把它们汇总成一个总数,当然用 SUM。如果销售场景用 MAX,等于只取了一笔最大订单,数据直接失真。
我实际接数据需求时碰到过这么一回事:某公司的渠道投放日报,每天每个渠道都有多笔消耗记录,写行转列时有人用 MAX 把消耗取了出来,对外报数很好看,但后台对账怎么都对不上。排查了半天,就是聚合函数选错。所以面试时遇到这种题,我建议你主动问一句“同一分组下会有多行明细吗”,这一句就能展示出你的业务意识。
另外还有一种情况,目标值不是数字,比如要转出手机号、订单号这类字符串。这时候聚合函数只能选 MAX 或 MIN,因为它们对字符串同样适用,SUM 和 AVG 直接报错。但如果一个分组下有多条记录,MAX 取出来的并不是“任意一条”,而是字典序最大的那条,这就有隐患。面试时你如果能主动指出“当业务允许多条时,这种写法只能退而求其次,更严谨的是先排重”,往往就是加分项。
2.3 多字段行转列:一个分组转出多个指标
有时光转一个分数不够,还要把排名、及格状态一起放到同一行。这时候只要把 CASE WHEN 和聚合函数复制多份即可:
SELECT 学生, MAX(CASE WHEN 课程 = '语文' THEN 分数 END) AS 语文分数, MAX(CASE WHEN 课程 = '语文' THEN 排名 END) AS 语文排名, MAX(CASE WHEN 课程 = '数学' THEN 分数 END) AS 数学分数, MAX(CASE WHEN 课程 = '数学' THEN 排名 END) AS 数学排名 FROM 成绩表 GROUP BY 学生;这个写法在实际报表里非常常见,一次查出多个维度,避免反复联表。要注意的是,每个字段片段里的 CASE 条件相同,只是 THEN 后面跟的列名不同,写的时候容易复制粘贴后忘记改,导致 A 列转出来 B 列的数据。这类低级错误在笔试紧张时最容易出现,所以我写完 SQL 会习惯性扫一遍“THEN 后面的字段跟 AS 后面的别名是否对应”。
2.4 PIVOT 写法与适用边界
部分商业数据库提供了原生透视语法 PIVOT,写起来比 CASE WHEN 简洁。以支持该语法的数据库为例,同样需求可以写成这样:
SELECT 学生, 语文, 数学 FROM ( SELECT 学生, 课程, 分数 FROM 成绩表 ) AS 源表 PIVOT ( MAX(分数) FOR 课程 IN (语文, 数学) ) AS 透视表;PIVOT 的语法结构是:先把数据放进一个子查询,PIVOT 接着指定聚合函数、要变成列名的字段、以及列名清单。它看起来很爽,但有一个致命限制:IN 里面的列名必须是写死的,课程列表一变,代码就得跟着改。很多面试者写完 PIVOT 很得意,等面试官追问一句“如果课程有五百个怎么办”,气势就下去了。
所以我的建议是,笔试中如果明确写了“某商业数据库环境”,可以使用 PIVOT;如果没有指定,优先写 CASE WHEN 聚合版本。面试官要的不是花哨,而是稳和思路清楚。你可以两种都会,但表达时先说通用方案,再补充“如果数据库支持原生透视还可以这样写”,既展示了广度又体现了判断力。
3. 列转行:UNION ALL 与 UNPIVOT 的实操对比
列转行是行转列的逆向过程。面试题里常出现的场景,是把一张宽表还原成长表,方便继续做筛选、聚合或者联表。前面成绩宽表的学生一行有三列成绩,现在要把它变成一门课一行,这就是列转行。
我喜欢用“拆”这个字来形容它:把一行的多个列值拆出来,每拆一个就形成一条新记录。面试新人容易犯的错,是试图用逆向 PIVOT 的思路来解题,可是在不少数据库里没有 UNPIVOT,而且笔试环境未必允许用专有语法,这时候 UNION ALL 是最可靠的通用解。
3.1 UNION ALL 实现列转行的标准姿势
先看宽表结构:
| 学生 | 语文 | 数学 | 英语 |
|---|---|---|---|
| 张三 | 90 | 85 | 78 |
目标长表:
| 学生 | 课程 | 分数 |
|---|---|---|
| 张三 | 语文 | 90 |
| 张三 | 数学 | 85 |
| 张三 | 英语 | 78 |
标准写法是这样:
SELECT 学生, '语文' AS 课程, 语文 AS 分数 FROM 成绩宽表 UNION ALL SELECT 学生, '数学' AS 课程, 数学 AS 分数 FROM 成绩宽表 UNION ALL SELECT 学生, '英语' AS 课程, 英语 AS 分数 FROM 成绩宽表;这里有两个细节值得展开。第一,‘语文’这种字符串常量作用很关键,它作为新列的取值,标记每一行是从哪个原始列拆出来的。第二,我写的是 UNION ALL 而不是 UNION。UNION 会去重,如果原始数据里本身就有重复行,UNION 会在你无意识的情况下把数据弄丢;UNION ALL 保留所有行,除非你明确知道要去重,否则就用 ALL。
还有一点,三个 SELECT 子句的列数、列顺序、数据类型必须保持一致。第一个子句是 学生、字符串常量、语文分数,第二第三个子句也必须按同样顺序摆放。顺序错了,SQL 不一定会报错,但语义就乱了,比如把课程名和分数对调,结果会非常难以排查。
3.2 UNPIVOT 写法与局限性
如果面试环境支持原生 UNPIVOT,代码会简洁不少:
SELECT 学生, 课程, 分数 FROM 成绩宽表 UNPIVOT ( 分数 FOR 课程 IN (语文, 数学, 英语) ) AS 拆表;这条语句的意思是:把语文、数学、英语这三列拆成行,列名放进课程列,列值放进分数列。逻辑很直观,但它和 PIVOT 一样,列名清单是硬编码的。如果要拆的列不确定,UNPIVOT 同样需要动态 SQL 配合。
UNPIVOT 对 NULL 值的处理方式也容易踩坑。默认情况下它会连带生成 NULL 值的记录。如果一门课没有成绩,分解后的长表里就会出现一条 分数 为空的记录,后续 AVG、SUM 统计时很可能产生偏差。很多标准文档不会重点讲这个,实际业务中确实会被它坑到。
3.3 列转行后的二次聚合:一套组合拳
面试不会只让你拆到一半。常见问法是“拆完之后,再统计每个学生各科平均分”之类。这种组合题其实很简单,但考的是你能不能把两步拆清楚。
宽表列转行之后,先落到一张临时结果或子查询里,再继续 GROUP BY。比如:
SELECT 学生, AVG(分数) AS 平均分 FROM ( SELECT 学生, '语文' AS 课程, 语文 AS 分数 FROM 成绩宽表 UNION ALL SELECT 学生, '数学' AS 课程, 数学 AS 分数 FROM 成绩宽表 UNION ALL SELECT 学生, '英语' AS 课程, 英语 AS 分数 FROM 成绩宽表 ) t GROUP BY 学生;把列转行写在子查询里,外面继续聚合,这种分层逻辑是数据分析工作的常态:先改表结构,再做统计。面试时你能把“改结构和算指标”两件事拆开表达,就已经比只会背函数的人高一个段位了。如果你还想更进一步,可以在转行时主动过滤掉空值,比如 WHERE 分数 IS NOT NULL,这样后续统计口径更稳。
3.4 宽表转长表时的空值处理
列转行时遇到 NULL 怎么办?我的原则是:先问业务规则。如果一门课缺考,空值在宽表里可能是“没参加”,也可能是“数据没录入”,两者处理方式完全不同。观察和沟通能力本身就是面试考核的一部分。
代码处理上有两种常见做法。一种是用 COALESCE 把空值填成 0,适合“缺考按零分算”的口径。另一种是直接在拆分时排除空值行,适合“缺考不计入统计”的口径。两种写法分别是:
SELECT 学生, '语文' AS 课程, COALESCE(语文, 0) AS 分数 FROM 成绩宽表;SELECT 学生, '语文' AS 课程, 语文 AS 分数 FROM 成绩宽表 WHERE 语文 IS NOT NULL;面试时如果题目没有明确缺考规则,你可以主动向面试官确认,或者把两种口径都列出来,说明“取决于业务怎么定义”。这种回答本身就体现了一个合格数据工程师的基本素养。
4. 动态列场景:面试中的加分项
很多面试者能搞定固定列的行转列、列转行,但一旦遇上“列也会变”的需求,就不知道怎么办了。其实动态列的本质,是把“SQL 规范”和“数据本身”结合起来,让代码根据数据自动生成。能答好这一问的人,基本都是真正做过报表系统或数据宽表处理的。
动态列的价值在于,静态写的 CASE WHEN 或 PIVOT,只能在报表需求固定的情况下使用。现实中季度会变、课程会变、区域会变,每次都去改脚本,不仅效率低,更容易出错。用程序生成 SQL 才是长期维护的正解。
4.1 动态行转列的三个步骤
动态行转列,说白了就三步:先查出所有可能的列名,再把列名拼成 SQL 片段,最后执行拼好的 SQL。这个过程可以用一句话概括:拿列名、拼字符串、跑动态 SQL。
第一步,查出去重后的课程列表。第二步,用字符串拼接函数,把每个课程包装成一个 CASE WHEN 片段。第三步,把片段并入一个完整查询,交给数据库执行。整个过程并不神秘。
4.2 用 GROUP_CONCAT 生成 CASE WHEN 片段
以常见数据库为例,假设表结构还是成绩表,目标是动态生成“每个课程一列”。先获取拼接好的片段:
SELECT GROUP_CONCAT( CONCAT('MAX(CASE WHEN 课程 = ''', 课程, ''' THEN 分数 END) AS `', 课程, '`') ) INTO @cols FROM (SELECT DISTINCT 课程 FROM 成绩表) t;@cols 这个变量里装的就是类似“MAX(CASE WHEN 课程 = '语文' THEN 分数 END) AS语文”这样的长字符串。接着把它拼到主查询中:
SET @sql = CONCAT('SELECT 学生, ', @cols, ' FROM 成绩表 GROUP BY 学生;'); PREPARE stmt FROM @sql; EXECUTE stmt;这套代码中有一个容易漏掉的细节:CASE WHEN 里的课程名如果本身就包含单引号,直接拼接会导致 SQL 语法错误。实际项目里处理课程名、渠道名这类带特殊字符的文本时,需要提前做转义,这也正是“看着简单,落地踩坑”的地方。面试时你哪怕只是提到这一步,面试官都会知道你踩过真实的坑。
4.3 动态列转行的实现思路
列转行同样可以动态实现。思路是先拿到宽表中需要拆解的列名,再逐列生成 SELECT 片段,最后用 UNION ALL 连接起来。片段的样式就是前面说的:
SELECT 学生, '语文' AS 课程, 语文 AS 分数 FROM 成绩宽表生成时用同一套拼接逻辑。如果宽表的列名本身不固定,可以从系统元数据表里读取列名。用元数据自动发现列,是从“手工写死”走向“自动适配”的关键一步。
动态实现确实比静态代码复杂,但它对应的是真实的工程需求。面试官问“列一变怎么办”,你要能说出思路,不要求立刻默写出完整代码。把三步流程和转义问题讲清楚,就已经超过大部分候选人了。
4.4 面试答题节奏:先把结论再说实现
我建议答题时先说结果,再讲过程。比如:“动态列我会分三步:第一步查出全部科目列表,第二步拼 SQL 片段,第三步执行动态 SQL。这个方案还需要处理科目名转义、以及 NULL 值过滤两个细节。”这种节奏让面试官快速抓住你的思路,也给你自己争取了组织语言的时间。
不要一上来就埋头写 GROUP_CONCAT,写半天没有人知道你往哪个方向走。真正的高手在面试里,往往先画一个目标表结构,再反向拆解需要哪些步骤。这个习惯本身就是工程能力的体现。
5. 高频踩坑点与排查速查表
行转列和列转行出错,很少是语法问题,更多是数据假设没想清楚。我把这几年实际遇到的问题汇总成一个排查清单,面试前扫一眼,能避掉大部分雷。
5.1 坑一:MAX 掩盖重复数据
如果原始数据里同一个分组在目标列下存在多行,MAX 会把最大那条捞出来,其余的直接消失。这在“期望保留明细”的场景下会造成严重失真。正确做法是先用 ROW_NUMBER 或 DISTINCT 把数据粒度归一,再行转列。判断方式很简单:行转列前先 COUNT(*) 看一眼每个分组对应几行数据,锁定粒度和预期,代码才不会写歪。
5.2 坑二:聚合函数选型错误
目标列是数值且需要汇总时用 SUM;目标列只取唯一值时用 MAX 或 MIN;目标列是字符串时只能用 MAX 或 MIN。逢到“取最近一条”这类更复杂的规则,MAX 可能都不够,得用窗口函数先排序打行号。面试时多问一句数据的更新频率和是否唯一,可以避免掉进这个坑。
5.3 坑三:UNION 与 UNION ALL 混淆
列转行用 UNION 会把重复行合并,数据量不知不觉变少。除非业务上明确要去重,否则一律写 UNION ALL。尤其是列转行之后还要做聚合求和,少一行就少一笔数值。排查时把 UNION 换成 UNION ALL 再对比结果行数,立刻能发现问题。
5.4 坑四:NULL 值导致聚合结果失真
宽表转长表后出现空值,会导致 AVG 算出来的均值偏低、SUM 看起来没有问题但 COUNT 会有出入。遇到这种情况,先明确业务口径是“缺省按 0 计”还是“缺省不纳入统计”,再选择 COALESCE 填充或 WHERE 过滤。这也是列转行里容易被轻视、但真正影响报表结论的细节。
5.5 坑五:把字符串拆行当成列转行
面试官如果给你一个字段,里面用逗号分隔了多个标签,让你拆成多行,这不是列转行,而是字符串拆分。用错方法会绕远路。字符串拆行可以用递归查询或数字表配合 SUBSTRING 处理,也可以使用某些数据库自带的字符串拆分函数。面试时先确认题目类型再动手,能省不少时间。
5.6 排查速查表
| 症状 | 大概率原因 | 处理方向 |
|---|---|---|
| 行转列结果多出异常大值 | 用了 MAX 但分组内有多行明细 | 改成 SUM,或在行转列前先归一粒度 |
| 行转列时字符串列报错 | 对字符串用了聚合函数 | 改用 MAX/MIN,或先确认唯一性 |
| 列转行后行数变少 | UNION 去重了 | 换成 UNION ALL |
| 列转行后均值偏低 | NULL 被当成 0 或参与计数 | 明确口径,COALESCE 或 WHERE 过滤 |
| 列名一变脚本就废 | 列名硬编码 | 改造成动态拼接 SQL |
| 转置后结果都为空 | CASE WHEN 条件写反或列名拼错 | 抽查一行源数据,逐步拆 CASE 条件调试 |
这张表我建议你收藏。面试前翻一眼,能帮你快速定位问题。平时开发遇到转置类需求,找对应的行排查,比一行行读代码快得多。
6. 一套仿真面试题与参考答案
最后,给你一套我面试别人时经常用的完整题目。它把行转列、列转行、动态列三件事串了起来,正好覆盖这篇文章讲到的所有知识点。
6.1 题目描述
某公司有一张订单明细表,字段包括 order_id、product_id、region(地区)、sales_amount(销售额)、sale_date(日期)。要求:
- 按 region 列转行,转成每个 product_id 一行、不同 region 作为列名、值为销售额的结果;
- 把第一步得到的结果列转行,还原成 product_id、region、sales_amount 三列的长表;
- 如果 region 列表会随业务新增,写出行转列的动态实现思路。
这道题看起来很基础,但三个小问层层递进,能同时考察固定列处理、逆向处理和动态扩展能力。
6.2 参考答案思路
第一问,先用 GROUP BY product_id 和 CASE WHEN 聚合:
SELECT product_id, SUM(CASE WHEN region = '华北' THEN sales_amount END) AS 华北, SUM(CASE WHEN region = '华东' THEN sales_amount END) AS 华东, SUM(CASE WHEN region = '华南' THEN sales_amount END) AS 华南 FROM 订单明细表 GROUP BY product_id;这里用 SUM 而不 MAX,因为同一个 product_id 在同一个 region 下可能有很多订单,需要加总。第二问,用 UNION ALL 把上述结果拆回长表:
SELECT product_id, '华北' AS region, 华北 AS sales_amount FROM 结果表 UNION ALL SELECT product_id, '华东' AS region, 华东 AS sales_amount FROM 结果表 UNION ALL SELECT product_id, '华南' AS region, 华南 AS sales_amount FROM 结果表;第三问,讲动态方案。先说三步:先查 DISTINCT region 列表;再用字符串拼接函数把每个 region 包成一段“SUM(CASE WHEN region = '...' THEN sales_amount END) ASregion”的代码片段;最后拼成完整 SELECT 并执行。区域名转义一定要提,这部分答出来基本就是高分。
6.3 回答时的三个表达技巧
第一,先复述需求,再动手写代码。“您看我的理解对不对,第一问是要以 product_id 为行、region 为列,交叉处是销售额合计。”这个动作让双方对齐口径,还能避免答非所问。
第二,写完一段代码后顺便讲讲关键假设。比如“这边我按 销售额求和 来处理,因为同一个产品在同一个区域会有多笔订单。”主动暴露假设,说明你有判断力,也能引导面试官说出更准确的预期。
第三,动态列部分不要急着写完整代码,先画流程,再挑关键片段解释。面试官需要的是你的思路,完整代码太占时间,反而容易把亮点淹没在细节里。
6.4 这道题还能怎么追问
如果你答得顺利,面试官还可能接着问:订单明细表里有退货金额,怎么在同一个行转列结果里同时展示销售额和退货额?这对应的是多字段行转列,把两个指标分别套一层 SUM(CASE WHEN ...) 即可。也可能问:某产品在某区域没有任何订单,行转列后值是 NULL,怎么处理?这时候就回到空值处理,按报表口径填 0 或保留空值。这些问题万变不离其宗,核心还是对分组、聚合、NULL 语义的把握。
7. 实操复盘与我的体会
文章写到这,其实没有太多需要再总结的了。我更想分享的是自己实操里的一点感受:行转列和列转行这件事,看起来只是几个 SQL 关键词的排列组合,但真正做好,功夫全在 SQL 之外。你需要先理解业务口径,再判断数据粒度,然后才轮到写代码。我见过太多开发在细节上翻车,不是不会写,而是没想清楚“同一个学生能不能有两科成绩”这类基础问题。
我自己的习惯是,无论多简单的转置需求,都会先查一下源数据的粒度,随手跑一条带分组计数的检查查询。粒度要是和理解不一致,早发现早处理,省得报表上线后再返工。动态列也是如此,哪怕当前只固定三个区域,我也会评估一下未来扩展的可能性,给脚本留个好改的结构。
如果你正准备面试,建议找一张真实场景的宽表和长表,把这篇文章里的 CASE WHEN、UNION ALL、动态拼接各写一遍,再自己给自己出几道变体题。写 SQL 这种事,看十遍不如手打一遍。真正动手跑过,才能在面试时对答如流,也才能在开会时被人问住的时候,快速给出那个拆数据的正确思路。