Как сравнить две таблицы значений и вывести расхождения в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Сравнение двух коллекций данных — это одна из наиболее частых задач, с которыми сталкивается разработчик 1С. Это может потребоваться при сверке данных из разных документов (например, Приходного и Расходного ордеров), при анализе остатков или импорте данных из внешних источников. В этой статье мы подробно разберем, как эффективно сравнить две таблицы значений (ТЗ), используя как возможности языка запросов, так и программные методы перебора.

Метод 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, и обычное вычитание не сработает.

Метод 2. Программный метод «Инверсии знака» (Алгоритм +/-)

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

  1. Создаем общую таблицу значений, куда будем копировать данные.
  2. Переносим данные из первой таблицы как есть.
  3. Переносим данные из второй таблицы, но умножаем значение в колонке Количество на -1.
  4. Выполняем метод Свернуть("Номенклатура", "Количество").
  5. Строки, где результат равен 0, удаляем — это совпадения. Остальное — наши расхождения.

Посмотрим на программную реализацию этого метода:


// Предположим, у нас есть ТЗ1 и ТЗ2
ТЗ_Сводная = ТЗ1.Скопировать();
// Добавим данные из ТЗ2 с инверсией
Для Каждого СтрИзТЗ2 Из ТЗ2 Цикл
    НоваяСтр = ТЗ_Сводная.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтр, СтрИзТЗ2);
    НоваяСтр.Количество = -СтрИзТЗ2.Количество;
КонецЦикла;

// Группируем данные
ТЗ_Сводная.Свернуть("Номенклатура", "Количество");

// Очищаем от нулевых разниц
Индекс = ТЗ_Сводная.Количество() - 1;
Пока Индекс >= 0 Цикл
    Если ТЗ_Сводная[Индекс].Количество = 0 Тогда
        ТЗ_Сводная.Удалить(Индекс);
    КонецЕсли;
    Индекс = Индекс - 1;
КонецЦикла;

Этот метод крайне эффективен для поиска количественных расхождений. Если после сворачивания Количество положительное — значит, в первой таблице товара больше. Если отрицательное — во второй.

Метод 3. Сравнение через поиск с использованием индексации

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

Проанализируем пример функции, которая находит уникальные строки и различия:


Функция НайтиРазличияВТаблицах(Таблица0, Таблица1, ПоляКлюча)
    
    // Создаем индекс для ускорения поиска
    Таблица1.Индексы.Добавить(ПоляКлюча);
    
    Разница = Таблица0.СкопироватьКолонки();
    Разница.Колонки.Добавить("ТипРасхождения", Новый ОписаниеТипов("Строка"));
    
    СтруктураПоиска = Новый Структура(ПоляКлюча);
    
    // Проверяем, что есть в Таблице0, но нет в Таблице1 или отличается
    Для Каждого Строка0 Из Таблица0 Цикл
        ЗаполнитьЗначенияСвойств(СтруктураПоиска, Строка0);
        НайденныеСтроки = Таблица1.НайтиСтроки(СтруктураПоиска);
        
        Если НайденныеСтроки.Количество() = 0 Тогда
            НоваяСтр = Разница.Добавить();
            ЗаполнитьЗначенияСвойств(НоваяСтр, Строка0);
            НоваяСтр.ТипРасхождения = "Отсутствует во второй таблице";
        Иначе
            Строка1 = НайденныеСтроки[0];
            // Сравниваем ресурсы (например, Количество)
            Если Строка0.Количество <> Строка1.Количество Тогда
                НоваяСтр = Разница.Добавить();
                ЗаполнитьЗначенияСвойств(НоваяСтр, Строка0);
                НоваяСтр.ТипРасхождения = "Разное количество";
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
    Возврат Разница;
КонецФункции

Важный момент: не забывайте, что для полноценного сравнения процедуру нужно запустить дважды (сначала проверяем ТЗ1 относительно ТЗ2, затем наоборот), либо использовать вспомогательную колонку-маркер «Проверено» во второй таблице, чтобы отловить те строки, которых не было в первой.

Нюансы работы с Критериями отбора

В процессе обсуждения на форуме возник вопрос: как сравнить данные, если одна из таблиц получается через КритерийОтбора.СвязанныеДокументы? Напрямую в запросе соединять критерий отбора с другими таблицами может быть проблематично с точки зрения производительности.

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

Если же связь возможна только через критерий, используйте конструкцию ВЫРАЗИТЬ, чтобы платформа понимала, к какой конкретно таблице документа нужно обратиться:


ВЫБРАТЬ
    СвязанныеДокументы.Ссылка КАК Ссылка
ИЗ
    КритерийОтбора.СвязанныеДокументы(&Параметр) КАК СвязанныеДокументы
ГДЕ
    СвязанныеДокументы.Ссылка ССЫЛКА Документ.РасходныйОрдерНаТовары

Подведение итогов

Мы рассмотрели три основных подхода к сравнению таблиц значений в 1С:

Выбор конкретного метода зависит от вашей задачи. Если данные уже находятся в базе — используйте запросы. Если вы оперируете внешними данными (например, из Excel) — используйте программные методы или загрузку ТЗ в запрос с предварительной типизацией.

← На главную