К статье
Практический материал

Разработка и Конфигурация · 12 мин

Как сравнить две табличные части документов в 1С

Способы сравнения двух табличных частей документов в 1С: перебор строк, запросы, свёртка таблиц значений. Готовые примеры кода, типичные ошибки и ответы на вопросы.

Сравнение табличных частей двух документов — типовая задача при доработке конфигураций: контроль изменений перед проведением, сверка заказа с реализацией, поиск расхождений между версиями документа. Универсальной встроенной функции «сравнить табличные части» в платформе нет, поэтому задачу решают несколькими способами — от простого перебора строк до запросов к базе данных.

Выбор метода зависит от того, где находятся данные: в памяти (объекты документов, таблицы значений) или в базе, а также от того, что считать результатом — факт расхождения или список различающихся строк. Ниже разобраны рабочие подходы с примерами кода на встроенном языке 1С.

Важно сразу определить ключ сравнения — набор колонок, по которым строка одной таблицы сопоставляется строке другой. Чаще всего это «Номенклатура + Характеристика», но в зависимости от задачи в ключ могут входить серия, склад, ставка НДС и другие реквизиты.

Определяем ключ сравнения и формат результата

Прежде чем писать код, ответьте на два вопроса. Первый — по каким колонкам строки считаются «одинаковыми». Если сравнивать только по номенклатуре, две строки с одним товаром, но разными характеристиками будут ошибочно объединены. Если включить в ключ слишком много колонок, строки не найдут пару, и весь результат «развалится» на добавленные и удалённые.

Второй вопрос — что считать различием. Варианты: строка есть в первой таблице, но отсутствует во второй (удалена); строка появилась только во второй (добавлена); строка есть в обеих, но отличаются значения количества, цены или суммы (изменена). Для учётных задач обычно нужны все три категории.

Результат удобно оформлять таблицей значений с колонками «ВидИзменения», ключевыми полями и старыми/новыми значениями. Такую таблицу легко вывести пользователю или обработать программно.

Способ 1. Перебор строк с соответствием

Самый простой вариант подходит для табличных частей до нескольких сотен строк. Идея: выгрузить вторую табличную часть в соответствие, где ключ — строковое представление ключевых колонок, а значение — строка таблицы. Затем перебрать первую таблицу и искать каждую строку в соответствии.

Функция СравнитьТабличныеЧасти(ТЧ1, ТЧ2) Экспорт

Результат = Новый ТаблицаЗначений;

Результат.Колонки.Добавить("ВидИзменения");

Результат.Колонки.Добавить("Номенклатура");

Результат.Колонки.Добавить("КоличествоБыло");

Результат.Колонки.Добавить("КоличествоСтало");

// Индексируем вторую таблицу

Индекс = Новый Соответствие;

Для Каждого СтрокаТЧ Из ТЧ2 Цикл

Ключ = Строка(СтрокаТЧ.Номенклатура.УникальныйИдентификатор());

Индекс.Вставить(Ключ, СтрокаТЧ);

КонецЦикла;

// Ищем изменённые и удалённые строки

Найденные = Новый Соответствие;

Для Каждого СтрокаТЧ Из ТЧ1 Цикл

Ключ = Строка(СтрокаТЧ.Номенклатура.УникальныйИдентификатор());

Строка2 = Индекс[Ключ];

Если Строка2 = Неопределено Тогда

НоваяСтрока = Результат.Добавить();

НоваяСтрока.ВидИзменения = "Удалена";

НоваяСтрока.Номенклатура = СтрокаТЧ.Номенклатура;

НоваяСтрока.КоличествоБыло = СтрокаТЧ.Количество;

Иначе

Найденные.Вставить(Ключ, Истина);

Если СтрокаТЧ.Количество <> Строка2.Количество Тогда

НоваяСтрока = Результат.Добавить();

НоваяСтрока.ВидИзменения = "Изменена";

НоваяСтрока.Номенклатура = СтрокаТЧ.Номенклатура;

НоваяСтрока.КоличествоБыло = СтрокаТЧ.Количество;

НоваяСтрока.КоличествоСтало = Строка2.Количество;

КонецЕсли;

КонецЕсли;

КонецЦикла;

// Ищем добавленные строки

Для Каждого СтрокаТЧ Из ТЧ2 Цикл

Ключ = Строка(СтрокаТЧ.Номенклатура.УникальныйИдентификатор());

Если Найденные[Ключ] = Неопределено Тогда

НоваяСтрока = Результат.Добавить();

НоваяСтрока.ВидИзменения = "Добавлена";

НоваяСтрока.Номенклатура = СтрокаТЧ.Номенклатура;

НоваяСтрока.КоличествоСтало = СтрокаТЧ.Количество;

КонецЕсли;

КонецЦикла;

Возврат Результат;

КонецФункции

