点鼠标很爽,但写M语言更爽。学会这个,你就是Power Query的高级玩家。
为什么要学M语言?
Power Query的界面操作其实背后都在生成M语言代码。点鼠标能完成80%的工作,但剩下20%的复杂需求,必须写代码。
而且,写代码有写代码的好处:
- 更灵活:循环、条件、自定义函数
- 更可控:精确控制每一步的逻辑
- 更可复用:保存成函数,到处调用
- 更装逼:同事会觉得你很厉害
M语言基础语法
M语言是大小写敏感的,这点要注意。
基本结构
let
步骤1 = 操作1,
步骤2 = 操作2,
结果 = 最后操作
in
结果
每个步骤后面加逗号,最后一个步骤不加。
创建表格
let
源 = #table(
{"姓名", "年龄", "部门"}, // 表头
{
{"张三", 25, "技术部"},
{"李四", 30, "销售部"},
{"王五", 28, "技术部"}
}
)
in
源
引用上一步的结果
let
源 = Excel.CurrentWorkbook(){[Name="表1"]}[Content],
改类型 = Table.TransformColumnTypes(源, {{"日期", type date}}),
筛选 = Table.SelectRows(改类型, each [销售额] > 10000)
in
筛选
常用函数速查
表格操作
// 筛选行
Table.SelectRows(表, each [列名] > 条件)
// 选择列
Table.SelectColumns(表, {"列1", "列2"})
// 删除列
Table.RemoveColumns(表, {"列1", "列2"})
// 重命名列
Table.RenameColumns(表, {{"旧名", "新名"}})
// 添加列
Table.AddColumn(表, "新列名", each 计算公式)
// 排序
Table.Sort(表, {{"列名", Order.Descending}})
列表操作
// 创建列表
{1, 2, 3, 4, 5}
// 序列
{1..10} // 1到10
// 列表求和
List.Sum(列表)
// 列表平均值
List.Average(列表)
// 列表最大值/最小值
List.Max(列表)
List.Min(列表)
文本操作
// 拼接
Text.Combine({"Hello", " ", "World"})
// 拆分
Text.Split("a,b,c", ",")
// 替换
Text.Replace("Hello World", "World", "Excel")
// 取长度
Text.Length("Hello")
// 取子串
Text.Start("Hello", 2) // 前2个字符
Text.End("Hello", 2) // 后2个字符
Text.Middle("Hello", 1, 2) // 从第2位开始取2个
日期时间
// 今天
DateTime.LocalNow()
// 日期部分
Date.From(DateTime.LocalNow())
// 年月日
Date.Year(日期)
Date.Month(日期)
Date.Day(日期)
// 本月初
Date.StartOfMonth(日期)
// 本月末
Date.EndOfMonth(日期)
实战案例1:动态获取最近N天的数据
需求: 只筛选最近30天的记录。
let
源 = Excel.CurrentWorkbook(){[Name="销售表"]}[Content],
改日期类型 = Table.TransformColumnTypes(源, {{"日期", type date}}),
// 获取今天
今天 = Date.From(DateTime.LocalNow()),
// 计算30天前
起始日期 = Date.AddDays(今天, -30),
// 筛选
筛选结果 = Table.SelectRows(改日期类型, each [日期] >= 起始日期)
in
筛选结果
每次刷新,自动计算从今天往前30天的数据。
实战案例2:递归展开多层JSON
场景: API返回的JSON是嵌套的,比如:
{
"订单": [
{
"订单号": "A001",
"商品": [
{"名称": "手机", "数量": 1},
{"名称": "耳机", "数量": 1}
]
}
]
}
需要展开成:
| 订单号 | 商品名称 | 数量 |
|---|---|---|
| A001 | 手机 | 1 |
| A001 | 耳机 | 1 |
M语言写法:
let
源 = Json.Document(获取的JSON),
订单列表 = 源[订单],
转表格 = Table.FromList(订单列表, Splitter.SplitByNothing()),
展开订单 = Table.ExpandRecordColumn(转表格, "Column1", {"订单号", "商品"}),
展开商品 = Table.ExpandListColumn(展开订单, "商品"),
展开商品详情 = Table.ExpandRecordColumn(展开商品, "商品", {"名称", "数量"})
in
展开商品详情
关键步骤:
Table.FromList把列表转成表格Table.ExpandRecordColumn展开记录(对象)Table.ExpandListColumn展开列表(数组)
实战案例3:创建自定义函数
场景: 有一个通用的清洗逻辑,要在多个查询里复用。
创建函数
// 命名为:清洗订单表
(订单表 as table) as table =
let
改类型 = Table.TransformColumnTypes(订单表, {{"日期", type date}, {"金额", type number}}),
删除空值 = Table.SelectRows(改类型, each [订单号] <> null),
加提成列 = Table.AddColumn(删除空值, "提成", each [金额] * 0.05)
in
加提成列
使用函数
let
原始数据 = Excel.CurrentWorkbook(){[Name="表1"]}[Content],
清洗后 = 清洗订单表(原始数据)
in
清洗后
函数可以接收多个参数,返回任何类型。
实战案例4:循环处理文件夹里的文件
场景: 每个文件需要不同的处理逻辑。
let
源 = Folder.Files("C:\\报表\\"),
筛选文件 = Table.SelectRows(源, each Text.EndsWith([Name], ".xlsx")),
// 添加处理列,每个文件调用自定义处理
处理结果 = Table.AddColumn(筛选文件, "处理后的数据", each
try 处理单个文件([Content]) otherwise null
),
// 展开处理后的数据
展开数据 = Table.ExpandTableColumn(处理结果, "处理后的数据",
Table.ColumnNames(处理结果{0}[处理后的数据])),
// 删除原始二进制列
删除列 = Table.RemoveColumns(展开数据, {"Content", "Extension", "Date accessed"})
in
删除列
// 自定义处理函数
处理单个文件 = (文件内容 as binary) as table =
let
加载 = Excel.Workbook(文件内容),
第一个表 = 加载{0}[Data],
提升标题 = Table.PromoteHeaders(第一个表)
in
提升标题
调试技巧
查看中间结果
在M语言编辑器里,点击某一行,可以看到这步的结果预览。
分步执行
在 in之前插入一个临时结果:
let
步骤1 = ...,
步骤2 = ...,
调试 = 步骤2, // 在这里暂停查看
步骤3 = ...
in
步骤3
错误处理
try 可能出错的代码 otherwise 默认值
学习资源
- Power Query官方文档(英文)
- M语言规范文档
- 多看点示例代码,比看书有用
一句话总结
M语言是Power Query的底层引擎。点鼠标是自动挡,写M语言是手动挡,开习惯了手动挡更爽。
学会M语言,你就能做出任何复杂的数据处理逻辑,不再受界面操作的限制。
M语言刚看的时候确实有点懵,但语法其实比VBA简单多了。写多了就会发现,它比点鼠标快多了,尤其是处理复杂逻辑的时候。推荐先从简单的函数开始,慢慢就会上瘾的。