Разработка и Конфигурация · 12 мин
Как сравнить две табличные части в 1С
Способы сравнения двух табличных частей в 1С: запросом, циклом, через таблицу значений. Готовые примеры кода, проверка изменений, типичные ошибки разработчиков.
Задача сравнить две табличные части возникает постоянно: нужно понять, изменил ли пользователь строки документа, найти расхождения между товарами заказа и реализации, зафиксировать изменения для истории или провести документ только при реальной модификации данных. Универсальной команды «сравнить» в платформе нет, поэтому разработчик выбирает подход под конкретный сценарий.
Основных подхода три: поэлементное сравнение в цикле, сравнение через запрос с полным соединением и свёртка таблиц в хеш или строку для быстрой проверки «изменилось / не изменилось». У каждого способа свои условия применения, ограничения по составу колонок и чувствительность к типам данных.
Ниже разобраны рабочие варианты кода на встроенном языке 1С, критерии выбора метода и типичные ошибки, из-за которых сравнение «не видит» реальных изменений или, наоборот, находит ложные расхождения.
Что именно нужно сравнить: определяем задачу
Прежде чем писать код, сформулируйте, какой результат нужен. Задачи бывают трёх типов, и от этого зависит выбор метода. Первый тип — булевская проверка «табличная часть изменилась или нет», например в обработчике ПередЗаписью. Второй тип — получение списка различающихся строк: что добавлено, что удалено, что изменено. Третий — сопоставление строк двух разных документов по ключу (номенклатура, характеристика) с выводом разницы по количеству и цене.
Также важно, сравниваете ли вы табличную часть объекта с её же версией в базе данных (типичный сценарий — ПередЗаписью) или две независимые таблицы: табличные части разных документов, таблицу значений и табличную часть, результат запроса и данные формы. Для первого случая есть готовый приём через Выгрузить() и запрос к базе, для второго обычно обе стороны приводят к таблице значений.
Быстрая проверка «изменилась ли табличная часть»
Если нужен только факт изменения, самый простой путь — сериализовать обе версии и сравнить результаты. В управляемых формах на клиенте доступна проверка модифицированности, но она не различает, что именно изменилось. Для точной проверки в серверном коде выгрузите текущую табличную часть и получите версию из базы запросом, затем сравните.
Компактный вариант — сравнение через ЗначениеВСтрокуВнутр: функция преобразует значение (в том числе таблицу значений) во внутреннее строковое представление. Если строки совпали — таблицы идентичны по составу колонок и значениям. Метод быстрый, но чувствителен к порядку строк и колонок: одинаковые по смыслу таблицы с разным порядком строк дадут разные строки.
Функция ТабличнаяЧастьИзменилась(Ссылка, ТабЧастьОбъекта, ИмяТабличнойЧасти)
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ * ИЗ Документ.РеализацияТоваровУслуг.Товары ГДЕ Ссылка = &Ссылка";
Запрос.Текст = СтрЗаменить(Запрос.Текст, "РеализацияТоваровУслуг", Ссылка.Метаданные().Имя);
Запрос.Текст = СтрЗаменить(Запрос.Текст, "Товары", ИмяТабличнойЧасти);
Запрос.УстановитьПараметр("Ссылка", Ссылка);
ТаблицаИзБазы = Запрос.Выполнить().Выгрузить();
ТаблицаТекущая = ТабЧастьОбъекта.Выгрузить();
Возврат ЗначениеВСтрокуВнутр(ТаблицаИзБазы) <> ЗначениеВСтрокуВнутр(ТаблицаТекущая);
КонецФункцииУчтите: порядок строк, возвращаемый запросом без УПОРЯДОЧИТЬ ПО, не гарантирован. Добавьте сортировку по номеру строки (НомерСтроки доступен в запросе к табличной части), иначе сравнение через сериализацию может дать ложное расхождение. Альтернатива — сортировка обеих таблиц значений методом Сортировать() по одинаковому набору колонок перед сравнением.
Сравнение через запрос: поиск различающихся строк
Когда нужно показать пользователю конкретные расхождения, надёжнее всего работает приём с объединением двух таблиц и группировкой. Обе таблицы значений помещаются во временные таблицы, объединяются через ОБЪЕДИНИТЬ ВСЕ с пометкой источника, затем группируются по всем сравниваемым колонкам. Строки, где количество вхождений из двух источников не совпадает, — это различия.
Этот метод корректно обрабатывает дубли строк: если в одной таблице две одинаковые строки, а в другой одна, группировка это покажет. Циклическое сравнение «строка к строке» такой случай часто пропускает или обрабатывает неверно.
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Таб1", Таблица1);
Запрос.УстановитьПараметр("Таб2", Таблица2);
Запрос.Текст =
"ВЫБРАТЬ Т.Номенклатура, Т.Количество, Т.Цена, 1 КАК Признак ПОМЕСТИТЬ Т1 ИЗ &Таб1 КАК Т
|;
|ВЫБРАТЬ Т.Номенклатура, Т.Количество, Т.Цена, -1 КАК Признак ПОМЕСТИТЬ Т2 ИЗ &Таб2 КАК Т
|;
|ВЫБРАТЬ В.Номенклатура, В.Количество, В.Цена, СУММА(В.Признак) КАК Итог
|ИЗ (ВЫБРАТЬ <em> ИЗ Т1 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ </em> ИЗ Т2) КАК В
|СГРУППИРОВАТЬ ПО В.Номенклатура, В.Количество, В.Цена
|ИМЕЮЩИЕ СУММА(В.Признак) <> 0";Результат с ненулевым итогом — набор строк, присутствующих только в одной из таблиц (или в разном количестве). Положительный итог означает строку из первой таблицы, отрицательный — из второй. Если выборка пустая, таблицы совпадают полностью с точностью до порядка строк.
Поэлементное сравнение в цикле
Цикл подходит, когда таблицы небольшие, порядок строк гарантированно одинаковый (например, сравниваете строку с её исходной версией по номеру строки) или нужна специальная логика сравнения отдельных колонок: нестрогое равенство для чисел, игнорирование служебных колонок, сравнение с допуском.
Типовой приём — пройти по строкам первой таблицы, найти соответствующую строку второй по ключу через Найти() или НайтиСтроки() и сравнить значения колонок по списку. Список сравниваемых колонок лучше задавать строкой, чтобы не переписывать код при добавлении реквизитов.
- 1Выгрузите обе табличные части в таблицы значений через Выгрузить()
- 2Определите ключевые колонки для поиска пары (например, Номенклатура и Характеристика)
- 3Для каждой строки первой таблицы найдите пару во второй через НайтиСтроки()
- 4Сравните значения нужных колонок; расхождения запишите в таблицу результата
- 5Отдельным проходом по второй таблице найдите строки, отсутствующие в первой
Главная ловушка метода — производительность: НайтиСтроки() внутри цикла даёт квадратичную сложность. Для таблиц в сотни строк это незаметно, для тысяч строк создайте индекс по ключевым колонкам (Таблица.Индексы.Добавить("Номенклатура")) или используйте соответствие, где ключ — строковое представление ключевых полей.
Сопоставление по ключу с выводом разницы
Когда сравниваются табличные части двух разных документов (заказ и реализация, счёт и оплата), сравнение «строка к строке» бессмысленно: порядок и состав строк различаются. Здесь используют полное соединение по ключу: номенклатура, характеристика, серия. Результат — для каждого ключа значения из обеих таблиц рядом, и разницу видно вычитанием.
На языке запросов это ПОЛНОЕ СОЕДИНЕНИЕ двух временных таблиц по ключевым полям с предварительной свёрткой: каждую таблицу сначала группируют по ключу, суммируя количество и суммы, чтобы один товар встречался одной строкой. Затем выбираются записи, где значения различаются или одна из сторон NULL (есть только в одной таблице).
Не забудьте обработать NULL через ЕСТЬNULL() — иначе сравнение «количество из первой таблицы не равно количеству из второй» для отсутствующей строки даст неопределённый результат, и расхождение не попадёт в выборку.
Сравнение методов
Быстрый ответ «изменилось/нет». Не показывает, что именно изменилось; чувствителен к порядку строк и колонок.
Находит различающиеся строки, корректно работает с дублями. Требует одинаковых типов колонок.
Гибкая логика по колонкам, простая отладка. Медленный на больших таблицах без индексов.
Подходит для разных документов и свёртки по ключу. Сложнее запрос, нужна обработка NULL.
Проверка результата и типичные ошибки
После реализации сравнения проверьте его на контрольных примерах: идентичные таблицы, таблица с одной изменённой строкой, с добавленной строкой, с удалённой, с переставленными местами строками, с дублями. Каждый случай должен давать ожидаемый результат — особенно важен случай перестановки строк, если для вашей задачи порядок не значим.
Отдельная ошибка — сравнение в ПередЗаписью на сервере с обращением к базе без учёта того, что документ новый. Для нового объекта запрос по ссылке вернёт пустой результат, и логика должна трактовать это как «вся табличная часть новая», а не как ошибку.
Как проверить корректность сравнения
Результат сравнения пуст, функция возвращает «не изменилась»
Находятся расхождения — проверяйте порядок строк и типы колонок
Расхождений нет, если порядок не важен для задачи
Все строки помечены как изменённые
Разное количество одинаковых строк обнаруживается
Дубли игнорируются — метод с Найти() не подходит
Все строки непустой таблицы попадают в расхождения
Ошибка исполнения или пустой результат
Производительность и ограничения
Для табличных частей типовых документов (десятки и сотни строк) любой из описанных методов отрабатывает мгновенно, и оптимизация не требуется. Проблемы начинаются на больших таблицах: сериализация ЗначениеВСтрокуВнутр потребляет память пропорционально размеру таблицы, а передача таблицы значений параметром в запрос — тоже не бесплатна.
Если сравнение выполняется при каждой записи документа, избегайте лишних выгрузок: выгружайте только нужные колонки через Выгрузить(,"Номенклатура,Количество,Цена") и не сравнивайте колонки, которые не влияют на решение. Для сценария «только проверить факт изменения» достаточно сравнить контрольные суммы или сериализованные строки, не строя полную таблицу расхождений.
FAQ
Как сравнить табличную часть объекта с версией в базе до записи?
В обработчике ПередЗаписью получите ссылку объекта, выполните запрос к табличной части по этой ссылке, выгрузите результат в таблицу значений и сравните с ЭтотОбъект.ИмяТабличнойЧасти.Выгрузить() любым из методов: сериализацией или запросом с группировкой. Для нового объекта запрос вернёт пустую таблицу — обработайте этот случай отдельно.
Почему сравнение через ЗначениеВСтрокуВнутр показывает различия в одинаковых таблицах?
Чаще всего причина в разном порядке строк или колонок. Отсортируйте обе таблицы методом Сортировать() по одинаковому набору колонок и убедитесь, что состав и порядок колонок совпадают. Также проверьте типы: строка длиной 50 и строка неограниченной длины с одинаковым содержимым могут сериализоваться по-разному.
Как сравнить только часть колонок табличной части?
Выгрузите таблицу с явным списком колонок: ТабЧасть.Выгрузить(, "Номенклатура,Количество,Цена"). Либо при сравнении в цикле перебирайте колонки из строкового списка, пропуская остальные. В запросном методе просто включите в выборку и группировку только нужные поля.
Как найти строки, которые есть в одной таблице, но отсутствуют в другой?
Используйте метод с объединением и группировкой: строки с положительным итогом по признаку источника есть только в первой таблице, с отрицательным — только во второй. Альтернатива для небольших таблиц — цикл по одной таблице с НайтиСтроки() по ключу во второй: пустой результат поиска означает отсутствие строки.
Можно ли сравнить табличные части двух разных видов документов?
Да, если привести их к таблицам значений с одинаковым набором колонок. Выгрузите каждую табличную часть с одинаковыми именами колонок (при необходимости переименуйте колонки в запросе через КАК) и сравнивайте по общему ключу, например по номенклатуре, через полное соединение.
Что быстрее: цикл или запрос?
На малых таблицах разница незаметна. На больших запрос с группировкой обычно выигрывает, так как обработка идёт на уровне СУБД без построчных обращений. Цикл с НайтиСтроки() без индексов на тысячах строк работает заметно медленнее — при необходимости добавьте индексы или используйте соответствие.
Что важно запомнить
Выбор метода зависит от задачи: факт изменения проверяйте сериализацией, различающиеся строки ищите запросом с объединением и группировкой, расхождения разных документов — полным соединением по ключу. Всегда контролируйте порядок строк, типы колонок и обработку NULL, а готовое сравнение проверяйте на контрольных наборах: идентичные таблицы, перестановка строк, дубли, добавление и удаление.