Обратите внимание: в качестве ключа используется уникальный идентификатор ссылки, а не наименование. Сравнение по наименованию ненадёжно — представление может измениться или совпадать у разных элементов. Если ключ составной, идентификаторы конкатенируются через разделитель.

Способ 2. Таблицы значений и НайтиСтроки

Если табличные части нужно сравнивать многократно или по разным ключам, удобнее выгрузить их в таблицы значений через метод Выгрузить() и использовать НайтиСтроки с отбором. Чтобы поиск не выполнялся полным перебором, добавьте индекс по ключевым колонкам.

ТЗ1 = Документ1.Товары.Выгрузить();

ТЗ2 = Документ2.Товары.Выгрузить();

ТЗ2.Индексы.Добавить("Номенклатура,Характеристика");

Отбор = Новый Структура("Номенклатура,Характеристика");

Для Каждого СтрокаТЗ Из ТЗ1 Цикл

ЗаполнитьЗначенияСвойств(Отбор, СтрокаТЗ);

НайденныеСтроки = ТЗ2.НайтиСтроки(Отбор);

Если НайденныеСтроки.Количество() = 0 Тогда

// строки нет во второй таблице

Иначе

// сравниваем НайденныеСтроки[0] со СтрокаТЗ

КонецЕсли;

КонецЦикла;

Метод хорош тем, что таблицы значений можно дополнительно обработать: свернуть дубли через Свернуть(), отсортировать, скопировать часть колонок через ВыгрузитьКолонку. Без индекса на больших таблицах НайтиСтроки работает медленно — это частая причина «тормозов» в готовых доработках.

Способ 3. Сравнение запросом к базе данных

Когда оба документа уже записаны в базу, самый быстрый способ — запрос с полным соединением двух табличных частей. Платформа выполнит сравнение на уровне СУБД, что на порядки быстрее перебора для таблиц в тысячи строк.

ВЫБРАТЬ

ЕСТЬNULL(ТЧ1.Номенклатура, ТЧ2.Номенклатура) КАК Номенклатура,

ЕСТЬNULL(ТЧ1.Количество, 0) КАК КоличествоБыло,

ЕСТЬNULL(ТЧ2.Количество, 0) КАК КоличествоСтало,

ВЫБОР

КОГДА ТЧ1.Номенклатура ЕСТЬ NULL ТОГДА "Добавлена"

КОГДА ТЧ2.Номенклатура ЕСТЬ NULL ТОГДА "Удалена"

КОГДА ТЧ1.Количество <> ТЧ2.Количество ТОГДА "Изменена"

КОНЕЦ КАК ВидИзменения

ИЗ

Документ.РеализацияТоваровУслуг.Товары КАК ТЧ1

ПОЛНОЕ СОЕДИНЕНИЕ

Документ.РеализацияТоваровУслуг.Товары КАК ТЧ2

ПО

ТЧ1.Номенклатура = ТЧ2.Номенклатура

И ТЧ1.Ссылка = &Документ1

И ТЧ2.Ссылка = &Документ2

ГДЕ

ТЧ1.Ссылка = &Документ1

ИЛИ ТЧ2.Ссылка = &Документ2

Условие на ссылки обоих документов обязательно: без него соединение перемножит строки всех документов вида, и результат будет некорректным, а запрос — тяжёлым. Отбор по ссылке лучше размещать в параметрах виртуальной таблицы документа, если конфигурация это позволяет, либо во временных таблицах.

Этот подход не подходит, если один из документов ещё не записан — его табличной части нет в базе. В этом случае несохранённую таблицу передают в запрос через параметр как таблицу значений (Поместить ВТ) и соединяют с табличной частью записанного документа.

Сравнение способов

Перебор с соответствием

Простой код, работает с любыми данными в памяти. Подходит для таблиц до нескольких сотен строк, не требует записи документов.

Таблицы значений + НайтиСтроки

Гибкая постобработка (свёртка, сортировка). Требует индексов, иначе медленно на больших таблицах.

Запрос с полным соединением

Самый быстрый для больших таблиц. Работает только с записанными в базу данными либо требует помещения ТЗ во временную таблицу.

Проверка результата и типичные ошибки

После реализации сравнения проверьте его на контрольных примерах: идентичные таблицы должны дать пустой результат; документ и его копия с одной изменённой строкой — ровно одну запись «Изменена». Полезно проверить граничные случаи: пустая табличная часть с одной стороны, дублирующиеся строки с одним ключом, строки с незаполненными ключевыми реквизитами.

Типичные ошибки
01
Сравнение ссылок по наименованию или представлению — ложные расхождения при переименовании элементов. Сравнивайте по уникальному идентификатору или самой ссылке.
02
Отсутствие свёртки дублей — две строки с одним товаром не находят пару и попадают в отчёт как удалённая и добавленная.
03
Неточное сравнение чисел и дат — различия в незначащих разрядах. При необходимости округляйте значения перед сравнением.
04
Поиск строк перебором без соответствия или индекса — квадратичная сложность, на таблицах в тысячи строк процедура выполняется минутами.
05
Игнорирование NULL в запросе с полным соединением — без ЕСТЬNULL расчёты и итоги по расхождениям дают неожиданные значения.

