做BI项目越久越会发现,真正卡住报表进度的往往不是DAX写不出来,而是数据清洗阶段那堆“该留哪行、该删哪列”的琐碎问题。PowerBI里的数据保留与删除操作,说起来就是Power Query编辑器里那几个按钮,可一旦搞混它们的排列逻辑和底层M函数,轻则报表数据对不上,重则刷出几十万行垃圾数据拖垮性能。这篇专栏把行级、列级、去重、错误处理以及服务端的数据保留策略一次讲透,适合刚接触Power Query的新手,也适合做了几年报表但没系统梳理过清理逻辑的同行。
1. 整体思路与方案选型:先搞清楚“做减法”的两个方向
1.1 为什么数据清理阶段要先做“减法”
数据进到PowerBI之前,源头表通常又脏又宽:Excel里顶着两三行标题说明、末尾挂着合计行、中间夹着大量空行,字段里还有“#REF!”之类的错误值。这些脏数据如果不提前处理,后面做度量值和可视化时,所有聚合结果都会被干扰。比如我遇到过某销售明细表里有几百行“小计”“总计”文本行,直接按金额求和时数据翻了好几倍,最后排查了大半天才发现是清洗环节漏了处理。
“先做减法”不是一句口号,而是BI开发的标准前置动作。它可以分成两类诉求:一类是明确要剔除的对象,比如删掉某几行、某几列、某种错误值;另一类是明确要保留的范围,比如只保留最近一年的订单、只保留核心字段、只保留出现多次的重复记录。这两类诉求对应Power Query里两个方向相反的工具组:删除类和保留类。
用生活化的方式理解:清理数据类似装修前的拆旧改造。你得先确定这间房哪些墙要拆、哪些墙要留,拆错了一面承重墙房子就塌了,留错了一堆杂物后面改造全是障碍。Power Query把“删除行”和“减少行”分成两个下拉按钮,目的就是逼着你先想清楚是“从全部数据里排除掉某些部分”,还是“从全部数据里只圈定某些部分”。虽然最终结果都是行数变少,但思考路径完全不同。
1.2 保留类操作和删除类操作的设计差异
Power Query的界面设计很有意思,它把“保留”相关操作放在“减少行”按钮下,把“删除”相关操作放在“删除行”按钮下。“减少行”下面有保留顶部行、保留底部行、保留间隔行、保留重复项;“删除行”下面有删除顶部行、删除底部行、删除间隔行、删除重复项、删除错误。
乍看这两组按钮是镜像对称的,实际操作中却有本质区别。保留类操作是白名单思维,适合你已经确定“只要这部分数据”的场景。比如业务方明确说只需要最近三个月的流水,那直接“保留底部N行”或按日期筛选,干净利落。删除类操作是黑名单思维,适合“除了某些问题数据,其余都要”的场景。比如报表里偶尔出现几行测试数据,你直接按条件筛选排除就行。
我个人的选型习惯是:能按条件筛选的尽量用筛选行,而不是手动删除;能按明确数量保留的先排好序再保留;不确定是否还会出现的脏数据类型,优先用删除错误或替换错误而不是直接删行。因为删行操作是物理性的,一旦数据源里又冒出同类问题,下次刷新还是会出错;而筛选条件则可以持续生效,下次刷新自动过滤。
另外要注意,这里的“删除”和Excel里按Delete键清除单元格是两码事。Power Query里的删除行是真正把行从表中移除,后续步骤中完全看不到这些行;而筛选行本质也是物理删除不满足条件的行,只是换了一种条件表达方式。如果只是想在视觉上隐藏某些行,应该回到报表视图用视觉级筛选器,而不是在Power Query里处理。
2. 行级保留与删除操作逐项拆解
2.1 删除顶部行、删除底部行、删除间隔行:处理结构型脏数据
删除顶部行最常见的场景是源表开头有企业名称、报表标题、生成日期等说明信息。比如导出的Excel前两行是“某某集团销售明细表”和“统计时间:2024-12”,真正的字段标题在第三行,数据从第四行开始。处理方式分两步:先“使用首行作为标题”把第三行提升为列名,再删除前两行。
这里有一个新手经常踩的坑:如果先删除前两行,再使用首行作为标题,就会把原始的第一行“某某集团销售明细表”当作列名,字段全部错位。所以顺序很重要,一般是先提升标题,再删多余行,或者先删除多余行,再手动指定标题行。我实操中为了让步骤更清晰,习惯先记录原始列结构,再处理头部说明行。
删除底部行与删除顶部行对称,主要处理表尾的合计行、签字栏、备注说明。很多财务系统导出的表底部会带“合计:12345元”“制表人:某某”,这些行如果不删,按列求和时会重复计算。删除间隔行则适合处理周期性夹杂的空行或分隔行,比如某些文本文件每五行数据插一行分隔线,可以用删除间隔行功能按偏移量和行数精确剔除。
这三个删除操作都对应M函数里的Table.RemoveFirstRows、Table.RemoveLastRows和Table.RemoveRange。底层逻辑是直接按物理位置删行,所以对数据排列顺序的依赖性很强。换句话说,如果数据源每次导出的行序不一致,按位置删除就要非常小心,建议先对数据排序再做位置删除,或者干脆改用条件筛选。
2.2 保留顶部行、保留底部行、保留间隔行:反向圈定数据范围
保留顶部行和保留底部行适合“只要某一部分”的场景。比如只看最近一周的数据,可以把数据按日期降序排列后保留顶部7行对应的日期范围;或者源表按日期升序排列,直接用保留底部N行取最近N天记录。
保留间隔行的使用频率相对低,但特定场景下非常有用。它允许你设置从第几行开始保留、保留多少行、跳过多少行,适合处理带周期性表头的文件。比如一份文本日志每50行插一行表头说明,你需要保留的是每个“数据段”而不是整个文件,就可以用保留间隔行把表头段全部剔除。
保留重复项这个按钮容易被忽略,但它和删除重复项一样重要。删除重复项是“每组重复值中只留第一行”,保留重复项则是“只留住重复出现的行,把所有唯一行全部删除”。举例来说,核查客户表时想找出有哪些客户重复注册了两次以上,就可以用保留重复项按客户ID分组,结果表里只剩下出现过多次的客户ID,唯一客户直接被过滤掉。
技术上,保留操作是取子集,删除操作也是取子集,两者互补。但“保留重复项”这个名字容易让人误解,我第一次用的时候以为它会把每组重复值全部保留,结果发现它把唯一行删了。后来才理解,这里的“保留”是指只保留满足“重复”条件的记录。
2.3 用“筛选行”实现条件化保留与删除
筛选行虽然按钮在“转换”或“主页”区域,但本质上是更灵活的保留/删除操作。你设置一个条件,条件为真的行被保留,条件为假的行被删除。比如只保留“地区=华东”的行,或删除“金额<0”的行,本质上都是筛选行。
筛选行相比位置删除有一个巨大优势:它不依赖行顺序,并且可以跟随数据源下推,也就是查询折叠。如果你的数据源是SQL Server,筛选操作会直接变成SQL里的WHERE语句,数据量再大也不会把全表先拉进Power Query内存再过滤,性能上有天壤之别。
实操中,我处理合计行通常用筛选行排除“类型=合计”或“备注=小计”,而不是删除底部行,因为合计行不一定总在底部,可能在表格中间穿插。按条件删行还有个好处,就是数据源增加新数据后,只要条件不变,清洗逻辑就一直有效,不需要每次刷新后重新调整删除位置。
3. 列级操作、重复值与错误值处理
3.1 删除列、删除其他列、选择列:三种列级精简方式的差异
列级保留与删除比行级操作更影响后续报表的字段可用性。Power Query里有三种相关操作:右键单列选择“删除列”、选中某列后选择“删除其他列”、以及“选择列”功能。三种方式本质都是保留或删除列,但使用场景略有区别。
“删除列”适合清理个别无用字段,源表有几十列,你只需要删掉其中一两列备注或辅助列时用得上。“删除其他列”则反过来,你选中要保留的某列后,把除此之外的全部列一次删除,适合源表有大量无用字段但核心字段集中在某几列的场景。“选择列”功能更直观,直接在弹出的复选框里勾选需要保留的字段。
我建议能用“选择列”尽量用选择列。原因很简单,它把“最终要保留哪些字段”这个结果明确记录在步骤里,后续别人看你的查询步骤时,一眼就能理解报表的数据口径。而“删除其他列”虽然结果相同,可一旦源表新增字段,新字段是否会被自动保留就变得不可控——有些情况新字段会被保留,有些情况不会,排查起来比较费劲。
列操作还要区分“物理删除”和“视觉隐藏”。如果某个字段你只是在报表界面不想展示,那应该在报表视图里取消勾选或删除视觉对象字段,而不是在Power Query里把列删掉。因为后续模型关系、计算列可能还要引用这些字段,一旦物理删除,再找回就得重新加载数据源。
3.2 删除重复项与保留重复项的实际语义
删除重复项按钮会按你选择的列进行分组去重,每组只保留第一行,其余全部删除。保留重复项按钮则只保留在分组中出现次数大于1的行,同时把唯一行删除。二者是一对互补操作。
理解这个逻辑有个关键点:删除重复项“保留第一行”里的“第一行”,取决于当前表格的行顺序。如果你在去重之前没有排序,那“第一行”就是数据源导出的原始顺序,不一定是你想留下的那一条。比如客户ID重复时,你希望保留最新一条订单记录,就必须先去重前按订单日期降序排序,再执行删除重复项。
实际操作步骤是:先按“订单日期”降序排列,然后选中“客户ID”列,点击“删除重复项”。这样每组重复客户ID里,最新订单日期的那行被保留,旧记录被删除。这个操作顺序在我做的客户交易模型里几乎每次都用,算是一个高频标准动作。
保留重复项则常用于找出脏数据或异常数据。比如员工表里工号重复,正常情况一个工号只能对应一个人,如果存在重复工号,说明数据录入有问题。用保留重复项按工号分组后,就能快速定位这些异常记录,再回源系统核实。
3.3 删除错误和删除空行的正确姿势
删除错误按钮专门处理步骤中因类型转换、函数计算产生的错误值。比如把文本列改成整数时,遇到“abc”就会产生错误,Power Query界面上显示Error字样。此时可以选择删除错误,也可以选择替换错误为null或0。
删除空行的“空”指整行所有字段都为空值,不是某一列为空。很多新手以为选中某列后删除空行就能清除该列的空值,实际并非如此。删除空行的判断对象是整行,如果其他列有值,即使你选中的列是空的,这一行也不会被删除。想清除某列的空值应该用筛选行或替换值,而不是删除空行。
另一个容易忽略的细节是空字符串和null的区别。Excel里一个单元格看起来是空的,可能是真的没值,可能是空字符串“”,也可能是空格字符。Power Query的“删除空行”默认只识别null和真正没内容的单元格,对“”和空格的识别各版本有差异。遇到这类情况,我建议你加一步:选中列,把空字符串替换为null,然后再执行删除空行。替换和删除两个动作配合使用,才能真正把“可视化地空着”的行清理干净。
4. 核心场景实操:从一张脏表到干净数据集的完整流程
4.1 模拟数据源特征与清洗目标
我习惯用一个模拟场景来说明整套操作流程。假设我拿到一张Excel表,内容是某公司2024年销售订单明细,原始数据有这些问题:
- 前两行是标题说明和统计时间。
- 第五行开始有“小计”“合计”文本行。
- 最后两行是“制表人”和“审核人”。
- 订单编号列存在重复。
- 金额列里有几行是错误值。
- 有多余的临时列,比如“备注2”“导入标记”。
清洗目标是生成一张规范化订单明细表,保留字段只有六个:订单编号、订单日期、客户名称、地区、产品、金额。
4.2 按顺序执行五类清洗操作
第一步,提升标题。在Power Query编辑器里,点击“使用首行作为标题”。因为跳过前面说明行后,第三行才是真正的列名,提升标题后列名会识别正确。
第二步,删除前面两行说明行。选中“删除行”下拉里的“删除前两行”。这里要留意,如果数据源每次导出的说明行数不固定,就不能用固定数量删除,而应该用筛选行排除包含特定关键词的行。
第三步,删除底部制表人和审核人行。同样用删除最后两行。但如果底部行数不固定,我建议改用条件筛选,比如筛选“客户名称”不等于“制表人”且不等于“审核人”,更稳妥。
第四步,选择列。点“选择列”,只勾选清洗目标里的六个字段,其余全部不勾选。
第五步,处理错误值。点击“删除错误”,把金额列转换时产生的错误行一次性移除。
第六步,按订单编号去重。先按订单日期降序排序,再对“订单编号”列执行删除重复项,保证保留最新订单。
第七步,最后检查空行。如果还有整行全空的记录,选择“删除空行”。这一步放在去重之后,可以避免无意义的空行参与排序。
4.3 验证结果与数据量变化
操作全部完成后,看右下角的行数统计和当前总列数。我的习惯是记录每一步后的行数变化,比如原始3668行,删完说明行剩3666行,筛选合计行后剩3571行,删除错误行后剩3548行,去重后剩3520行。把这些数字记录在备注或说明文档里,将来数据对不上时,能快速判断是哪一步出了问题。
验证还有一个关键动作:检查每列的“数据预览”图标是否正确。金额列应该是数字图标,日期列应该是日历图标,如果类型不对,优先用“更改类型”步骤强制修正。
最后把表格加载到数据模型,创建关系时确认订单编号列没有重复值,再开始写度量值。整个过程中所有操作都会保留在“查询设置”的“应用的步骤”里,这也是Power Query最优秀的设计之一,清洗过程完全可追溯、可回放。
4.4 手工输入M代码的常用写法速查
界面上的按钮操作,最终都会生成M语言代码。学会阅读这些代码,能让你手工排错和批量处理时效率大增。上面完整流程对应的核心M代码大致如下:
let Source = Excel.Workbook(File.Contents("C:\data\订单明细.xlsx"), null, true), Sheet = Source{[Item="销售明细",Kind="Sheet"]}[Data], PromotedHeaders = Table.PromoteHeaders(Sheet, [PromoteAllScalars=true]), RemoveFirst = Table.RemoveFirstRows(PromotedHeaders, 2), RemoveLast = Table.RemoveLastRows(RemoveFirst, 2), FilterRows = Table.SelectRows(RemoveLast, each [客户名称] <> "制表人" and [客户名称] <> "审核人"), SelectedColumns = Table.SelectColumns(FilterRows, {"订单编号", "订单日期", "客户名称", "地区", "产品", "金额"}), ChangedType = Table.TransformColumnTypes(SelectedColumns, {{"订单编号", Int64.Type}, {"订单日期", type date}, {"金额", Currency.Type}}), RemovedErrors = Table.RemoveErrors(ChangedType), SortRows = Table.Sort(RemovedErrors, {{"订单日期", Order.Descending}}), DistinctRows = Table.Distinct(SortRows, {"订单编号"}) in DistinctRows这段代码里最值得注意的是Table.SelectRows函数的条件写法,它通过each表达式逐行判断;Table.Distinct的第二参数指定去重依据列;Table.Sort排序会影响Distinct保留的是哪一行,这个顺序在代码里同样关键。
5. 服务端的数据保留策略与数据集生命周期管理
5.1 增量刷新中的保留期设置
数据保留不仅是Power Query里删行删列的问题,还涉及Power BI服务端的数据集刷新机制。当一个数据集体量达到数亿行时,每次全量刷新会消耗大量容量资源和刷新时间,业界通行做法是给事实表配置增量刷新。
增量刷新的核心有两组参数:RangeStart和RangeEnd。设置好参数后,Power BI会按分区拉取数据,只增量刷新最近N天的新增和变化数据。而“保留期”这个设置,对应的是历史数据的留存时长。比如设置保留期为3年,系统会保留最近3年的分区数据,超过3年的历史分区会被自动删除。
这个保留期机制非常适合订单流水、日志记录这类只增不减的事实表。原始数据库里的历史数据是否删除是源系统的事,Power BI侧只负责按策略保留建模所需的时间窗口,既控制了数据集容量,又避免了无关紧要的旧数据干扰报表性能。
设置增量刷新时还有两个数据加载选项值得注意:删除并重新加载和检测数据删除。删除并重新加载适合目标窗口内数据会大幅变化的情况,它是把对应分区的数据全部清掉再重新拉取。检测数据删除则能识别源表中被物理删除的记录,并把增量分区中匹配的记录一并同步删除。选哪种方案取决于源端是否允许硬删除数据,以及数据一致性要求。
这些配置不是固定不变的。每季度我都建议检查一次保留期,因为业务数据量增长速度可能超出预期,保留期过长会导致分区过多、刷新时间膨胀,保留期过短则可能丢失用户低频查询的历史数据。
5.2 删除数据集、报表页和工作区对象
数据保留与删除操作还应该延伸到Power BI服务端的工作区管理。一个工作区里可能堆积了几十个数据集,其中不少是测试用的临时模型或已被替代的旧版本。长时间不清理,会占用容量存储配额、增加管理成本。
删除数据集前要做的检查事项:确认没有其他报表依赖该数据集、确认没有已配置的数据刷新计划指向它、必要时先导出.pbix备份再删除。报表端的清理则更轻量,直接在报表视图删除无用页面即可,删除页面不会影响数据模型,只影响展示层。
工作区层面还可以按生命周期管理对象。上线一年以上的旧工作区如果不再使用,可以考虑归档或删除。Power BI容量中删除数据集后不会立即释放全部存储空间,系统会有垃圾回收周期,这个过程中容量统计可能出现延迟,属于正常现象。
5.3 数据删除的合规留痕与协作提醒
一旦涉及删除操作,就要想到可追溯性。尤其是团队协作场景里,你删掉的数据集可能还支撑着某个已发布报表。我的习惯是删除任何数据集或报表前,在工作区说明文档里记录删除对象、删除原因、删除日期,并保留一份备份到共享存储。
对增量刷新保留期调整这类敏感操作,建议先在开发工作区验证,再同步到生产环境。生产环境调整保留期前,务必确认下游看板和订阅没有依赖被清理的历史分区。数据删除这件事,宁可多问一句,也不要在恢复无门的情况下冒进。
6. 常见问题与排查技巧实录
6.1 高频问题速查表
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| 删除前几行后列名错乱 | 先删行再提升标题,导致原始说明行变成了列名 | 先提升标题或手动指定标题行,再删除多余行 |
| 删除重复项后留下的行不是想要的 | 去重前没有排序,保留了数据源原始顺序的第一行 | 先按期望字段排序,再去重 |
| 删除空行后仍有很多空值行 | 空字符串或空格字符不被“删除空行”识别 | 先替换空字符串为null,再删除空行 |
| 金额列有Error但删除错误没生效 | 未选中对应列,删除错误默认处理整表,可能偏差 | 先选中目标列再执行删除错误,或改用替换错误值 |
| 底部合计行删不干净 | 合计行不固定在末尾,删除底部N行错删了数据行 | 改用筛选条件排除“合计”“小计”关键词 |
| 数据源新增列后报表字段丢失 | 之前用了删除其他列,新列被保留导致模型混乱 | 改用选择列,明确最终保留字段集合 |
| 全量刷新太慢 | 数据量大但没有增量刷新,且等历史分区都保留 | 配置增量刷新,设置合理保留期 |
6.2 排查思路与踩坑记录
第一步永远是看“应用的步骤”列表。Power Query保留了每一步操作,任何一步异常都能直接点进去看当时的预览结果。如果某一步得到的行数和你预期不符,大概率是上一步的筛选或排序条件没有生效。
第二步是查看是否有步骤被自动折叠进了上一步。Power Query有时会合并步骤,比如连续删除空行和删除错误可能被合并,排查问题时步骤数量变少会让人困惑。右键点击步骤选择“查看本机查询”,在M代码面板里能看到完整表达式。
我踩过最大的坑是去重时忘记排序,结果一个订单表的“第一行”都是最早一批导入的测试数据,导致正式客户信息被丢弃。后来我养成了一个习惯:每次执行删除重复项之前,都在心里默念“先排序,再去重;排序字段就是你想保留的那条记录的依据”。还有一次删除空行后,报表里仍能看到一堆空行,排查了很久才发现那些行里某个字段不是null而是空字符串,替换为null后才真正删掉。
6.3 性能优化与查询折叠注意事项
数据量大的表,清洗操作的下推能力至关重要。筛选行、删除重复项、选择列这类操作在大多数数据库源上可以下推为SQL执行,意味着源端帮你做好了过滤,传输到Power Query的数据量极小。而“删除前几行”“删除后几行”这类基于位置的操作用查询折叠的难度较大,大表上执行时往往需要把数据先拉进内存。
判断一个步骤是否下推成功,可以右键步骤点击“查看本机查询”,如果看到的是数据库查询语法而非M函数,说明已经下推。为了保证性能,我建议把位置类删除操作放在清洗流程末尾,先用筛选条件把数据量压下来,再对剩余数据执行位置操作。
M代码的健壮性也会影响性能。尽量避免使用each [列名] = "值"这样对每一行做复杂判断的写法,能用Table.SelectRows一次性完成多条过滤的,就不要拆成多个筛选步骤。步骤数量不是越少越好,但冗余步骤确实会增加计算开销,建议定期整理应用的步骤,删除临时性操作。
我个人在实际操作中的体会是,Power Query里所有保留与删除操作,本质都是在给数据建立一份“边界清单”。边界定得越清晰,后面模型越稳定。每次改完清洗逻辑,我都会把行数变化和关键操作顺序记录在项目文档里,这种看起来不起眼的细节,在数据出问题需要追溯时价值极高。如果你刚接触PowerBI,不用急着背M函数,先把界面上的保留、删除、筛选、去重这几个按钮的适用场景分清楚,再拿一张真实业务表从头到尾走一遍,做完之后你会发现,数据清洗这关过了,DAX和可视化都是水到渠成的事。