Merge в n8n присоединил чужую цену: объединяем items по ключу
Одна ветка n8n получает заказы, другая — рассчитанную стоимость. В первой items идут A, B, во второй API вернул B, A. Merge по позиции приклеивает стоимость B к заказу A. Узел остаётся зелёным, поля присутствуют, а ошибка обнаруживается уже в счёте.
Позиция в массиве имеет смысл только тогда, когда обе ветки гарантированно сохраняют один порядок и одинаковое число элементов. Для независимых API такой гарантии обычно нет. Объединяйте данные по устойчивому ключу и проверьте, что он действительно определяет одну запись с каждой стороны.
Сначала найдите ключ нужной сущности
Для одного итога на заказ подходит order_id. Для отдельных позиций заказа этого мало: понадобится line_id либо составной ключ заказа и позиции. Если соединять несколько товарных строк по одному order_id, вы получите несколько совпадений и сможете размножить строки.
Приведите тип ключа до Merge. Строка «0012» может быть значимым идентификатором; преобразование в число уничтожит нули. Согласуйте формат на входе обеих веток, не включая нечёткое сравнение ради случайно совпавшего теста. Пустой ключ или отсутствующий ID должен попасть в разбор данных до формирования счёта.
Соберите маленький workflow без внешних API
Добавьте Manual Trigger и две параллельные Code node в режиме Run Once for All Items. Первую назовите Orders и вставьте первый фрагмент, вторую — Prices со вторым фрагментом. Оба узла возвращают настоящий формат n8n items: массив объектов с полем json.
return [
{ json: { order_id: "A", customer: "Анна" } },
{ json: { order_id: "B", customer: "Борис" } },
];return [
{ json: { order_id: "B", quoted_price: 900 } },
{ json: { order_id: "A", quoted_price: 1500 } },
];Подключите Orders к Input 1 узла Merge, Prices к Input 2. Выберите Mode: Combine, Combine By: Matching Fields, Fields to Match: order_id. Для этого примера задайте Output Type: Keep Matches и Output Data From: Both Inputs Merged Together. Порядок Prices специально противоположный: он должен перестать влиять на соответствие заказов и цен.
Эквивалентные параметры узла приведены ниже. В существующем workflow проверьте версию Merge и названия настроек в интерфейсе; старые версии узла могут отличаться. Если поля на входах называются по-разному, включите Fields To Match Have Different Names и задайте левое и правое поле явно.
{
"mode": "combine",
"combineBy": "combineByFields",
"fieldsToMatchString": "order_id",
"joinMode": "keepMatches",
"outputDataFrom": "both",
"options": {}
}Проверьте соответствие после Merge
Добавьте ещё одну Code node после Merge, также Run Once for All Items. Она проверит, что вы получили ровно два разных заказа и у каждого своя стоимость. Ошибка остановит тестовый workflow до любого внешнего действия. Никаких credentials для этого примера не требуется.
const rows = $input.all();
const expected = new Map([["A", 1500], ["B", 900]]);
const ids = rows.map(item => item.json.order_id);
if (rows.length !== expected.size || new Set(ids).size !== ids.length) {
throw new Error("Missing or duplicate orders after Merge");
}
for (const item of rows) {
const { order_id, quoted_price } = item.json;
if (!expected.has(order_id) || quoted_price !== expected.get(order_id)) {
throw new Error("Wrong price for order " + order_id);
}
}
return rows;Ожидаются A с ценой 1500 и B с ценой 900, независимо от порядка строк на выходе. Затем временно переключите Combine By на Position: проверка должна завершиться ошибкой цены. Так тест показывает, что проверяет соответствие, а не только наличие двух объектов.
Решите, что делать с отсутствующими совпадениями
Keep Matches отдаёт только пары, которые нашлись с обеих сторон. Если Prices потеряет заказ B, этот заказ исчезнет из результата. Для формирования счёта такое поведение может быть недопустимо. Выберите Enrich Input 1, когда требуется сохранить все заказы и отдельно обработать отсутствующую цену. После узла направьте записи без quoted_price в ветку уточнения, до отправки клиенту.
При нескольких ценах на один order_id сначала определите, какая запись действующая: например, по версии расчёта. Настройка Include First Match Only выбирает один результат, но сама не объясняет, почему именно эта цена правильная. Лучше отклонить неоднозначные входы или предварительно отобрать нужную версию явным правилом.
Не давайте обоим входам одинаковое имя price для разных величин, например заказа и доставки. Задайте quoted_price и delivery_price либо настройте разрешение конфликтов полей осознанно. Совпадение ключа не решает, какое из конфликтующих значений должен получить следующий узел.
Верните реальные данные постепенно
Сначала замените только Orders настоящим источником и сохраните контрольный вывод ключей. Затем подключите Prices и сравните число уникальных ID на каждом входе. Зафиксируйте неизвестные ключи, пропуски и повторения до Merge. Это позволяет различить проблему объединения и неполный ответ внешнего API.
Оставьте переставленные входы как fixture регрессионного теста. Добавьте ещё три случая: пропущенная цена, повтор order_id и строковый ID с ведущими нулями. Небольшой набор таких данных обнаружит ошибочную смену режима Merge раньше, чем проверка одного счастливого запуска с одинаковым порядком массивов.
Проверьте весь workflow до внешней записи
После правильного объединения потребуется обрабатывать неполные ответы API и проверять связь items на остальных шагах. В платном курсе «Продвинутый n8n» вы отработаете item linking, API-интеграции, ограниченные повторы и sub-workflows. Блоки тестирования на fixtures и контролируемых сбоях помогут сохранить соответствие данных при развитии автоматизации, а наблюдаемость — разбирать ошибки уже работающего процесса. Откройте программу платного курса «Продвинутый n8n: production-автоматизации и AI-агенты».
Продолжить на практике
- Продвинутый n8n: production-автоматизации и AI-агенты ↗
Продвинутый курс по n8n: надёжные интеграции, AI-агенты, RAG, тестирование и self-hosted production.
