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