Разработка и Конфигурация · 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
Условие на ссылки обоих документов обязательно: без него соединение перемножит строки всех документов вида, и результат будет некорректным, а запрос — тяжёлым. Отбор по ссылке лучше размещать в параметрах виртуальной таблицы документа, если конфигурация это позволяет, либо во временных таблицах.
Этот подход не подходит, если один из документов ещё не записан — его табличной части нет в базе. В этом случае несохранённую таблицу передают в запрос через параметр как таблицу значений (Поместить ВТ) и соединяют с табличной частью записанного документа.
Сравнение способов
Простой код, работает с любыми данными в памяти. Подходит для таблиц до нескольких сотен строк, не требует записи документов.
Гибкая постобработка (свёртка, сортировка). Требует индексов, иначе медленно на больших таблицах.
Самый быстрый для больших таблиц. Работает только с записанными в базу данными либо требует помещения ТЗ во временную таблицу.
Проверка результата и типичные ошибки
После реализации сравнения проверьте его на контрольных примерах: идентичные таблицы должны дать пустой результат; документ и его копия с одной изменённой строкой — ровно одну запись «Изменена». Полезно проверить граничные случаи: пустая табличная часть с одной стороны, дублирующиеся строки с одним ключом, строки с незаполненными ключевыми реквизитами.
Отдельная ошибка — сравнение табличных частей объектов, полученных через ПолучитьОбъект(), без контроля версий. Пока вы сравнивали, другой пользователь мог изменить документ. Если результат сравнения влияет на проведение или обмен данными, читайте данные в транзакции или проверяйте версию данных объекта.
Практические сценарии применения
Контроль изменений перед проведением
Частая задача — запретить или залогировать изменение табличной части согласованного документа. В подписке «ПередЗаписью» документ из базы читается через Ссылка.Товары.Выгрузить(), а новая табличная часть берётся из объекта. Сравнение любым из описанных способов даёт список изменений для записи в журнал или отказа от записи.
Сверка заказа и реализации
При сверке документов разных видов состав колонок табличных частей может отличаться. Приведите обе таблицы к общему набору колонок: выгрузите только нужные поля, переименуйте колонки при необходимости, затем применяйте запрос или перебор. Расхождения по количеству в такой сверке — это обычно недопоставка или перепоставка.
- 1Определите ключ сравнения — колонки, однозначно идентифицирующие строку.
- 2Решите, какие виды расхождений нужны: добавленные, удалённые, изменённые строки.
- 3Выберите способ: перебор с соответствием для малых таблиц, запрос — для больших и записанных документов.
- 4Сверните дубли по ключу и отключите влияние незначащих колонок.
- 5Проверьте результат на контрольных примерах: идентичные таблицы, одно изменение, пустая таблица.
Ограничения и альтернативы
Если нужно не программное сравнение, а разовая визуальная сверка, проще вывести обе табличные части в табличные документы и использовать штатное сравнение файлов (меню «Файл — Сравнить файлы») — платформа умеет сравнивать табличные документы построчно. Для конфигураций на управляемых формах различия удобно показывать пользователю через условное оформление динамического списка расхождений.
Для регулярной сверки больших объёмов (например, синхронизация с внешней системой) имеет смысл хранить хеши строк: при загрузке считать контрольную сумму строки и сравнивать хеши вместо полного сравнения всех колонок. Это существенно ускоряет повторные проверки.
Как проверить результат
Результат пустой
Есть записи о расхождениях — ошибка в ключе или логике
Ровно одна запись «Изменена» с корректными старым и новым значениями
Лишние записи «Добавлена»/«Удалена» — ключ слишком широкий или узкий
После свёртки расхождений нет
Ложные расхождения — свёртка не выполнена
Частые вопросы
Можно ли сравнить табличные части документов разных видов?
Да. Выгрузите обе табличные части в таблицы значений, приведите их к общему набору колонок (оставьте только сравниваемые поля, при необходимости переименуйте) и сравнивайте любым способом. В запросе для разных видов документов соединяются разные таблицы — синтаксис это позволяет.
Как сравнить табличную часть объекта в памяти с данными в базе?
Данные из базы получите выгрузкой через ссылку: ДокументСсылка.Товары.Выгрузить(). Данные из памяти возьмите из объекта формы. Дальше применяйте перебор с соответствием или поместите таблицу значений в запрос через временную таблицу.
Почему сравнение работает медленно на больших таблицах?
Чаще всего причина — вложенный перебор без индекса: для каждой строки первой таблицы выполняется полный проход по второй. Добавьте индекс на таблицу значений, используйте соответствие вместо поиска перебором или перенесите сравнение в запрос.
Как учесть порядок строк при сравнении?
Если порядок строк значим (например, этапы производства), добавьте в ключ сравнения номер строки. Учтите, что при вставке строки в середину все последующие строки получат другие номера и будут признаны изменёнными — обычно вместо номера строки используют реквизит «КодСтроки» или аналогичный стабильный идентификатор.
Есть ли готовая функция сравнения в типовых конфигурациях?
В библиотеке стандартных подсистем есть общие процедуры работы с таблицами значений, но полноценной универсальной функции сравнения табличных частей с детализацией различий нет — её реализуют прикладным кодом под конкретный ключ и набор колонок. В некоторых типовых решениях встречаются прикладные механизмы сверки для конкретных пар документов.
Что важно запомнить
Сравнение табличных частей строится вокруг правильно выбранного ключа строки. Для небольших таблиц достаточно перебора с соответствием, для больших и записанных в базу документов используйте запрос с полным соединением. Перед сравнением сворачивайте дубли, сравнивайте ссылки по идентификаторам и проверяйте алгоритм на контрольных примерах.