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

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

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

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

Важные требования к таблицам значений перед передачей в запрос

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

  1. Типизация колонок: Все сравниваемые колонки должны иметь четко объявленное ОписаниеТипов. Если хотя бы одна колонка имеет составной или неопределенный тип («Произвольный»), платформа выдаст ошибку выбора типа в запросе.
  2. Единообразие типов аналитики: Если одна таблица прочитана из внешнего файла (например, Excel), а вторая выгружена из документа базы данных, колонки Номенклатура или ЕдиницаИзмерения могут различаться по типам (строка и ссылка). Перед передачей в запрос приведите данные к единому типу.
  3. Временные таблицы: Для работы с ТаблицейЗначений в запросе ее необходимо загрузить во временную таблицу с помощью ключевого слова ПОМЕСТИТЬ.

Способ 1. Сравнение через объединение и группировку (ОБЪЕДИНИТЬ ВСЕ)

Рассмотрим наиболее надежный и производительный способ решения задачи. Чтобы избежать проблем «декартова произведения» при соединении строк с одинаковой номенклатурой (дублей), мы можем объединить обе выборки, присвоив количеству из первой таблицы положительный знак, а из второй — отрицательный.

Проанализируем пример схемы запроса:


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

Запрос.УстановитьПараметр("Таблица1", Таблица1);
Запрос.УстановитьПараметр("Таблица2", Таблица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, что позволит корректно рассчитать разницу количеств.

Способ 3. Сравнение без запросов через копирование и свертку таблицы значений

Посмотрим на решение задачи программным методом в коде 1С без вызова движка запросов. Этот подход удобен при работе с небольшими таблицами прямо в оперативной памяти.

Алгоритм решения выглядит следующим образом:

  1. Создаем новую колонку МаркерРазницы в первой и второй таблице.
  2. Заполняем МаркерРазницы в первой таблице числом 1, умноженным на количество (или со значением 1 для факта наличия строки).
  3. Заполняем МаркерРазницы во второй таблице со значением -1, аналогично умноженным на количество.
  4. Объединяем две таблицы в один общий массив или общую таблицу значений.
  5. Применяем метод Свернуть() по ключевым колонкам аналитики.

Рассмотрим пример реализации на языке 1С:


// Создаем копию первой таблицы и добавляем служебную колонку
ТаблицаИтог = Таблица1.Скопировать();
ТаблицаИтог.Колонки.Добавить("МаркерКоличества", Новый ОписаниеТипов("Число"));

Для Каждого Стр Из ТаблицаИтог Цикл
    Стр.МаркерКоличества = Стр.Количество;
КонецЦикла;

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

// Сворачиваем по аналитике
ТаблицаИтог.Свернуть("Номенклатура, ЕдиницаИзмерения", "МаркерКоличества");

// Удаляем совпадающие позиции (где разница равна 0)
Индекс = ТаблицаИтог.Количество() - 1;
Пока Индекс >= 0 Цикл
    Если ТаблицаИтог[Индекс].МаркерКоличества = 0 Тогда
        ТаблицаИтог.Удалить(Индекс);
    КонецЕсли;
    Индекс = Индекс - 1;
КонецЦикла;

Рекомендации по обработке погрешностей вычислений

При сравнении чисел с плавающей точкой (дробных количеств) разница может составлять доли копеек или микро-значения из-за особенностей округления. Чтобы избежать ложноположительных расхождений, применяйте функцию ВЫРАЗИТЬ() в запросах для приведения результата к необходимой точности, либо проверяйте абсолютную разницу на превышение допустимой погрешности (например, > 0.001 или < -0.001).

← На главную