# 🧹 B26｜用 AI 写 Power Query M 查询做数据清洗 — 配套资产

> **系列**：AI 办公实战：从小白到达人
> **文章号**：B26（Track 7 办公开发 / Level 2 进阶）
> **配套中文标题**：用 AI 写 Power Query M 查询做数据清洗
> **配套英文标题**：Write Power Query M Queries with AI

---

## 一、这是给谁的？解决什么问题？

写给**每月要从系统导一份"脏销售明细"、手动改空格、改货币符号、再算提成**的朋友：每张表都打开、手填、保存，复制粘贴到手酸；改一个规则，三十张表都得重做一遍。

主教程教你**用 AI 生成 Power Query 的 M 代码**——你只要把"列名 + 规则"说清楚，AI 就替你写代码，你只负责看懂、贴进去、刷新。

本目录提供**真实可运行的最小完整流水线**：

| 文件 | 干什么 |
|------|--------|
| `数据源_销售明细_sample.csv` | 14 行示例数据（含 `¥` / `%` / 空格 / 不必要列） |
| `清洗查询_zh.pq` | 完整 M 查询（中文注释版）：5+ 步函数管道 |
| `清洗查询_en.pq` | 完整 M 查询（英文注释版） |
| `清洗前后对比_zh.md` | 逐步对照"清洗前→清洗后"，便于理解每步干了啥 |
| `清洗前后对比_en.md` | 同上，英文版 |

跑通本目录，你手里就有了一份"贴进高级编辑器即可用"的 M 模板——下回遇到同结构脏表，改改列名和规则就上线。

---

## 二、使用方法（5 步跑通示例）

### 第一步：把示例 CSV 导入 Excel，建表命名 `RawData`

1. 打开 Excel（Office 365 / 2021 均可）；
2. 数据 → 从文本/CSV → 选 `数据源_销售明细_sample.csv` → 导入；
3. 选中导入后的区域，按 **`Ctrl + T`**（或 插入 → 表格），勾选"表包含标题"；
4. **表设计 → 表名** 改成 **`RawData`**（大小写敏感，必须与 `.pq` 代码里 `[Name="RawData"]` 一致）。

> 不改表名会出现什么？高级编辑器里跑时会报 `Expression.Error: 找不到名为"..."的表`。

### 第二步：打开"空白查询 → 高级编辑器"

1. 数据 → 获取数据 → 从其他源 → **空白查询**；
2. 在右侧"查询设置"面板找到 **高级编辑器**（图标 `{ }`）；
3. 把里面默认两行 `let Source = "" in Source` **整段清空**。

### 第三步：贴入 M 代码

打开 `清洗查询_zh.pq`，整段复制（用 `Ctrl+A` → `Ctrl+C`），粘进高级编辑器，点 **完成**。

立刻在预览里看到 14 行 × 7 列的清洗结果。如果出现红字，回到 `清洗前后对比_zh.md` 的"核验清单"逐条对照。

### 第四步：把结果落地到工作表

主页 → **关闭并上载** → **关闭并上载至...** → 选 表格 → 现有工作表 → 选个空单元格（比如 `H1`）→ 确定。

### 第五步：以后每月自动复用

每月新数据来了：
1. 把新数据**覆盖**进 `RawData` 表（保持列名、表名不变）；
2. 右键查询 → **刷新**（或 数据 → 全部刷新）。

**整套清洗代码一行都不用改**，这就是"规则写一次、反复用一辈子"的 Power Query 红利。

---

## 三、文件清单

| 文件名 | 类型 | 作用 | 行数 |
|--------|------|------|------|
| `B26_M查询_中文README.md` | Markdown | 本文件 | — |
| `B26_PowerQuery_M_EnglishREADME.md` | Markdown | 英文版 README | — |
| `数据源_销售明细_sample.csv` | CSV | 14 行示例数据，含脏点 | 15 |
| `清洗查询_zh.pq` | M 代码 | 完整查询（中文注释） | ~75 |
| `清洗查询_en.pq` | M 代码 | 完整查询（英文注释） | ~75 |
| `清洗前后对比_zh.md` | Markdown | 逐步对照说明（中文） | ~110 |
| `清洗前后对比_en.md` | Markdown | 逐步对照说明（英文） | ~110 |

---

## 四、实战代码（M 核心片段逐行解读）

### ① 取数 + 选列

```powerquery
// 从当前工作簿读名为 "RawData" 的表
Source = Excel.CurrentWorkbook(){[Name="RawData"]}[Content],

// 白名单选列，丢掉"备注"
KeepCols = Table.SelectColumns(Source, {"订单号", "客户姓名", "地区", "销售额", "折扣"}),
```

