PMC群:49周订单规则分配专题 - 1

古哥计划
古哥计划

优秀创作者

目标:根据订单中的零件类型(B列),对每个零件的订单数量(C列)进行条件求和。具体规则为:!

  • 当某零件首次出现时,返回 0

  • 后续每次出现该零件时,返回该零件此前所有出现次数的数量总和(不包括当前行的数量)


核心函数:

📌

1. FILTER 是前置筛选工具

  • 描述:必须先根据零件类型筛选出同类型的历史数据,才能进行累计计算。

  • 应用场景:数据预处理阶段,用于筛选符合条件的数据集。

2. MAP 是逐行处理的核心函数

  • 描述:对每一行独立执行逻辑,适用于“按行判断并计算”的场景。

  • 应用场景:处理需要对每个数据点单独应用逻辑的情况。

3. OFFSET 动态获取历史区域

  • 描述:结合动态引用(如 B2:x),实现当前行以上所有数据的提取。

  • 应用场景:当需要从一组数据中提取特定部分时使用。

4. SUM + TAKE 实现排除当前值

  • 描述:通过 SUM(a) - TAKE(a, -1) 可有效剔除当前行的数量,仅保留历史总和。

  • 应用场景:累加历史数据而不包括当前记录。

5. LET 提升公式可读性

  • 描述:定义变量避免重复引用,尤其适用于复杂嵌套结构。

  • 应用场景:简化公式表达式,提升代码的可读性和维护性。

6. DROP 用于删除指定位置的数据

  • 描述:如 DROP(..., 1) 删除初始空值,确保输出结果从第一行开始。

  • 应用场景:清理数据集,去除不必要的数据。


方法一:使用 MAP + FILTER + OFFSET(简洁型)

=MAP(B2:B8,LAMBDA(x,LET(a,FILTER(OFFSET(B2:x,,1),B2:x=x),SUM(a)-TAKE(a,-1))))
📌

1️⃣ MAP(B2:B8, LAMBDA(x, ... ))

  • 作用:对 B2:B8 中的每一行(即每个单元格)依次执行一个自定义操作。

  • x 代表当前正在处理的那一行的值(比如第3行时,x = B3 的内容,如 "C")。

  • 相当于一个“循环”:遍历 B2、B3、…、B8,每次把当前值传给 x


2️⃣ LET(a, ..., SUM(a) - TAKE(a, -1))

  • 作用:定义一个临时变量 a,避免重复计算,提升可读性。

  • 最终返回 SUM(a) - TAKE(a, -1)


3️⃣ OFFSET(B2:x,,1)

  • 作用:动态获取从 B2 到当前行 x 所在行数量列(C列)数据

  • 虽然写的是 B2:x,但 OFFSET(...,,1) 表示向右偏移1列 → 实际取的是 C2:C?(对应当前行的 C 列区域)。

📌 举例:

  • 当处理第4行(x = B4)时:

  • B2:x 表示 B2:B4

  • OFFSET(B2:x,,1) → 相当于 C2:C4

⚠️ 注意:OFFSET 在这里被用作动态引用区域,配合 FILTER 使用。

4️⃣ FILTER(OFFSET(B2:x,,1), B2:x = x)

  • 作用:从 C2:C?(当前行及之前)中,筛选出与当前零件 x 相同的所有数量

  • 条件 B2:x = x:检查 B2 到当前行中哪些等于当前零件名。

📌 举例(假设当前行是第5行,x = "A"):

  • B2:B5 = ["C", "C", "A", "C"] → 不,等等,实际要看你的数据

  • 假设 B2:B5 = ["C", "C", "A", "A"]

  • x = "A"

  • B2:x = x → [FALSE, FALSE, TRUE, TRUE]

  • OFFSET(...,,1) → C2:C5 = [8, 10, 5, 8]

  • FILTER(C2:C5, [F,F,T,T]) → [5, 8]

  • 所以 a = {5; 8}


5️⃣ SUM(a) - TAKE(a, -1)

  • SUM(a):所有匹配数量的总和(包括当前行)

  • TAKE(a, -1):取数组 a最后一个元素(即当前行的数量)

  • 相减后 → 得到当前行之前的累计和

📌 继续上例:

  • a = {5; 8}

  • SUM(a) = 13

  • TAKE(a, -1) = 8(当前行的数量)

  • 结果 = 13 - 8 = 5 → 正确!这是“A”在第5行之前的累计(只有第3行的5)

对于首次出现的零件(如第一个 "C"):

  • a = {8}(只有当前行)

  • SUM(a) - TAKE(a,-1) = 8 - 8 = 0 → 符合要求!

特点:

  • 简洁直观,利用 MAP 对每一行独立处理

  • 使用 FILTER 获取同零件的所有历史数据

  • SUM(...)-TAKE(...,-1) 实现累计求和(排除当前行)

  • 依赖 OFFSET,适用于 Excel 365 环境

优点:
  • 代码简短,易于理解

  • 性能良好,适合中等规模数据集

缺点:
  • 需要正确处理 OFFSET 的引用逻辑


方法二:使用 MAP + SUMIFS + COUNTIF(直接计算型)

=MAP(B2:.B100,C2:.C100,LAMBDA(X,Y,LET(B,B2:X,C,C2:Y,IF(COUNTIF(B,X)=1,0,SUMIFS(C,B,X)-Y))))
📌

1️⃣ MAP(B2:B100, C2:C100, LAMBDA(X,Y, ... ))

作用:对 B2:B100 和 C2:C100 中的每一行(即每个零件和数量)依次执行一个自定义操作。

  • X 代表当前正在处理的那一行的零件值(如第3行时,X = B3 的内容,如 "C")。

  • Y 代表当前行的数量值(如 C3 = 10)。

  • 相当于一个“循环”:遍历 B2 到 B100,每次把当前行的零件和数量传给 X 和 Y。


