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

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

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

Метод 1: Использование запроса с объединением и группировкой

Этот метод считается наиболее «архитектурно правильным» и производительным, особенно если таблицы содержат сотни или тысячи строк. Основная идея заключается в том, чтобы представить одну таблицу как «приход» (положительные значения), а вторую как «расход» (отрицательные значения), после чего схлопнуть их группировкой. Рассмотрим алгоритм реализации этого метода.

Для начала нам необходимо передать обе таблицы значений в менеджер временных таблиц. Разберем этот процесс по шагам:

  1. Подготовим таблицы значений, убедившись, что типы колонок совпадают.
  2. Поместим первую таблицу (ТЗ_Старая) во временную таблицу.
  3. Поместим вторую таблицу (ТЗ_Новая) во временную таблицу.
  4. Выполним запрос, который объединит данные и вычислит разницу.

Проанализируем пример кода для такого запроса:


// Создаем запрос
Запрос = Новый Запрос;
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

// Передаем таблицы в запрос
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ТЗ_Старая ИЗ &ТЗ1 КАК ТЗ1";
Запрос.УстановитьПараметр("ТЗ1", ТЗ1);
Запрос.Выполнить();

Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ТЗ_Новая ИЗ &ТЗ2 КАК ТЗ2";
Запрос.УстановитьПараметр("ТЗ2", ТЗ2);
Запрос.Выполнить();

// Основной расчет разницы
Запрос.Текст = 
"ВЫБРАТЬ
|	ВложенныйЗапрос.Состав КАК Состав,
|	ВложенныйЗапрос.Серия КАК Серия,
|	СУММА(ВложенныйЗапрос.Количество) КАК КоличествоРазница
|ИЗ
|	(ВЫБРАТЬ
|		ТЗ_Старая.Состав КАК Состав,
|		ТЗ_Старая.Серия КАК Серия,
|		-ТЗ_Старая.Количество КАК Количество
|	ИЗ
|		ТЗ_Старая КАК ТЗ_Старая
|	
|	ОБЪЕДИНИТЬ ВСЕ
|	
|	ВЫБРАТЬ
|		ТЗ_Новая.Состав,
|		ТЗ_Новая.Серия,
|		ТЗ_Новая.Количество
|	ИЗ
|		ТЗ_Новая КАК ТЗ_Новая) КАК ВложенныйЗапрос
|
|СГРУППИРОВАТЬ ПО
|	ВложенныйЗапрос.Состав,
|	ВложенныйЗапрос.Серия
|
|ИМЕЮЩИЕ
|	СУММА(ВложенныйЗапрос.Количество) <> 0";

РезультатСравнения = Запрос.Выполнить().Выгрузить();

В данном примере мы используем оператор ОБЪЕДИНИТЬ ВСЕ. Обратите внимание, что в первой части объединения мы умножаем Количество на -1. После группировки строки с нулевым итоговым количеством будут означать, что данные не изменились. Секция ИМЕЮЩИЕ отсекает такие строки, оставляя только те, где количество увеличилось, уменьшилось, либо появились/исчезли уникальные комбинации «Состав + Серия».

Метод 2: Программный перебор с использованием поиска по структуре

Если по каким-то причинам использование запроса невозможно (например, код выполняется в контексте, где недоступны временные таблицы, или это клиентская часть), можно применить программный поиск. Рассмотрим пример, предложенный на форуме, где одна таблица очищается от строк, найденных в другой.

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


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

Важное замечание по производительности: В приведенном выше коде используется метод НайтиСтроки. Чтобы этот поиск работал быстро, необходимо обязательно добавить индексы к таблице значений. Рассмотрим, как это сделать:

ТЗ1.Индексы.Добавить("Состав, Серия, Количество");

Без индексации платформа будет выполнять линейный перебор строк ТЗ1 для каждой итерации цикла по ТЗ2, что приведет к квадратичной сложности алгоритма O(N*M).

Метод 3: Свертка в памяти (Метод "Свернуть")

Этот способ позволяет быстро объединить две таблицы в одну и проанализировать их без использования механизма запросов. Разберем ситуацию: у нас есть ТЗ_Старая и ТЗ_Новая.

  1. Создадим новую таблицу ТЗ_Результат с колонками: Состав, Серия, КолСтарое, КолНовое.
  2. Скопируем данные из первой таблицы в ТЗ_Результат, заполняя только КолСтарое.
  3. Добавим данные из второй таблицы в ту же ТЗ_Результат, заполняя только КолНовое.
  4. Вызовем метод ТЗ_Результат.Свернуть("Состав, Серия", "КолСтарое, КолНовое").

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


Для каждого Стр Из ТЗ_Результат Цикл
    Если Стр.КолСтарое <> Стр.КолНовое Тогда
        // Количество изменилось
        Разница = Стр.КолНовое - Стр.КолСтарое;
        Сообщить("По позиции " + Стр.Состав + " разница: " + Разница);
    КонецЕсли;
КонецЦикла;

Метод 4: Использование объекта "Соответствие" (Map)

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

Рассмотрим по шагам:

  1. Ключом в соответствии будет уникальный идентификатор строки (например, комбинация Состава и Серии).
  2. Значением будет выступать количество.

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

// Сравниваем со второй таблицей
Для каждого Стр Из ТЗ_Новая Цикл
    Ключ = Строка(Стр.Состав.УникальныйИдентификатор()) + "_" + Строка(Стр.Серия);
    СтароеКоличество = КартаЗначений.Получить(Ключ);
    
    Если СтароеКоличество = Неопределено Тогда
        Сообщить("Новая строка: " + Стр.Состав);
    ИначеЕсли СтароеКоличество <> Стр.Количество Тогда
        Сообщить("Изменилось количество для: " + Стр.Состав);
    КонецЕсли;
КонецЦикла;

Метод 5: Сравнение через Систему компоновки данных (СКД)

Как отмечалось в обсуждении на форуме, СКД предоставляет мощные инструменты для объединения наборов данных. Мы можем создать два набора данных типа «Объект» (наши таблицы значений), связать их по полям Состав и Серия и настроить «Полное соединение».

Это позволит в отчете наглядно увидеть:

Подведем итоги: для разовых проверок небольших таблиц подойдет метод со Структурой и НайтиСтроки (при условии индексации). Для массовой обработки данных в серверном коде лучшим выбором будет Запрос с временными таблицами или метод Свернуть.

← На главную