**两个 API**：
- `Excel.CurrentWorkbook()` 返回当前工作簿所有"表"的列表，`{[Name="RawData"]}` 按名字取一项，`[Content]` 取表内容。
- `Table.SelectColumns(表, {"列1","列2",...})` 白名单挑列。

### ② 文本清洗

```powerquery
TrimText = Table.TransformColumns(KeepCols, {
    {"地区",      each Text.Upper(Text.Trim(_)), type text},
    {"客户姓名",  each Text.Trim(_),            type text}
}),
```

**三个糖**：
- `each _` = `(_) => _`，`_` 是"当前行"占位符；
- `Text.Trim` 去首尾空白，`Text.Upper` 转大写；
- `type text` 锁列类型，避免后面被推断成 `any`。

### ③ 文本转数字

```powerquery
ToNumber = Table.TransformColumns(TrimText, {
    {"销售额", each Number.FromText(Text.Remove(_, {"¥", ",", " "})), type number},
    {"折扣",   each Number.FromText(Text.Remove(_, {"%", " "})),     type number}
}),
```

**关键顺序**：必须先 `Text.Remove` 把 `¥` `,` `%` 空格全删掉，再 `Number.FromText`。直接 `Number.FromText("¥1,200")` 会报错。

### ④ 自定义函数 + 加列

```powerquery
// 自定义函数 = 你定义的可重复调用的小公式
CalcCommission = (sales as number, discount as number) as number =>
    if discount > 20 then sales * 0.03 else sales * 0.05,

// 每行调用上面那个函数
AddCommission = Table.AddColumn(ToNumber, "提成",
    each CalcCommission([销售额], [折扣]), type number),
```

**两个要点**：
- 自定义函数必须用 `=>`，不是 `=`；
- `[销售额]` `[折扣]` 是 `each` 内部访问"当前行某列"的语法；离开 `each` 想取列必须用参数（如 `CalcCommission` 里用的是 `sales` 而非 `[销售额]`）。

### ⑤ 条件列 + 重排

```powerquery
AddLevel = Table.AddColumn(AddCommission, "等级",
    each if [销售额] >= 10000 then "A"
         else if [销售额] >= 5000  then "B"
         else "C", type text),

Result = Table.ReorderColumns(AddLevel,
    {"订单号", "客户姓名", "地区", "销售额", "折扣", "提成", "等级"})
```

**两个要点**：
- M 的 `if` 没有 `end`，每条分支用 `then` 串起来，最后 `else` 收尾；
- `Table.ReorderColumns` 只是改列顺序、不动数据。

---

## 五、常见问题 / 避坑指南

1. **`Expression.Error: 找不到名为"..."的表`**
   表名拼错 / 没建表 / 用了中文标点。回到第一步，确认 `RawData` 大小写一致、列名一字不差。

2. **`Number.FromText` 报错 / 转换后整列是 `null`**
   数据里还有 `¥` `%` `,` 符号没清干净。`Text.Remove` 的字符集少了哪个补哪个；中文全角空格 `U+3000` 要写成 `{"　"}`。

3. **`each [列名]` 报"找不到列"**
   列名拼错最常见；另一个原因是"`each` 外面写了 `[列名]`"——`[列名]` 只能在 `each` 内部用，函数体里要用参数名。

4. **改了高级编辑器代码，点关闭后没保存**
   必须点 **完成** 才算保存；只关窗口 = 改动丢弃。下次刷新还会跑旧代码。

5. **整列变成 `Error`**
   一般是源表里某行的"销售额"为空、或者出现"待定"之类的脏文本。先在 `RawData` 里手动清掉这一行（或加 `try ... otherwise null`），再回 M。

---

## 六、下一步

跑通本文档，你手里就有了"贴进高级编辑器即可用"的 M 清洗模板。下一步推荐两个方向：

- **B23《用 AI 搭数据模型》**：把清洗结果建成多张表的关系模型，配合 DAX 算指标，是"清洗之后做汇总"的标准接力。
- **B25《用 Python + openpyxl 批量处理 Excel》**：当你想**批量处理几十张 Excel** 而不是单张清洗时，Python + openpyxl 的"读-改-写"流水线更顺手。
- **只想用鼠标点、完全不碰代码**？回看 B2《用 AI 一键清洗脏数据》——它是"AI 出清单、你照着点按钮"的轻量路线。

---

> 📌 **快速核查清单**（跑之前再确认一次）：
> - [ ] `RawData` 表已建好（`Ctrl+T`）；
> - [ ] 表名 `RawData` 大小写一致；
> - [ ] 列名 `订单号 / 客户姓名 / 地区 / 销售额 / 折扣` 与代码逐字相同；
> - [ ] 高级编辑器里默认两行已删、整段 M 代码已贴；
> - [ ] 点"完成"后预览 14 行 × 7 列、无红字；
> - [ ] 点"关闭并上载"成功落到工作表。