В новой выгрузке один заказ появился, другой пропал, у третьего изменилась сумма. Сравнивать строки по их месту в файле нельзя: порядок мог поменяться. В этой инструкции сопоставим записи по ID и получим четыре отдельных результата через штатный Compare Datasets в n8n.
Результат инструкции
Соберём ручной workflow на двух вымышленных снимках заказов. Старую выгрузку подключим к Input A, новую — к Input B. Сопоставлять будем по order_id. В конце увидим записи только в A, одинаковые записи, изменения и записи только в B. Никаких обновлений или удалений в базе схема не выполняет.
Результат находится в четырёх выходах Compare Datasets, а не в одном сводном JSON. Для основного примера в каждом выходе будет по одной записи. Изменённый заказ сохранит старое и новое значение суммы, включая исходный ноль. AI-модель и API-ключи не нужны.
Что понадобится
- Работающий n8n и доступ к новому учебному workflow. Проверено на n8n 2.42.4 и Compare Datasets версии 2.3.
- Code на JavaScript для подготовки и проверки входа. Реальные CRM, таблицы и базы подключать не нужно.
- Договор данных: order_id — строка из трёх цифр, name — непустой текст, amount_minor — целое неотрицательное число копеек.
Установить n8n локально через Docker
Шаг 1. Импортируйте готовую схему
Скачать n8n-compare-snapshots.json
Создайте отдельный workflow. В меню редактора выберите Import from File и загрузите JSON. В схеме шесть узлов и две ветки после Check Snapshots:
Manual Trigger → Sample Snapshots → Check Snapshots
Check Snapshots → Before Rows → Compare Snapshots [Input A]
Check Snapshots → After Rows → Compare Snapshots [Input B]
Compare Snapshots — штатный Compare Datasets. Остальные узлы кроме триггера — Code. Workflow неактивен, credentials и ID чужой схемы не содержит. Не добавляйте узлы удаления, обновления CRM и отправки отчёта: сначала проверим само сравнение.
Шаг 2. Подготовьте старую и новую выгрузки
Скачать учебные данные n8n-compare-demo.json
Файл дан для сверки, автоматически узлом не загружается. В Sample Snapshots выберите Mode: Run Once for All Items, Language: JavaScript. Вставьте:
const before = [
{"order_id":"001","name":"Альфа","amount_minor":150000},
{"order_id":"002","name":"Бета","amount_minor":0},
{"order_id":"003","name":"Гамма","amount_minor":50000}
];
const after = [
{"order_id":"001","name":"Альфа","amount_minor":150000},
{"order_id":"002","name":"Бета","amount_minor":20000},
{"order_id":"004","name":"Дельта","amount_minor":70000}
];
return [{ json: { before, after } }];
Выполните источник от Manual Trigger. Ожидается один item с массивами before и after. В каждом массиве три заказа. ID 001 — строка с ведущими нулями, сумма у 002 в старом снимке — число 0. По данным должно получиться:
| ID | В старом снимке | В новом снимке |
|---|---|---|
| 001 | Альфа, 150000 копеек | Альфа, 150000 копеек |
| 002 | Бета, 0 копеек | Бета, 20000 копеек |
| 003 | Гамма, 50000 копеек | Нет |
| 004 | Нет | Дельта, 70000 копеек |
Сопоставляем по order_id, а не по имени клиента: у одного клиента могут быть разные заказы. Для реального источника сначала определите надёжный ключ. В этой инструкции значение ID уникально внутри каждого снимка, но один и тот же ID закономерно встречается в обоих.
Шаг 3. Проверьте оба снимка до сравнения
В Check Snapshots задайте Run Once for All Items, JavaScript и вставьте:
const items = $input.all();
if (items.length !== 1) throw new Error('one_snapshot_item_required');
const source = items[0].json;
for (const side of ['before', 'after']) {
const rows = source[side];
if (!Array.isArray(rows)) throw new Error(side + '_array_required');
if (rows.length > 20) throw new Error(side + '_too_many_rows');
const ids = new Set();
for (const row of rows) {
if (!row || typeof row !== 'object' || Array.isArray(row) || Object.keys(row).sort().join('|') !== 'amount_minor|name|order_id') {
throw new Error(side + '_row_fields_invalid');
}
if (typeof row.order_id !== 'string' || !/^\d{3}$/.test(row.order_id) || ids.has(row.order_id)) {
throw new Error(side + '_id_invalid_or_duplicate');
}
if (typeof row.name !== 'string' || !row.name.trim()) throw new Error(side + '_name_required');
if (!Number.isSafeInteger(row.amount_minor) || row.amount_minor < 0) {
throw new Error(side + '_amount_invalid');
}
ids.add(row.order_id);
}
}
return [{ json: { before: source.before, after: source.after } }];
Проверка допускает пустой массив, но отклоняет null, объект вместо массива, больше 20 записей и повтор ID в одном снимке. В каждой записи должны быть ровно три согласованных поля. Текстовая сумма, отрицательное число, дробные копейки и число вне безопасного целочисленного диапазона не пройдут.
Двадцать записей — выбранный учебный лимит, не ограничение Compare Datasets. Для реальной выгрузки отдельно согласуйте набор полей: техническое updated_at не должно случайно становиться бизнес-изменением, но удалять его из сравнения без понимания тоже нельзя.
В Settings всех узлов оставьте On Error: Stop Workflow. Always Output Data, Continue On Fail, Retry On Fail и Execute Once не включайте. При нарушении договора нельзя переходить к сравнению неполных или неоднозначных записей.
Шаг 4. Превратите старый снимок в items
В Before Rows выберите Run Once for All Items, JavaScript и вставьте:
const rows = $input.first().json.before;
return rows.length ? rows.map(json => ({ json })) : [{ json: {} }];
Для основного примера узел выдаст три items, по одному заказу в каждом. Это важное преобразование: Compare Datasets должен получать отдельные записи, а не один объект с вложенным массивом before.
Для пустого массива код выдаёт один технический пустой объект. Он нужен, чтобы ветка выполнилась и дошла до сравнения. В проверенной версии 2.3 Compare Datasets исключает такой пустой объект из данных. В результирующих выходах он не должен появляться как заказ.
Шаг 5. Подготовьте новый снимок и подключите входы
В After Rows задайте Run Once for All Items, JavaScript и вставьте:
const rows = $input.first().json.after;
return rows.length ? rows.map(json => ({ json })) : [{ json: {} }];
Соедините выход Check Snapshots с обоими Code-узлами. Выход Before Rows подключите именно к Input A узла Compare Snapshots, выход After Rows — к Input B. У Compare Datasets два отдельных входа; подключение обеих веток к одному входу не соответствует нашей задаче.
После полного запуска Before Rows и After Rows должны выдавать по три items. Для пустой ветки ожидается один технический пустой объект до Compare, но ноль записей после его фильтрации. Работа с пустыми снимками проверена отдельно, включая случай, когда оба массива пустые.
Шаг 6. Настройте сопоставление по ID
В Compare Snapshots в Fields to Match добавьте одну пару: Input A Field — order_id, Input B Field — order_id. Введите имена полей как обычный текст, не как выражения со значением конкретного заказа.
Сначала узел находит записи с одинаковым ключом, затем сравнивает их остальные поля. Если вместо order_id сопоставить name с order_id, нужные пары не найдутся: заказы могут разойтись в выходы только A и только B, хотя выполнение завершится успешно. Зелёного статуса недостаточно.
Не добавляйте amount_minor в Fields to Match для этой задачи. Иначе изменившаяся сумма может помешать найти пару одного и того же заказа. Ключ отвечает на вопрос «это та же запись?», остальные поля — на вопрос «что у неё изменилось?».
Шаг 7. Сохраните обе версии изменения
В When There Are Differences выберите Include Both Versions. Fuzzy Compare оставьте Off. В Options задайте Multiple Matches: Include First Match Only, Fields to Skip Comparing оставьте пустым, Disable Dot Notation — Off.
Мы явно задали режим, а не полагаемся на настройки по умолчанию. Повторы ID всё равно отклоняются раньше в Check Snapshots: Include First Match Only не используется как способ молча убрать дубли. Суммы и типы должны соответствовать договору до сравнения.
Не добавляйте amount_minor в Fields to Skip Comparing. В нашем тесте это скрыло единственное изменение заказа 002, и он попал в Same. Для реального процесса пропускайте только поля, которые действительно не влияют на требуемое сравнение.
Шаг 8. Выполните схему и откройте четыре выхода
Нажмите Execute Workflow. Откройте Output узла Compare Snapshots и последовательно просмотрите четыре именованных выхода. В основном примере ожидается:
| Выход | Число записей | Что должно быть |
|---|---|---|
| In A only | 1 | 003: нет в новой выгрузке |
| Same | 1 | 001: данные совпали |
| Different | 1 | 002: сумма 0 → 20000 |
| In B only | 1 | 004: новая запись |
Same для заказа 001 содержит исходный объект старого снимка:
{
"order_id": "001",
"name": "Альфа",
"amount_minor": 150000
}
Здесь четыре результата относятся к разным выходам одного узла, не к четырём items одного общего массива. Не подключайте все выходы к одному следующему действию без сохранения смысла ветки. Эта инструкция заканчивается просмотром различий.
Шаг 9. Разберите изменённую запись
В выходе Different у заказа 002 ожидается такой объект:
{
"keys": {
"order_id": "002"
},
"same": {
"order_id": "002",
"name": "Бета"
},
"different": {
"amount_minor": {
"inputA": 0,
"inputB": 20000
}
}
}
keys содержит ключ сопоставления. same — поля, которые совпали, включая ID и имя. В different.amount_minor.inputA лежит старая сумма 0, в inputB — новая 20000. Это значения в копейках; 20000 копеек — 200 рублей.
Include Both Versions не выбирает победителя и не обновляет систему. Если переключиться на Use Input B Version, Different будет содержать новую запись без такого подробного сравнения. Для нашей инструкции сохраните обе версии, чтобы видеть, откуда и куда изменилось значение.
Шаг 10. Проверьте независимость от порядка строк
В Sample Snapshots временно переставьте строки в обоих массивах в обратном порядке. Выполните всю схему. Заказ 001 должен остаться в Same, 002 — в Different, 003 — только в A, 004 — только в B. Мы сравниваем ключи, а не позиции.
Верните порядок. Затем временно сделайте after точной копией before. Ожидаются три записи в Same и ноль в остальных выходах. Нулевое количество Different в таком тесте — правильный результат, не ошибка. После проверки верните исходный after из шага 2.
Шаг 11. Проверьте пустые выгрузки
Временно задайте before пустым массивом [], сохранив исходный after. Ожидаются три записи только в B, а остальные выходы пустые. Затем верните before и сделайте after пустым: три записи должны оказаться только в A.
Если оба массива пустые, все четыре выхода должны содержать ноль записей. Compare Snapshots при этом должен выполниться, а технические пустые объекты из Before Rows и After Rows не должны попасть в результат. После теста восстановите оба массива.
Пустой массив и недоступная выгрузка — не одно и то же. Код принимает [] как явно переданный пустой снимок, но отклоняет null и объект. В реальной интеграции нельзя превращать сетевую ошибку или обрезанную пагинацию в «новая база пуста». Проверку полноты источника нужно сделать до этого workflow.
Шаг 12. Проверьте дубли и восстановите настройки
В Sample Snapshots временно добавьте в before вторую запись с order_id 001. Check Snapshots должен остановиться с before_id_invalid_or_duplicate ещё до сравнения. Удалите дубль. Если заменить amount_minor нового заказа на строку, например "20000", ожидается after_amount_invalid, а не приблизительное сравнение.
Проверьте направление A/B: если поменять входы местами, старое и новое значение в Different тоже поменяются местами, а записи 003 и 004 окажутся в противоположных выходах. Верните Before Rows к A и After Rows к B.
Сохраните исходные данные, order_id с обеих сторон, Include Both Versions, Fuzzy Compare Off и пустой Fields to Skip Comparing. Выполните ещё раз и сверьте все четыре выхода с шагом 8. В схеме по-прежнему не должно быть действий записи или удаления данных.
Если результат отличается
| Симптом | Что проверить | Ожидаемое условие |
|---|---|---|
| before_id_invalid_or_duplicate | ID старого снимка | Уникальная строка из трёх цифр |
| after_amount_invalid | Тип и значение новой суммы | Безопасное целое число копеек |
| Всё только в A и B | Fields to Match | order_id с обеих сторон |
| 002 попал в Same | Fields to Skip Comparing | amount_minor не пропущен |
| Новые и пропавшие перепутаны | Подключение входов | Старое A, новое B |
Почему In A only не означает «удалить»
Заказ может отсутствовать в новой выгрузке из-за фильтра, прав доступа, диапазона дат, ошибки API или неполной загрузки страниц. Наш пример сравнивает переданные снимки, но не доказывает полноту новой выгрузки. Выход In A only — повод проверить причину, не разрешение на удаление заказа в CRM.
Прототип не записывает историю, не синхронизирует системы и не разрешает конфликты за пользователя. Перед автоматическими изменениями нужны подтверждение полноты данных, правила приоритетов, журнал действий и возможность восстановления. Дубли ID нельзя скрывать произвольным выбором первой записи.
Прочитать CSV и проверить типы полей
Убрать дубли по согласованному ключу
Остановить workflow при неверных данных
Что проверено редакцией
17 октября 2026 года workflow проверен на n8n 2.42.4 в отдельном Docker-экземпляре ручными запусками API. Все 35 тестов прошли: основной пример, порядок строк, одинаковые и полностью разные записи, отсутствие общих ID, пустые снимки, одна запись, изменения имени и суммы, нули, дубли и неверные поля, типы и размеры. Отдельно проверены перепутанные входы, пропуск суммы, неверный ключ, выбор версии B и отсутствие пары сопоставления. Сверены точные данные всех четырёх выходов, обе версии изменения и отсутствие технических пустых записей. Основной запуск использует точный скачиваемый workflow. Compare Datasets 2.3 выполнялся один раз за запуск. Настройки сверены с официальной документацией и кодом узла. Редакторские клики, Cloud, реальные источники, пагинация, большие объёмы, запись в базы и удаление не тестировались. Учебный контейнер остановлен, существующие workflows не менялись. Обложка — редакционная AI-иллюстрация, не снимок реального интерфейса.
Частые вопросы
Почему нельзя сравнивать по номеру строки?
Порядок строк в двух выгрузках может отличаться. В примере записи сопоставляются по order_id, поэтому перестановка строк не меняет их принадлежность к выходам.
Где один итоговый JSON со всеми изменениями?
В этой схеме его нет. Штатный Compare Datasets возвращает четыре отдельных выхода: только A, одинаковые, разные и только B. Их нужно проверять по отдельности.
Зачем отклонять дубли, если есть Multiple Matches?
Режим выбора совпадений не объяснит, какая из конфликтующих записей правильная. В этой инструкции договор требует один ID на запись внутри каждого снимка, поэтому дубли останавливают сравнение.
Что получится, если новая выгрузка пустая?
Все старые записи окажутся в In A only. Это правильно для переданных данных, но не подтверждает, что источник действительно пуст: сначала нужно исключить ошибку загрузки и неполную выгрузку.
Можно автоматически удалять записи из In A only?
Не по этой инструкции. Отсутствие в снимке не доказывает необходимость удаления. Сначала проверьте полноту данных и правила процесса, затем предусмотрите подтверждение и восстановление.