2️⃣ LET(B, B2:X, C, C2:Y, IF(COUNTIF(B,X)=1, 0, SUMIFS(C,B,X)-Y))

作用:定义临时变量 B 和 C,分别表示从 B2 到当前行 X 的零件列和数量列,避免重复引用。

  • 最终返回:

  • 如果是该零件首次出现(COUNTIF=1),返回 0;

  • 否则,返回该零件此前所有出现次数的数量之和(SUMIFS 减去当前行数量 Y)。


3️⃣ COUNTIF(B, X)

作用:统计在区域 B(即 B2 到当前行)中,零件 X 出现的次数。

  • 若结果为 1 → 表示是第一次出现 → 返回 0;

  • 若大于 1 → 表示已有历史记录 → 进入累计逻辑。


4️⃣ SUMIFS(C, B, X)

作用:在数量列 C 中,筛选出所有零件等于 X 的行,并求和。

  • 包括当前行的数据,因此需要减去 Y 才能得到“历史累计”。


5️⃣ SUMIFS(C, B, X) - Y

作用:计算该零件在当前行之前的所有数量总和。

  • 示例:若某零件前两次出现数量为 8 和 10,当前为 7,则此表达式返回 18(8+10),即历史累计。


6️⃣ IF(COUNTIF(B,X)=1, 0, SUMIFS(C,B,X)-Y)

作用:综合判断是否首次出现。

  • 是 → 返回 0;

  • 否 → 返回历史累计和。

  • 完美实现“首次为0,后续为历史累计”的业务规则。

特点:

  • 直接利用 SUMIFSCOUNTIF 进行条件统计

  • COUNTIF 判断是否为首行,是则返回 0

  • SUMIFS(...)-Y 实现累计求和(排除当前行)

优点:
  • 不依赖动态数组函数,兼容性好

  • 直观易懂,便于调试

缺点:
  • 可能需要多次遍历数据,性能稍低


方法三:使用 REDUCE + VSTACK + FILTER(逐步累加型)

=DROP(REDUCE("",B2:B8,LAMBDA(X,Y,VSTACK(X,LET(B,Y,SUM(FILTER(C1:OFFSET(B,-1,1),B1:OFFSET(B,-1,)=B,0)))))),1)
📌

1️⃣ REDUCE("", B2:B8, LAMBDA(X, Y, ... ))

作用:对 B2:B8 中的每一行逐个处理,从初始值 "" 开始,通过累积方式构建一个结果数组。

  • X 是上一轮累积的结果(初始为空)

  • Y 是当前正在处理的零件值(如第3行时,Y = B3 = "C")

  • 每次迭代都将新计算出的值“堆叠”到 X 上,最终形成完整结果


2️⃣ VSTACK(X, ...)

作用:将当前累积结果 X 与新计算出的值垂直拼接(即“追加一行”)。

  • 例如:若 X 是 {0; 8},新值是 18,则 VSTACK 后变为 {0; 8; 18}

  • 这样逐步构建出整列的累计结果


3️⃣ LET(B, Y, SUM(FILTER(C1:OFFSET(B,-1,1), B1:OFFSET(B,-1,) = B, 0)))

作用:为当前零件 Y 计算其此前所有出现的数量之和(不含当前行)。

  • B 被设为当前零件值 Y(如 "C")

  • OFFSET(B, -1, 1) 表示:从当前单元格 B(即某行的 B 列)向上偏移 1 行、向右偏移 1 列 → 定位到 上一行的 C 列

  • 因此 C1:OFFSET(B,-1,1) 表示从 C1 到当前行上方一行的 C 列区域(即不包含当前行)

  • 同理,B1:OFFSET(B,-1,) 表示从 B1 到当前行上方一行的 B 列区域


4️⃣ FILTER(C1:OFFSET(B,-1,1), B1:OFFSET(B,-1,) = B, 0)

作用:在当前行之前的所有行中,筛选出零件等于当前零件 B 的那些数量。

  • 条件 B1:... = B:找出历史中同零件的行

  • 返回对应的 C 列数量值组成的数组

  • 若无匹配项,FILTER 返回错误,但因有第3参数 0,会返回 0(不过实际由外层 SUM 处理)


5️⃣ SUM(FILTER(...))

作用:对筛选出的历史数量求和。

  • 如果当前是该零件第一次出现,则 FILTER 结果为空或无匹配 → SUM 返回 0

  • 如果是第二次或之后出现,则返回此前所有同零件数量的总和


6️⃣ DROP(..., 1)

作用:去掉 REDUCE 生成结果中的第一个元素(即初始空值 "" 对应的占位行)。

  • 因为 REDUCE 从 "" 开始,VSTACK 第一次会把 "" 和第一个结果拼在一起 → 形成 {""; 0; 8; ...}

  • DROP(..., 1) 删除首行,使结果从真正的第一个数值开始,对齐 D2 单元格


整体逻辑总结:

该公式通过 REDUCE 逐行遍历零件列,对每个零件动态查看其上方所有行中同名零件的数量,并求和。首次出现时上方无同名数据,SUM=0;后续出现时自动累加历史值。最后用 DROP 去掉初始空行,得到与数据行数一致的累计结果列。

特点:

  • 逐行累积,使用 REDUCE 构建最终结果

  • 每次遇到新零件时,通过 FILTER 获取其历史数据并求和

  • DROP(...,1) 去除初始值

优点:
  • 适合需要逐行累积的应用场景

  • 结果自然形成动态数组

缺点:
  • 代码较长,不易于初学者理解

  • 多次调用 FILTER,可能影响性能

广东省
浏览 224
1
2
分享
2 +1
1 +1
全部评论