在 n8n 建構複雜工作流程時,Merge 節點和 Loop Over Items 是兩個非常強大的工具。Merge 可以合併多個資料來源,Loop 則讓你能夠逐項處理資料。但當這兩個節點一起使用時,卻容易踩到一些令人困惑的陷阱。
我最近在處理一個需要合併多個資料源並迴圈處理的流程時,遇到了兩個看似不可思議的問題:明明預期會合併成一批資料,結果卻被分成多批;明明資料的 ID 都對得上,Join 卻沒有成功。
這篇文章記錄這些問題的根本原因和解決方案,幫助你避開這些陷阱。
問題一:Merge 輸出資料被分成多批
問題描述
假設我有 5 個活動資料需要處理,流程設計如下:
活動資料 (5 筆) ↓IF 節點分流 ↓分支 A (2 筆) → 處理 → Merge ↓分支 B (3 筆) → 處理 → Merge預期結果:Merge 節點輸出 1 批,包含 5 筆資料
實際結果:Merge 節點輸出 2 批,第一批 2 筆,第二批 3 筆
讓我們用表格對比一下預期與實際的差異:
| 情況 | 預期輸出 | 實際輸出 | 問題 |
|---|---|---|---|
| 5 個活動經過 IF 分流 | 1 批資料(5 筆) | 2 批資料(2 筆 + 3 筆) | ❌ 資料被分批 |
| 後續節點處理 | 一次處理完所有資料 | 需要分兩次處理 | ⚠️ 流程複雜化 |
原因:n8n v1 的深度優先執行順序
這個問題的根源在於 n8n v1 的執行順序是深度優先(Depth-First),而不是我們直覺認為的廣度優先。
實際執行流程:
IF 節點分流 5 個活動(2 個走分支 A,3 個走分支 B) ↓n8n 先「完整」跑完分支 A(2 個) ↓ 到達 Merge → 輸出第一批 [2 筆]
然後才跑分支 B(3 個) ↓ 到達 Merge → 輸出第二批 [3 筆]Merge 節點的 Append 模式不會「等待」所有分支的資料到齊,而是有資料進來就立即輸出。因為 n8n 是深度優先執行,所以第一個分支完成時 Merge 就會輸出一次,第二個分支完成時再輸出一次。
重要觀念n8n v1 是深度優先執行:先完整跑完一個分支到底,再回來跑下一個分支。分支的執行順序由畫布上的位置決定(從上到下、從左到右)。
解決方案
方法一:用 Loop Over Items 包住整個流程(推薦)
這是最可靠的解決方案。透過將整個流程包在 Loop Over Items 中,強制讓每一筆資料都完整走完所有分支後,才處理下一筆資料。
正確的工作流程架構:
資料來源 (5 筆) ↓Loop Over Items (batch size: 1) ← 新增 ↓ (每次只處理 1 筆)IF 判斷分流 ↓各自的處理流程 ↓回到 Loop Over Items ↓Loop 的 "done" 輸出 → 自動收集所有 5 筆 ↓後續處理(排序等)優勢:
- ✅ Loop 會自動收集所有迭代的結果
- ✅ 保證輸出是單一批次
- ✅ 執行順序可預測
- ✅ 不需要額外的 Merge 或 Aggregate 節點
為什麼不能用 Aggregate 解決?
你可能會想:「在 Merge 後面加個 Aggregate 不就好了?」
分支 A → Merge → Aggregate → 單一批次輸出? ↑分支 B ──┘❌ 這個方法無效!
因為 Aggregate 本身也會被執行多次。當第一批資料(2 筆)到達 Aggregate 時,Aggregate 就會執行並輸出;當第二批資料(3 筆)到達時,Aggregate 又會再執行一次。所以你還是會得到 2 批資料,只是每批被「打包」成單一項目而已。
執行順序:
| 步驟 | 發生的事 | Aggregate 輸出 |
|---|---|---|
| 1 | 分支 A 完成 → Merge 輸出第一批(2 筆) | Aggregate 執行 → 輸出第 1 個打包項目 |
| 2 | 分支 B 完成 → Merge 輸出第二批(3 筆) | Aggregate 再執行 → 輸出第 2 個打包項目 |
| 結果 | ❌ 還是 2 批,只是變成 2 個打包項目 | 並沒有合併成單一批次 |
重要提醒Aggregate 無法解決「分批」問題,因為它本身也會隨著輸入的批次而被多次執行。唯一可靠的解法是使用 Loop Over Items 包裝整個流程。
最佳實踐建議如果你的流程需要確保所有資料合併成單一批次,必須使用 Loop Over Items 包裝整個流程。這是唯一穩定且可預測的做法,沒有其他捷徑。
問題二:Merge Join 時部分資料沒匹配到
問題描述
我想使用 Merge 節點的 Combine 模式,透過 category_id 欄位來 join 兩個資料來源:
活動資料(含 category_id) ↓類別資料(含 category_id) → Merge (Combine mode, join by category_id) ↓期望:活動資料 + 對應的類別資訊但實際執行後,有些活動明明有對應的 category_id,卻沒有成功合併到類別資訊。
根本原因:Merge 放在 Loop 內,但 Input 2 在 Loop 外
這是最容易被忽略,但影響最嚴重的問題! 大多數 Join 失敗的情況都源於這個架構錯誤。
錯誤架構示意
❌ 錯誤架構
類別資料 (Loop 外,只執行 1 次) ──┐ ↓ Input 2Loop Over Items │ ↓ (loop) │ 處理活動... ─────────────────→ Merge (在 Loop 內) ↓ ↑ Input 1 回到 Loop問題分析:Loop 每次迭代時發生了什麼?
| Loop 迭代 | Input 1 (活動資料) | Input 2 (類別資料) | Merge 結果 | 原因 |
|---|---|---|---|---|
| 第 1 次 | ✅ 活動 1 | ✅ 全部類別資料 | ✅ 合併成功 | 類別資料還在 |
| 第 2 次 | ✅ 活動 2 | ❌ 空的 | ❌ 沒東西可 join | 類別資料被第 1 次消耗掉了 |
| 第 3 次 | ✅ 活動 3 | ❌ 空的 | ❌ 沒東西可 join | 類別資料被第 1 次消耗掉了 |
| 第 4 次 | ✅ 活動 4 | ❌ 空的 | ❌ 沒東西可 join | 類別資料被第 1 次消耗掉了 |
| 第 5 次 | ✅ 活動 5 | ❌ 空的 | ❌ 沒東西可 join | 類別資料被第 1 次消耗掉了 |
為什麼類別資料會被「消耗」掉?
因為類別資料的節點在 Loop 外面,只會執行一次。當 Loop 第一次迭代時,Merge 節點會取得並「使用」這份類別資料。但在第二次迭代時,n8n 不會重新執行 Loop 外的節點,所以 Input 2 就變成空的了。
解決方案:把 Merge 移到 Loop 外
正確的架構應該是:
✅ 正確架構
類別資料 (只取 1 次) ─────────────┐ ↓ Input 2Loop Over Items │ ↓ (loop) │ 處理活動... │ ↓ │ 回到 Loop │ ↓ │Loop "done" 輸出 (全部活動) ──────→ Merge (在 Loop 外) ↓ ↑ Input 1後續處理...為什麼這樣可以運作?
| 元素 | 執行次數 | 資料狀態 | 結果 |
|---|---|---|---|
| 類別資料節點 | 1 次 | 完整保留,不會被消耗 | ✅ |
| Loop 處理活動 | 5 次(每筆一次) | 每次處理一筆活動 | ✅ |
| Loop “done” 輸出 | 1 次 | 輸出全部 5 筆活動 | ✅ |
| Merge | 1 次 | 5 筆活動 × 完整類別資料 | ✅ 全部都能 join |
這樣的架構確保:
- ✅ 類別資料只取一次,完整保留不會被消耗
- ✅ 活動資料經過 Loop 處理後一次輸出全部
- ✅ Merge 在 Loop 外執行,能夠正確 join 所有資料
重要原則Loop 內需要重複使用的資料,來源可以放 Loop 外,但 Merge 必須放 Loop 外!
如果把 Merge 放在 Loop 內,Loop 外的資料只會在第一次迭代時有效,後續迭代會失去資料。
補充:其他可能的 Join 失敗原因
如果你已經確認架構正確(Merge 在 Loop 外),但 Join 還是失敗,可以檢查以下項目:
資料類型不一致
有些情況下,兩邊要 join 的欄位資料類型可能不一致:
| 來源 | category_id 型別 | 範例值 |
|---|---|---|
| Google Sheets | string(字串) | "1", "2", "3" |
| API 回傳 | number(數字) | 1, 2, 3 |
當一邊是 "1" (string),另一邊是 1 (number) 時,Merge 會認為它們不匹配("1" !== 1)。
解決方法:開啟 Fuzzy Compare
Merge 節點提供了 Fuzzy Compare 功能來自動處理型別差異:
- 打開 Merge 節點
- 展開 Options 區塊
- 開啟 Fuzzy Compare ✅
或者手動統一型別:
// ✅ 統一轉換為 number{{ Number($json.category_id) }}
// 或統一轉換為 string{{ String($json.category_id) }}其他檢查項目
- 欄位名稱拼寫:確認兩邊的欄位名稱完全一致(包含大小寫)
- 空值處理:確認資料中沒有
null或undefined值 - 前後空白:字串值可能有前後空白(如
" 1 "vs"1")
除錯建議在 Merge 前加一個 Code 節點,用
console.log()檢查兩邊要 join 的欄位值和型別,可以快速找出不匹配的原因。
重要原則總結
原則 1:理解 n8n v1 執行順序
n8n v1 是**深度優先(Depth-First)**執行:先完整跑完一個分支到底,再回來跑下一個分支。
執行順序受什麼影響?
- 畫布上的節點位置(從上到下、從左到右)
- 分支的繪製順序
實務建議:
- 如果需要精確控制執行順序,使用 Loop Over Items 包裝
- 不要假設分支會「同時」執行
原則 2:Loop + Merge 的正確搭配
正確的架構模式:
✅ 正確模式
Loop 外的資料 ──┐ ↓Loop 處理中... │ │Loop done ─────→ Merge (Loop 外) │ └──→ 後續處理❌ 錯誤模式
Loop 外的資料 ──→ Merge (Loop 內) ← 只有第一次迭代有資料 ↑Loop 處理中... ──┘記憶口訣:
Loop 內需要重複使用的資料,來源可以放 Loop 外,但 Merge 必須放 Loop 外!
原則 3:Join 前檢查資料類型
在使用 Merge Combine 模式進行 join 時:
必做檢查清單:
- 兩邊要 join 的欄位資料型別是否一致(string vs number)
- 是否需要開啟 Fuzzy Compare
- 是否需要在 Merge 前統一轉換型別
型別統一方法:
// ✅ 方法 1:在 Set 節點或 Code 節點中轉換{{ Number($json.category_id) }}
// ✅ 方法 2:開啟 Merge 的 Fuzzy Compare 選項除錯技巧
遇到 Merge 或 Loop 的問題時,可以使用以下技巧快速定位:
技巧 1:檢查 Merge 的兩個 Input
點開 Merge 節點,分別查看 Input 1 和 Input 2 的資料:
- 確認兩邊的資料筆數
- 確認要 join 的欄位值和型別
- 確認資料是否為空
如何檢查:
- 點擊 Merge 節點
- 在右側面板點選 “Input 1” 或 “Input 2”
- 查看資料內容和結構
技巧 2:觀察節點執行次數
如果 Merge 節點被執行了多次,代表資料是分批進來的:
- 查看節點左上角的執行次數標記
- 如果看到 “1/2”、“2/2” 這樣的標記,代表 Merge 執行了 2 次
- 這通常代表你遇到了「問題一」的情況
技巧 3:使用 Sticky Note 標註
在複雜流程中,使用 Sticky Note(便利貼)標註哪些節點在 Loop 內、哪些在 Loop 外:
┌─────────────────────┐│ Loop 外的節點 ││ - 類別資料取得 │└─────────────────────┘
┌─────────────────────┐│ Loop 內的節點 ││ - IF 判斷 ││ - 資料處理 │└─────────────────────┘
┌─────────────────────┐│ Loop 外的節點 ││ - Merge ││ - 最終處理 │└─────────────────────┘這樣可以幫助你和團隊快速理解流程架構,避免踩坑。
希望這篇文章能幫助你避開 n8n Merge + Loop 的常見陷阱!如果你在使用 n8n 時遇到其他問題,歡迎交流討論。
參考資料:
回報錯字、失效連結,或告訴我你想看的延伸主題。