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