这些天在社群答疑时,接连有好几个朋友问到同一个问题:为什么我的数据在一列里塞了好几个值,想拆成一行一个,PowerQuery里的“拆分列”却总是变成拆成多列?或者反过来,想把一列按逗号劈开成几列,结果怎么弄都不对劲。其实这些都是 PowerQuery 里最基础也最容易绕晕的两个操作——按列分行和按列分列。很多人一上来就点界面按钮,却搞不清背后到底是“行方向扩展”还是“列方向扩展”,导致一顿操作猛如虎,结果表结构全乱。
今天这篇就专门把这两个操作掰开揉碎。我会从操作路径、界面按钮、M函数写法、常见坑点几个角度都过一遍,保证你看完能彻底分清:什么场景该“分行”,什么场景该“分列”,以及为什么有些人做出来的结果是错的。
这篇内容没有太高门槛,只要你会打开 Power BI Desktop、能进 Power Query 编辑器,就能跟着做。我尽量用实际工作中常见的脏数据来演示,不搞那些教科书式的完美样例。
1. 先分清你到底要“分列”还是“分行”
很多新手一上来就问“怎么拆分列”,但实际上他想要的效果是把一列里的多个值变成多行。这两个操作在英语里对应的是Split Column into Rows和Split Column into Columns,中文界面里分别叫拆分成行和拆分成列。名字听起来像双胞胎,实际处理逻辑完全不同。
1.1 按列分行的真实场景
“按列分行”通俗点说:你想把某一列单元格里的多个内容,往下扩展成多条记录。举个例子,订单表里有一个“商品标签”列,内容可能是"促销,新品,热卖",同一行还有其他订单信息。如果你希望每个标签单独占一行,同时其他列(订单号、日期、金额)跟着复制下来,那你要的就是按列分行。
这种需求在哪些行业最常见?我做过的项目里至少有这些:
- 电商后台导出的订单表,一个订单买了多个商品,商品名挤在一个单元格里。
- 用户画像表里,一个用户的兴趣爱好是
"篮球,游泳,阅读",想拆开做关联分析。 - 权限表里,一个角色挂了多个菜单 ID,需要展开成明细行。
- 表单系统导出的多选字段,比如“您关注的内容”选了 5 项,全部堆在一格里。
这类数据如果不展开成行,后续做透视、做计数、做去重分析基本都废了。我见过有人硬生生把这种数据放到 Excel 里用Ctrl+H手动处理到半夜,那真的没必要。
1.2 按列分列的真实场景
“按列分列”则是把一列内容向右拆成多个独立的列。典型情况是:原始数据里有一列叫“收货地址”,内容是"广东省广州市天河区体育西路 123 号",你想把它拆成省、市、区三个字段,那就要按列分列。
更常见的还有:
- 日期时间列
"2025-03-20 14:30:00",想拆成日期列和时间列。 - 姓名列
"张三-男-28岁",想拆成多个属性列。 - 从后台导出的经纬度
"113.2644, 23.1291",拆成经度和纬度两列。 - 全角逗号或空格分隔的复合编码
"A001 A002 B003"。
分列的本质是把“宽字段”变成“多个窄字段”,是典型的字段拆分,行数不变,只是列数变多了。
1.3 一张表讲清楚两者的区别
| 对比维度 | 按列分行(拆分成行) | 按列分列(拆分成列) |
|---|---|---|
| 数据流方向 | 纵向扩展,行数变多 | 横向扩展,列数变多 |
| 其他列的处理 | 值会自动重复填充 | 保持不变 |
| 每个单元格拆分结果落点 | 新生成的行里 | 新生成的列里 |
| 最常见入口 | 拆分列→按分隔符→高级选项→拆分成行 | 拆分列→按分隔符(默认拆分成列) |
| 底层 M 函数 | Table.ExpandListColumn/List.ExpandListColumn | Table.SplitColumn |
| 典型应用 | 一对多关系还原、标签打散 | 复合字段拆分、地址结构化 |
提示:在 Power Query 中文界面里,“拆分列”按钮底下默认是“按分隔符”,弹出来的窗口下方有一个“拆分成”选项,下拉里可以选“拆分成列”或“拆分成行”。很多人没注意这个选项,默认拆成了列,然后越看越不对劲。
2. 实操一:按列分行,把单元格里的多个值展开成多行
这一节我用一个真实工作里遇到的表来做演示。假设你有一个产品评分明细表,每个产品对应了几个不同的评分维度,但导出的时候所有维度值被合并到了一列里。
2.1 界面操作步骤
第一步,选中需要拆分的列。点列头选中“评分项”这一列。注意:一定是要拆哪一列就先选中哪一列,你在“添加列”菜单里做和“转换”菜单里做,效果是完全不同的。
第二步,找到“拆分列”按钮。它通常在“转换”选项卡下面,点“拆分列→按分隔符”。
第三步,选择或输入分隔符。如果数据里是中文逗号,就选“自定义”,输入,;如果英文逗号,可以直接选“逗号”。有一点非常关键:选错分隔符不会报错,但拆出来的结果会是一整块,或者变成奇怪的列结构。
第四步,最关键的一步——在“拆分成”选项里,把默认的“拆分成列”改成“拆分成行”。很多做错的人就是栽在这一步。
第五步,点确定,看结果。你会发现行数变成了原来的好几倍,其他列(比如产品名称、评分)都被自动往下填充了。这就是标准的分行效果。
2.2 底层 M 函数拆解
做完界面操作,Power Query 会自动生成 M 代码。如果你点开“高级编辑器”,能看到类似这样的代码:
let 源 = Excel.Workbook(File.Contents("C:\评分数据.xlsx"), null, true), 评分表_Sheet = 源{[Item="评分表",Kind="Sheet"]}[Data], 提升的表头 = Table.PromoteHeaders(评分表_Sheet, [PromoteAllScalars=true]), 已拆分评分项 = Table.ExpandListColumn( Table.TransformColumns(提升的表头, {{"评分项", Splitter.SplitTextByDelimiter(",", QuoteStyle.Csv), let 项类型 = (type nullable text) meta [Serialized.Text = true] in type table [评分项=项类型]}}), "评分项" ) in 已拆分评分项看着有点复杂对不对?但其实核心就两个动作:
Splitter.SplitTextByDelimiter负责把“评分项”这一列里的文本,按逗号切割成一个列表。Table.ExpandListColumn负责把列表里的每个元素展开成独立的一行。
你可以把Splitter理解成一把剪刀,先把绳子剪成一段一段的;ExpandListColumn则是一台分发机,把每一段分别放到一个盘子里。两兄弟配合,才能完成“分行”。
如果你在“添加列”选项卡里操作,生成的代码会略有不同,会用到Table.AddColumn加一个“列表”列,然后再展开。比如:
已添加自定义 = Table.AddColumn(提升的表头, "评分项拆分", each Text.Split([评分项], ",")), 已展开评分项拆分 = Table.ExpandListColumn(已添加自定义, "评分项拆分")这种写法其实是先生成列表列,再展开列表,逻辑更显性。对于初学者,我建议用这种方式理解,等熟练了再用一步到位的写法。
2.3 多个分隔符怎么办
实际数据里经常有脏数据,同一个字段里一会儿用逗号,一会儿用顿号,还有换行符。比如"红色、蓝色,绿色\n白色"。这时候先别急着拆,可以在“拆分列”窗口的“自定义分隔符”中输入一个固定字符吗?不行,它只能按一个分隔符拆。
解决办法是先做字符替换,把各种分隔符统一成一种。比如先把、和\n(换行符)都替换成英文逗号,再按逗号拆分。
更高级的做法是直接用 M 函数写正则式拆分。Power Query 本身没有现成的正则函数,但可以用Splitter.SplitTextByRegex配合自定义函数。不过那对新手来说略复杂,日常工作中我更推荐“先替换、再拆分”两步走:
替换分隔符 = Table.ReplaceValue(提升的表头, "、", ",", Replacer.ReplaceText, {"评分项"}), 再替换换行 = Table.ReplaceValue(替换分隔符, "#(lf)", ",", Replacer.ReplaceText, {"评分项"})2.4 分行以后常见连带问题
展开成行之后,你会发现一个很实际的问题:展开后的新列,类型变成了 Text。哪怕原来的列是数字,展开后也变成文本了。这是因为拆分本身就是字符串切割操作。所以拆完之后,你需要重新把列类型改回去,再继续后面的处理。
另外,如果原始数据里某些单元格是空的,或者没有分隔符,那么拆分出来的结果可能是一个单元素列表,展开后就是它自己,不影响行数。但如果单元格真的为空,拆分后可能会产生空行或 null 值,最好在展开之后用“筛选行”把空值去掉,或者用“替换值”把 null 替换成有意义的占位值。
3. 实操二:按列分列,把复合字段拆成多列
分列的操作比分行更常见,很多从 CRM、ERP、财务系统导出来的报表都带这种“复合字段”。下面我用一个最常见的“省市区地址拆分”来演示。
3.1 界面操作步骤
假设原始数据长这样:
| 客户编号 | 收货地址 |
|---|---|
| A001 | 广东省广州市天河区体育西路123号 |
| A002 | 浙江省杭州市西湖区文三路456号 |
| A003 | 江苏省南京市鼓楼区中山北路789号 |
业务部门的需求是:把“省”、“市”、“区”分别拆出来做区域分析。
第一步,选中“收货地址”列。
第二步,点击“拆分列→按分隔符”。
第三步,在弹窗中选择分隔符。这里要看数据本身的规律——地址字段里有用空格分隔的,也有直接拼接的。如果用空格分隔,选“空格”即可;如果数据是"广东省广州市天河区"这样没有分隔符的,那就没法直接按分隔符拆,只能按“从非数字到数字”之类的规则拆,或者用文本截取函数。
我遇到的多数导出数据,省市区后面都会跟着一段街道详情,而省市区之间常常是靠固定字符分隔的(比如空格或-)。我们假设数据是"上海市-浦东新区-张江路88号",那分隔符就是-。
第四步,确认“拆分成”选项是默认的“拆分成列”。
第五步,点确定。这时 Power Query 会根据分隔符把字段拆成多个列,列名默认叫“收货地址.1”、“收货地址.2”……然后你可以右键重命名成“省”、“市”、“区”。
3.2 只看最后一部分的处理技巧
有时候你不需要拆所有的段,只想要最后一段。比如地址中的“门牌号”。这种需求用拆分再删除列也能做,但效率低。推荐直接在“拆分列”窗口里选“拆分成列”,然后下面有个“高级选项”区域,里面有“拆分为”次数设置。
再提供一种思路:从右往左截取。比如字段规律是“最后一个-之后就是门牌号”,那么可以用Text.AfterDelimiter这个函数:
门牌号 = Table.AddColumn(上一步, "门牌号", each Text.AfterDelimiter([收货地址], "-", 1))这里第二个参数是分隔符,第三个参数1表示找到最后一个分隔符之后的内容。如果找不到分隔符,会返回 null,需要注意。
3.3 底层 M 函数拆解
分列的底层 M 函数是Table.SplitColumn,它会把一列拆成多列。界面操作生成的代码大概长这样:
已拆分列 = Table.SplitColumn(提升的表头, "收货地址", Splitter.SplitTextByDelimiter("-", QuoteStyle.Csv), {"收货地址.1", "收货地址.2", "收货地址.3"})也就是说,Power Query 把“收货地址”这一列按-切开,分别放入收货地址.1、收货地址.2、收货地址.3三个新列。这是非常标准的横向拆分。
如果你在追加列里用Text.Split,也可以实现同样的效果:
已添加自定义 = Table.AddColumn(提升的表头, "地址拆分", each Text.Split([收货地址], "-")), 已提取值 = Table.TransformColumns(已添加自定义, {{"地址拆分", each Text.Combine(List.Transform(_, Text.From), "|"), type text}})不过这种写法是在一个单元格里生成一个列表,还需要配合“提取值”或“扩展到新列”来使用,比较绕。日常我还是推荐直接Table.SplitColumn。
3.4 分隔符不统一时的处理
地址数据是最容易“脏”的。有的行是"省-市-区",有的行是"省 市 区",还有的干脆缺了“市”这一层(比如直辖市)。如果直接拆分,结果行有 2 列、有 3 列、有 4 列,非常难看。
我的建议是:先做标准化,再做拆分。把所有的分隔符统一替换成同一种,缺失的部分用占位符补齐。比如直辖市地址,可以在“市”的位置填一个“本市”,这样拆分的列数就稳定了。
还有一种偷懒但实用的办法:先用按列分列拆出前两段,剩下的“区”和“街道”放一起,再对剩下的那一列做第二次拆分。分步拆比一步到位更容易排查问题,特别是数据量大、规则杂的时候,我强烈建议不要试图一步到位。
4. 进阶场景:多列同时拆分和动态列数
新手做熟了单列拆分,会觉得一切尽在掌握。但实际业务里总会出现一些想骂人的需求,比如“这几列都要拆,而且每行拆出来的数量还不一样”。
4.1 两列同时拆分成行
假设一个课程选报表,长这样:
| 学生姓名 | 选修课程 | 兴趣爱好 |
|---|---|---|
| 小明 | 数学,语文 | 篮球,游泳 |
| 小红 | 英语,物理,化学 | 阅读,音乐,绘画 |
现在你想把“选修课程”和“兴趣爱好”都拆分成行。如果你先后拆两次,结果会变成笛卡尔积——每个课程会和每个兴趣配对一次,数据量爆炸。这个其实不一定是错,得看你业务上是否真的需要“每个课程配每个兴趣”的组合。多数情况下,这种“两个字段同步拆分”的需求是希望课程和兴趣按位置一一配对,而不是交叉组合。
要实现一一配对,M 函数里可以这样写:把两列都拆成列表,然后按位置合并列表,最后展开。核心思路是基于“索引号”的配对——把两列分别拆成列表,再把两个列表按位置组合成一个个记录,最后展开成行。
说句大实话,这种需求我在真实项目里遇到得很少。绝大多数业务的正确逻辑就是交叉组合,或者只需要拆一列。你要是真遇上了一一配对的需求,优先检查一下数据来源,看看能不能从源系统直接导出成明细行,硬用 Power Query 处理会非常折腾。
4.2 动态列数的分列
再看一个有意思的需求。数据源里有一个“属性”列,内容是"颜色:红色,尺寸:L,材质:棉"。你想把它拆成“颜色”、“尺寸”、“材质”三个字段。问题是不同行里可能属性的数量不一样,有些行只有颜色和尺寸,有些行多一个“品牌”。
这种“键值对转宽表”的需求,单纯用拆分列是不够的。常规做法分三步:
第一步,按逗号把每个属性对拆成行。 第二步,按分号或冒号把“键”和“值”拆成两列。 第三步,透视列——用“键”做列名,“值”做值。
这套操作在现代 Power Query 里用“透视列”功能非常顺畅。前提是你要先把行拆好,把键值分离。只要键值对格式够规整(每段都是键:值结构),这套方案几乎不会出问题。
如果键值对里有些值是空白的,透视之后会出现大量 null 列,记得在透视之前筛选掉空值,或者透视之后用“替换值”把 null 替换成“无”。
4.3 慎重使用“拆分到行”时的性能问题
这是一个性能话题。在 10 万行的订单表上,把商品列表展开成行,行数可能会膨胀到 30 万、50 万甚至更多。Power Query 里这一步是运行在内存中的,数据量一大,刷新会明显变慢。
我踩过的一个典型坑是:把一张 8 万行的销售表按“商品标签”展开成行,一下子变成了 60 多万行,Power BI 模型刷新时间从 30 秒涨到 6 分钟,而且生成的报表视觉对象点起来也有点卡。
如果你的数据量很大,我的建议是:
- 尽量在前置数据库层面就做拆分(SQL 里有
STRING_SPLIT或LATERAL VIEW EXPLODE),不要等到 Power Query 里再做。 - 如果必须在 Power Query 做,那就在展开之前过滤掉不需要的行,减少膨胀倍数。
- 展开之后马上删除中间过程产生的辅助列,减小模型体积。
- 不要在 Power Query 里保留展开前的大宽表,做完即删。
顺便说一句,很多人以为把“拆分列→拆分成行”改成“拆分成列”就能避免性能问题,那是错觉。分列后列数变多,同样会拖慢刷新速度,只是列的膨胀不会让行数翻倍而已。
5. 常见问题与排错:自己踩过的坑
这一节我专门记录几类高频报错和异常结果。这些坑每一个都有人专门在群里问过,每次回答几乎都是一样的排查路径。
5.1 拆分后多出一堆空列
现象:分列之后,表的最右边多了很多列名为“收货地址.4”“收货地址.5”的列,全是 null。
原因:Power Query 按分隔符分列时,默认的拆分模式是“每次出现分隔符时拆分”,如果你没有限制拆分次数,而某些行的分隔符比其他行多,多出来的片段就会各自成列。比如一条地址里有三个-,另一条里只有两个-,实际列数会按最大的那个值来生成,于是少的那些行就会出现空列。
解决:在“拆分列”窗口的“高级选项”里把“拆分为”设置为固定值,比如 3。或者干脆拆完之后用“选择列”删掉不需要的尾列。我日常使用中更推荐后者,因为手选删除不会误伤数据。
5.2 拆分行后行数对不上
现象:你觉得自己明明按“拆分成行”操作了,结果行数没变多,或者变多了但明显超过预期。
原因:最常见的是你选错了列。比如你要拆的是“商品标签”,却选中了“订单编号”列。另一种情况是分隔符写错了:如果分词符是中文逗号但你填了英文逗号,整个字符串不会被切开,Power Query 会把它当作一个单元素列表,行数自然不变。
排查:先看预览窗口。分列/分行操作在弹窗下方会有实时预览,你可以在点确认之前先观察拆分的瞬间效果。这是最有效的排错手段——比任何事后检查都快。
5.3 展开列表时提示“无法展开”
现象:写 M 函数时,你用了Table.ExpandListColumn,但报错说无法找到列名,或者数据类型不对。
原因:Table.ExpandListColumn要求被展开的列必须是“列表类型”。如果你手工添加了一列,但这个列不是列表而是文本,那它当然动不了。展开之前可以用Table.TransformColumns先把文本转成列表,或者在添加自定义列时用Text.Split生成明确的列表。
解决:添加列时确保写的是Text.Split([列名], "分隔符"),而不是直接引用文本。我经常看到有人写成each [列名],忘了加Text.Split,结果新列只是原值的拷贝,展开必然失败。
5.4 刷新时报错“找不到分隔符”
现象:第一次操作时数据里有逗号,拆分成功了。但刷新数据时,某些新数据行的分隔符变了(比如变成了中文逗号),Power Query 找不到对应分隔符,直接报错。
原因:拆分操作是按“行”计算的,当你指定的分隔符在某一行不存在时,Power Query 的默认行为是对那一行做整段保留,不一定会报错。真正会报错的是你用了自定义函数强制要求分隔符存在,比如Text.AfterDelimiter里指定missingField: Text.MissingField.Error。
解决:尽量用界面操作的默认容错能力;写 M 函数时给Text.AfterDelimiter的第三个参数选择Text.MissingField.UseNull而不是Error,出错面会小很多。
5.5 拆分后列类型全乱了
现象:明明“数量”列是整数,拆分行之后,整列变成了文本。
原因:Power Query 的拆分操作默认生成的列类型是文本,它不会去推测“数字字符串是不是数字”。这是正常的,因为文本切割的结果天然是字符串。
解决:拆分完成后,在“转换”选项卡里用“检测数据类型”重新推断一次,或者手动选中列改成整数。我一般在拆分之后会整体做一次“检测数据类型”,省得一条条改。
6. 一些我这些年沉淀下来的小心得
做 Power Query 拆分的活儿做了这么多年,有几个小习惯算是刻在肌肉记忆里了,分享出来供你参考。
第一,凡是涉及拆分的操作,永远先复制一份原始列再动手。这不是多余,而是给你自己留退路。Power Query 的每一步操作都像流水线上的工序,你可以在“应用的步骤”里随时回退,但有些细微的误操作(比如类型变了、列删了)会污染后续步骤。保留原始列,相当于给你的数据清洗买了一份保险。
第二,分隔符统一永远优先于一次性拆分。我见过太多人纠结“怎么用一个函数拆多种分隔符”,其实思路就应该是先替换再拆分。把中文逗号、顿号、空格、全角逗号都替换成一种半角逗号,后面一切好说。这个思路不仅适用于 Power Query,在任何数据处理工具里都适用。
第三,能用界面按钮搞定的,别为了炫技去写复杂 M 函数。M 语言支持正则、支持自定义函数、支持各种高级拆分模式,但这些对最终报表使用者毫无意义。你写了一个炫酷但没人能维护的 M 函数,三个月后你自己回来看都想不起来当初为什么这么写。界面按钮能完成 90% 的需求,剩下的 10% 再考虑写函数。
第四,先做小样,再跑全量。特别是面对几万行以上的数据,千万不要在完整数据上反复试错。取前 100 行做测试,把拆分逻辑完全确认无误之后,再改数据源范围跑全量。Power Query 的每一步操作在底层都是“按需计算”,但在大表上反复撤销和重做,刷新体验依然很糟糕,还会让你误判到底是什么步骤慢。
第五,拆分后马上检查行数和列数。我习惯在一个拆分步骤后面立刻加一个“统计行数”的中间步骤,或者直接在“应用到步骤”里留意查询的预览行数。如果行数变化和预期不符,当场就发现问题,不要等到建模完才发现。
这些习惯看着简单,但每一条都是从真实项目的加班和返工里磨出来的。Power Query 拆分功能本身不复杂,真正复杂的是数据永远比你想象的更脏,业务逻辑永远比你理解的更绕。把这两个基础操作练熟了,后面遇到任何“一对多展开”“宽表转窄表”的需求,你会发现思路都大同小异。
最后再多说一句:如果你用的数据源是 Excel 表,注意把原始表转成“表”格式(Ctrl+T),再加载进 Power Query。不然新增数据后刷新,区域范围不会自动扩展,你会一头雾水地看着拆分结果老是缺行。这个小问题看着低级,碰上一次就知道多耽误时间了。