В процессе разработки на платформе 1С:Предприятие часто возникает задача анализа изменений в данных. Одной из наиболее типичных ситуаций является сравнение двух таблиц значений (ТЗ), например, до и после редактирования пользователем или при сопоставлении данных из разных источников. В данной статье мы подробно разберем несколько эффективных методов, которые позволяют вычислить разницу в количестве по ключевым полям (Состав, Серия).
Этот метод считается наиболее «архитектурно правильным» и производительным, особенно если таблицы содержат сотни или тысячи строк. Основная идея заключается в том, чтобы представить одну таблицу как «приход» (положительные значения), а вторую как «расход» (отрицательные значения), после чего схлопнуть их группировкой. Рассмотрим алгоритм реализации этого метода.
Для начала нам необходимо передать обе таблицы значений в менеджер временных таблиц. Разберем этот процесс по шагам:
Проанализируем пример кода для такого запроса:
// Создаем запрос
Запрос = Новый Запрос;
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
// Передаем таблицы в запрос
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ТЗ_Старая ИЗ &ТЗ1 КАК ТЗ1";
Запрос.УстановитьПараметр("ТЗ1", ТЗ1);
Запрос.Выполнить();
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ТЗ_Новая ИЗ &ТЗ2 КАК ТЗ2";
Запрос.УстановитьПараметр("ТЗ2", ТЗ2);
Запрос.Выполнить();
// Основной расчет разницы
Запрос.Текст =
"ВЫБРАТЬ
| ВложенныйЗапрос.Состав КАК Состав,
| ВложенныйЗапрос.Серия КАК Серия,
| СУММА(ВложенныйЗапрос.Количество) КАК КоличествоРазница
|ИЗ
| (ВЫБРАТЬ
| ТЗ_Старая.Состав КАК Состав,
| ТЗ_Старая.Серия КАК Серия,
| -ТЗ_Старая.Количество КАК Количество
| ИЗ
| ТЗ_Старая КАК ТЗ_Старая
|
| ОБЪЕДИНИТЬ ВСЕ
|
| ВЫБРАТЬ
| ТЗ_Новая.Состав,
| ТЗ_Новая.Серия,
| ТЗ_Новая.Количество
| ИЗ
| ТЗ_Новая КАК ТЗ_Новая) КАК ВложенныйЗапрос
|
|СГРУППИРОВАТЬ ПО
| ВложенныйЗапрос.Состав,
| ВложенныйЗапрос.Серия
|
|ИМЕЮЩИЕ
| СУММА(ВложенныйЗапрос.Количество) <> 0";
РезультатСравнения = Запрос.Выполнить().Выгрузить();
В данном примере мы используем оператор ОБЪЕДИНИТЬ ВСЕ. Обратите внимание, что в первой части объединения мы умножаем Количество на -1. После группировки строки с нулевым итоговым количеством будут означать, что данные не изменились. Секция ИМЕЮЩИЕ отсекает такие строки, оставляя только те, где количество увеличилось, уменьшилось, либо появились/исчезли уникальные комбинации «Состав + Серия».
Если по каким-то причинам использование запроса невозможно (например, код выполняется в контексте, где недоступны временные таблицы, или это клиентская часть), можно применить программный поиск. Рассмотрим пример, предложенный на форуме, где одна таблица очищается от строк, найденных в другой.
Выясним причину выбора этого метода: он прост в реализации «на скорую руку». Однако помните, что без предварительной подготовки он может работать медленно на больших объемах данных.
Процедура ВыделитьОтличия(Знач ТЗ1, Знач ТЗ2)
// Проходим по второй таблице (новые данные)
Для каждого Строка Из ТЗ2 Цикл
СтруктураОтбор = Новый Структура;
СтруктураОтбор.Вставить("Состав", Строка.Состав);
СтруктураОтбор.Вставить("Серия", Строка.Серия);
СтруктураОтбор.Вставить("Количество", Строка.Количество);
// Пытаемся найти идентичную строку в исходной таблице
НайденныеСтроки = ТЗ1.НайтиСтроки(СтруктураОтбор);
Если НайденныеСтроки.Количество() > 0 Тогда
// Если нашли - удаляем её из ТЗ1, так как изменений нет
Для Каждого СтрокаКУдалению из НайденныеСтроки Цикл
ТЗ1.Удалить(СтрокаКУдалению);
КонецЦикла;
КонецЕсли;
КонецЦикла;
// В ТЗ1 останутся только те строки, которые изменились или исчезли
Если ТЗ1.Количество() > 0 Тогда
Сообщить("Найдены изменения в составе или количестве!");
КонецЕсли;
КонецПроцедуры
Важное замечание по производительности: В приведенном выше коде используется метод НайтиСтроки. Чтобы этот поиск работал быстро, необходимо обязательно добавить индексы к таблице значений. Рассмотрим, как это сделать:
ТЗ1.Индексы.Добавить("Состав, Серия, Количество");
Без индексации платформа будет выполнять линейный перебор строк ТЗ1 для каждой итерации цикла по ТЗ2, что приведет к квадратичной сложности алгоритма O(N*M).
Этот способ позволяет быстро объединить две таблицы в одну и проанализировать их без использования механизма запросов. Разберем ситуацию: у нас есть ТЗ_Старая и ТЗ_Новая.
ТЗ_Результат с колонками: Состав, Серия, КолСтарое, КолНовое.ТЗ_Результат, заполняя только КолСтарое.ТЗ_Результат, заполняя только КолНовое.ТЗ_Результат.Свернуть("Состав, Серия", "КолСтарое, КолНовое").После свертки мы получим таблицу, где каждая строка содержит и старое, и новое количество для каждой серии. Проанализируем результат простым циклом:
Для каждого Стр Из ТЗ_Результат Цикл
Если Стр.КолСтарое <> Стр.КолНовое Тогда
// Количество изменилось
Разница = Стр.КолНовое - Стр.КолСтарое;
Сообщить("По позиции " + Стр.Состав + " разница: " + Разница);
КонецЕсли;
КонецЦикла;
Для максимально быстрого поиска на стороне клиента (или в серверном коде без запроса) рекомендуется использовать объект Соответствие. Это аналог хэш-таблицы, где поиск по ключу происходит мгновенно.
Рассмотрим по шагам:
КартаЗначений = Новый Соответствие;
// Заполняем карту данными из первой таблицы
Для каждого Стр Из ТЗ_Старая Цикл
Ключ = Строка(Стр.Состав.УникальныйИдентификатор()) + "_" + Строка(Стр.Серия);
КартаЗначений.Вставить(Ключ, Стр.Количество);
КонецЦикла;
// Сравниваем со второй таблицей
Для каждого Стр Из ТЗ_Новая Цикл
Ключ = Строка(Стр.Состав.УникальныйИдентификатор()) + "_" + Строка(Стр.Серия);
СтароеКоличество = КартаЗначений.Получить(Ключ);
Если СтароеКоличество = Неопределено Тогда
Сообщить("Новая строка: " + Стр.Состав);
ИначеЕсли СтароеКоличество <> Стр.Количество Тогда
Сообщить("Изменилось количество для: " + Стр.Состав);
КонецЕсли;
КонецЦикла;
Как отмечалось в обсуждении на форуме, СКД предоставляет мощные инструменты для объединения наборов данных. Мы можем создать два набора данных типа «Объект» (наши таблицы значений), связать их по полям Состав и Серия и настроить «Полное соединение».
Это позволит в отчете наглядно увидеть:
Подведем итоги: для разовых проверок небольших таблиц подойдет метод со Структурой и НайтиСтроки (при условии индексации). Для массовой обработки данных в серверном коде лучшим выбором будет Запрос с временными таблицами или метод Свернуть.