Lazy loaded imageEP018:理解上下文(each)和((x)=>)嵌套,解决国际标准排名问题

type
status
date
slug
summary
tags
category
icon
password
notion image

专为初学者设计的“M函数上下文与嵌套(套娃)”教学文档
为了让你学得明白,我们全程只使用一份核心数据,但会通过 3 个循序渐进的案例,彻底拆解 each_(x)=> 的奥秘。

准备工作:核心示例数据

请在 Excel 中录入以下数据,并加载到 Power Query 编辑器中,查询名称设为 业绩表
姓名
部门
业绩
张三
销售一部
80
李四
销售一部
91
王五
销售二部
70
赵六
销售二部
90

第一阶段:照镜子 —— 理解 each_ (行上下文)

教学目标:明白 each 就是开启“逐行扫描模式”,而 _ 就是“当前这一行自己”。

案例 1:给每个人发一份“个人档案”

我们想在表格后面加一列,这一列的内容就是这一行数据本身(Record)。
操作步骤:
  1. 点击 【添加列】 -> 【自定义列】
  1. 新列名:个人档案
  1. 公式输入:(注:步骤公示栏那里的函数,系统会自动补全为 each _)
    结果展示:
    姓名
    ...
    业绩
    个人档案 (新列)
    张三
    ...
    80
    [Record]
    李四
    ...
    91
    [Record]
    深度解析:
    • each:相当于大喊一声“全体都有!”它告诉 Power Query 引擎:“接下来的指令,要对每一行分别执行一次。”
    • _ (下划线):这就是“当前行”的代号。
      • 当处理第一行时,_ 就等于 [姓名="张三", 部门="销售一部", 业绩=80]
      • 当处理第二行时,_ 就变成了李四的记录。
    • 比喻:就像让每个人拿一面镜子照自己。_ 就是镜子里的像。

    第二阶段:取名字 —— 理解 (x)=> (自定义变量名)

    教学目标:明白 each 只是简写,(x)=> 才是 M 函数的真面目。学会给环境起名字,防止混淆。

    案例 2:用“全名”来提取业绩

    我们不直接用 [业绩],而是用函数的完整写法来提取业绩,并打个折。
    操作步骤:
    1. 点击 【添加列】 -> 【自定义列】
    1. 新列名:折后业绩
    1. 公式输入(手动删除系统自动生成的 each):

      为什么 each 容易,这个难?

      因为 each 是个“马甲”。当你写 each [业绩]*0.9 时,Power Query 在后台悄悄把它翻译成了:(_) => _[业绩]*0.9
      • 它自动把名字起成了 (下划线)
      • 它允许你省略 _ ,直接写 [业绩]
      但是!当你手动写 (x)=> 时,你就撕掉了这个马甲:
      1. 你自己起了名字(比如叫 x)。
      1. 你就失去了省略名字的特权。
      1. 你必须写成 x[业绩]*0.9 ,而不能只写 [业绩]*0.9
      结果展示:
      姓名
      ...
      业绩
      折后业绩
      张三
      ...
      80
      72
      李四
      ...
      90
      81
      深度解析:
      • each [业绩] 其实就是 (_) => _[业绩] 的偷懒写法。
      • (当前行) => ...
        • 当前行:这是我们给 _ 起的一个有意义的名字(变量名)。你也可以叫它 xk 或者 当前行
        • =>:表示“去执行后面的计算”。

      为什么要找麻烦这样写?

      虽然写起来麻烦,但当我们有两层环境时(比如大娃套小娃),只有给它们起了不同的名字,才不会搞混“哪个是爸爸,哪个是儿子”,才能用来做一些更复杂的函数编写操作,比如下面第三阶段的排名问题。
      场景:我想看看“张三”的业绩,是不是比“李四”高?
      • 如果都用偷懒写法
        • “它的业绩 > 它的业绩吗?”
        • Power Query 懵了:哪个“它”是张三?哪个“它”是李四?
      • 如果用正式写法(起名)
        • 我们给张三起名叫 (X),给李四起名叫 (Y)。
          (X) => 去列表里找 (Y) => 看看 Y[业绩] > X[业绩] 吗?
      总结:
      • each:适合单兵作战,只有一个“它”
      • (x)=>:适合多方混战,给每个对象起不同的名字(x, y, old, new),这样在公式里打架的时候,能分清谁是谁。

      第三阶段:专业套娃 —— 解决“排名”问题 (嵌套上下文)

      教学目标:这是本课的终极目标。利用嵌套,在一个函数里同时调用“当前行数据”“整张表数据”,用当前行数据和整张表数据比较,从而确定排名。

      案例 3:计算业绩排名(中国式排名)

      业务逻辑:对于“张三”(80分)来说,排第几名? 逻辑是:“我去数一数,全班有多少人的分数比张三高?”
      • 如果有人比张三高(比如李四91,赵六90),那就是2个人比他高。
      • 那张三就是第 2+1 = 3 名。
      这里涉及两个对象:
      1. “我” (张三)当前行上下文。
      1. “全班” (整张表)全局上下文。
      操作步骤:
      1. 点击 【添加列】 -> 【自定义列】
      1. 新列名:排名
      1. 公式输入(高能预警,请仔细逐字输入,以下版本是直接在PQ高级编辑器编辑的版本):

      ⚠️ 关键点解析

      这段代码在业务上实现的是“基于业绩数值的中国式排名(关键点:同分并列,名次跳跃)”。
      我们可以把它拆解为三个层面的含义来理解:

      1. 核心业务逻辑:通过“比我强的人数”来确定排名

      这段代码没有使用现成的排序函数,而是用了一种更原始、更符合直觉的逻辑来计算名次。
      • 业务场景:假设你是一名销售员(即代码中的 当前行)。
      • 计算过程
          1. 你拿着自己的业绩单,去跟全公司所有人(代码中的 更改的类型 表)进行比较。
          1. 你数一数:“全公司有多少人的业绩比我高?”(即 Table.SelectRows 里的筛选条件 >)。
          1. 假设你数出来有 5个人 业绩比你高。
          1. 那么你的排名就是:5 + 1 = 第 6 名

      2. 对“并列排名”的处理(关键业务细节)——按国际排名规则

      这种算法天然解决了一个复杂的业务问题:如果有两个人业绩一样怎么办?
      举例推演: 假设 A 和 B 的业绩都是 100万,并列全公司最高。C 的业绩是 90万。
      • 对于 A:比 A 业绩高的人数是 0。排名 = 0 + 1 = 第 1 名
      • 对于 B:比 B 业绩高的人数是 0(因为 A 等于 B,不大于 B)。排名 = 0 + 1 = 第 1 名
      • 对于 C:比 C 业绩高的人数是 2(A 和 B 都比 C 高)。排名 = 2 + 1 = 第 3 名
      结论:这种排名方式会产生 1、1、3 的名次序列(跳过了第2名),这符合国际的竞赛排名规则。

      总结

      这段代码用一句话概括就是:“为每一位员工计算排名,排名的定义是:业绩优于该员工的人数 + 1。”
      结果展示:
      姓名
      业绩
      排名
      张三
      80
      3
      李四
      91
      1
      王五
      70
      4
      赵六
      90
      2

      定义变量的适用范围

      比如上面的代码中,针对[业绩]的列,我们定义了两个不同变量下的[业绩],一个是在当前行的上下文中,一个是在全局上下文中,那么这个变量定义的适用范围是哪些?
      这个问题,触及到了 Power Query (M语言) 中最核心的概念:“迭代(Iteration)”
      简单直接的回答是:当前行 仅仅指代“此时此刻正在计算的那一行”。它是一个动态的概念,我们可以用“流水线工人”的例子来打比方。

      1. 形象理解:流水线隐喻

      想象 Table.AddColumn 是一个流水线工人,他的任务是给每一个流过来的销售员(每一行数据)贴上一个“排名标签”。
      • 整个表:就是传送带上所有的销售员业绩队列。
      • 当前行:就是工人手里正在拿着的这一个销售员
      工作过程是这样的:
      1. 第一轮:工人拿起第一个销售员(张三)
          • 此时,当前行 = 张三
          • 工人拿着“张三(当前行)”去跟“整个销售员业绩(全表)”比业绩。
          • 算完,把张三放回去。
      1. 第二轮:工人拿起第二个产品(李四)。
          • 此时,当前行 变成了 李四
          • 注意:刚才那个“张三”已经不是当前行了,现在的焦点全在李四身上。
          • 工人拿着“李四(当前行)”去跟“整个销售员业绩(全表)”比业绩。
      1. 第三轮... 以此类推,直到处理完最后一行。
      所以,当前行 的范围是仅限一行,但它是动态变化的。

      2. 代码层面的技术解析

      在代码 (当前行) => ... 中:
      • 它的本质:这是一个变量名(参数)。
      • 它的数据类型:它是 Record(记录) 类型。
        • 它包含了这一行的所有信息,比如 [姓名="张三", 部门="销售一部", 业绩=100]
        • 这就是为什么你可以用 当前行[业绩] 来获取具体的数值。
      • 它的作用域:它只在这个 Table.AddColumn 的函数内部有效。

      3. 为什么需要区分“当前行”和“全表”?

      在你的双层嵌套代码中,其实有两个角色在互动:
      • 角色 A(主角):当前行
        • 代表:“我”
        • 在计算第 N 行时,它就是第 N 行的数据。
      • 角色 B(背景板):内层函数的 全公司销售
        • 代表:“所有人”
        • 这是一个固定的、完整的列表。
      代码翻译过来就是:
      “拿 【我(当前行),这是动态变化的】 的业绩,去和 【所有人全公司销售】 的业绩一个一个比,看看有多少人比我强,比我强的人数再加1,就是我的排名。”

      4. 一个小实验

      如果你把代码改成这样:
      结果会是:
      • 第一行显示:张三
      • 第二行显示:李四
      • ...
      这证明了 当前行 每次只代表它所在的哪一行

      所以,变量的适用范围总结如下:

      • 是单行吗? 是的,每次计算只代表一行。
      • 是固定的吗? 不是,随着计算从上往下进行,它依次代表第一行、第二行、第三行……
      • 名字重要吗? 不重要。你把它写成 (x) => ... 或者 (_) => ... 效果完全一样,叫 当前行 只是为了让你读代码时更容易理解它是“Me(我)”。

      嵌套函数逐层释义

      notion image
      这里解释的是 国际标准排名(即并列第1,下一个就直接跳到第3)。
      我们假设:当前正在处理的一行“张三”(业绩 80)。 全公司还是那 4 个人:张三(80)、李四(90)、王五(70)、赵六(90)(注意:李四和赵六都是 90 分,比张三高)
      我们由最内层向最外层,一层层剥开这个“数人头”的过程:

      第 1 层(最内层):比对条件

      代码:
      • 动作:这是一个判断逻辑。拿着张三的业绩(80),去问全公司的每一个人。
      • 逻辑
        • 李四(90) > 80?
        • 王五(70) > 80?
        • 赵六(90) > 80?
      • 含义:找到比我强的人。

      第 2 层(筛选):拿到比我强的人员名单

      代码:
      • 动作:根据第 1 层的判断结果,把符合条件的人(行)留下来。
      • 结果:此时得到的是一个 Table(表)
      • 内容:这个表里有 2 行数据:
          1. 李四 (90)
          1. 赵六 (90)

      第 3 层(统计人数):数数有几个人

      代码:
      • 动作:直接数一数刚才筛选出来的这张表里,一共有几行(也就是几个人)。
      • 计算:表里有李四、赵六,共 2 行
      • 结果:数字 2
      • 人话翻译:意思是“比张三业绩高的人,一共有 2 个”。(不管是李四还是赵六,虽然分一样,但算 2 个人头)

      第 4 层(最外层):计算最终排名

      代码:
      • 动作:最终排名 = 比我强的人数 + 1。
      • 计算2 + 1 = 3
      • 结果:张三的排名是 第 3 名

      总结各步骤

      步骤
      此时数据的形态
      数据内容 (以张三为例)
      核心逻辑
      1. 筛选
      Table (表)
      李四(90), 赵六(90)
      找出所有比我强的人
      2. 计数
      Number (数字)
      2
      关键点! 直接数行数(人头),不去重
      3. 加一
      Number (数字)
      3
      前面有2个人,所以我(张三)排第3,直接跳过第2名,没有第2名。

      总结:初学者避坑指南

      1. 什么时候用 _
          • 当你只需要处理当前这一行数据,且不涉及嵌套(不套娃)时,用 each _ 或直接写 [列名] 最快。
      1. 什么时候用 (x)=>
          • 当你需要“拿当前行的数,去跟整张表里的其他数做比较”时(如排名、累计求和、查找同类),必须用 (x)=> 给外层环境起个独特的名字,否则 M 函数会分不清谁是谁。
      1. 心法口诀
          • AddColumn 是大娃,手里拿着当前行
          • SelectRows 是二娃,手里拿着整张表
          • 要想二娃认大娃,必须起名别乱抓。
      按照以上这个文档的内容,逐项去练习,你就能彻底翻越 M 函数最陡峭的这座“上下文”大山了!

       
      【扫码识别下发二维码,获取本文使用到的及历史以来提及、发布的相关精选和原创资料,和我们一起充电,加入后可享受每月6次免费咨询提问,帮助解决您的特定问题!】

      如果本篇文章对您有帮助或启发,请帮我们点赞、转发、推荐、关注,让更多想转型财务BP、锻造数据分析和可视化洞察能力的财务同行们看到,关注【老汪洞察】,不迷路!
      notion image
      notion image
       
      温馨提示
      🙏🏻
      如果您不想错过【老汪洞察】的文章,请将我们设为"星标",这样每次最新文章推送才会第一时间出现在您的订阅列表里。 方法:点击文章页面左上角蓝色文字“老汪洞察”进入主页,点击关注后,再点主页右上角"...",然后选择"设为星标",即可完成,感谢您的支持。
       
      上一篇
      一个案例学习理解let...in骨架函数的嵌套逻辑
      下一篇
      EP017:列表(List)函数族的统计运算
      Loading...
      文章列表
      让财税成为经营的力量
      管理报表
      从Power Query到Power BI,入门到精通
      699课程讲义
      VBA小工具
      电脑与网络
      知识运用