Отдельная ошибка — сравнение табличных частей объектов, полученных через ПолучитьОбъект(), без контроля версий. Пока вы сравнивали, другой пользователь мог изменить документ. Если результат сравнения влияет на проведение или обмен данными, читайте данные в транзакции или проверяйте версию данных объекта.

Практические сценарии применения

Контроль изменений перед проведением

Частая задача — запретить или залогировать изменение табличной части согласованного документа. В подписке «ПередЗаписью» документ из базы читается через Ссылка.Товары.Выгрузить(), а новая табличная часть берётся из объекта. Сравнение любым из описанных способов даёт список изменений для записи в журнал или отказа от записи.

Сверка заказа и реализации

При сверке документов разных видов состав колонок табличных частей может отличаться. Приведите обе таблицы к общему набору колонок: выгрузите только нужные поля, переименуйте колонки при необходимости, затем применяйте запрос или перебор. Расхождения по количеству в такой сверке — это обычно недопоставка или перепоставка.

Порядок действий
  1. 1
    Определите ключ сравнения — колонки, однозначно идентифицирующие строку.
  2. 2
    Решите, какие виды расхождений нужны: добавленные, удалённые, изменённые строки.
  3. 3
    Выберите способ: перебор с соответствием для малых таблиц, запрос — для больших и записанных документов.
  4. 4
    Сверните дубли по ключу и отключите влияние незначащих колонок.
  5. 5
    Проверьте результат на контрольных примерах: идентичные таблицы, одно изменение, пустая таблица.

Ограничения и альтернативы

Если нужно не программное сравнение, а разовая визуальная сверка, проще вывести обе табличные части в табличные документы и использовать штатное сравнение файлов (меню «Файл — Сравнить файлы») — платформа умеет сравнивать табличные документы построчно. Для конфигураций на управляемых формах различия удобно показывать пользователю через условное оформление динамического списка расхождений.

Для регулярной сверки больших объёмов (например, синхронизация с внешней системой) имеет смысл хранить хеши строк: при загрузке считать контрольную сумму строки и сравнивать хеши вместо полного сравнения всех колонок. Это существенно ускоряет повторные проверки.

Контрольные точки

Как проверить результат

Сравнение документа с самим собой
Норма

Результат пустой

Тревожный признак

Есть записи о расхождениях — ошибка в ключе или логике

Копия документа с одной изменённой строкой
Норма

Ровно одна запись «Изменена» с корректными старым и новым значениями

Тревожный признак

Лишние записи «Добавлена»/«Удалена» — ключ слишком широкий или узкий

Таблицы с дублями номенклатуры
Норма

После свёртки расхождений нет

Тревожный признак

Ложные расхождения — свёртка не выполнена

Частые вопросы

Можно ли сравнить табличные части документов разных видов?

Да. Выгрузите обе табличные части в таблицы значений, приведите их к общему набору колонок (оставьте только сравниваемые поля, при необходимости переименуйте) и сравнивайте любым способом. В запросе для разных видов документов соединяются разные таблицы — синтаксис это позволяет.

Как сравнить табличную часть объекта в памяти с данными в базе?

Данные из базы получите выгрузкой через ссылку: ДокументСсылка.Товары.Выгрузить(). Данные из памяти возьмите из объекта формы. Дальше применяйте перебор с соответствием или поместите таблицу значений в запрос через временную таблицу.

Почему сравнение работает медленно на больших таблицах?

Чаще всего причина — вложенный перебор без индекса: для каждой строки первой таблицы выполняется полный проход по второй. Добавьте индекс на таблицу значений, используйте соответствие вместо поиска перебором или перенесите сравнение в запрос.

Как учесть порядок строк при сравнении?

Если порядок строк значим (например, этапы производства), добавьте в ключ сравнения номер строки. Учтите, что при вставке строки в середину все последующие строки получат другие номера и будут признаны изменёнными — обычно вместо номера строки используют реквизит «КодСтроки» или аналогичный стабильный идентификатор.

Есть ли готовая функция сравнения в типовых конфигурациях?

В библиотеке стандартных подсистем есть общие процедуры работы с таблицами значений, но полноценной универсальной функции сравнения табличных частей с детализацией различий нет — её реализуют прикладным кодом под конкретный ключ и набор колонок. В некоторых типовых решениях встречаются прикладные механизмы сверки для конкретных пар документов.

Итог

Что важно запомнить

Сравнение табличных частей строится вокруг правильно выбранного ключа строки. Для небольших таблиц достаточно перебора с соответствием, для больших и записанных в базу документов используйте запрос с полным соединением. Перед сравнением сворачивайте дубли, сравнивайте ссылки по идентификаторам и проверяйте алгоритм на контрольных примерах.