Почему различаются итоги в запросах при сверке регистров и как правильно найти расхождения?

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

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

Анализ проблемы: почему соединения «в лоб» не работают

Разберем ситуацию, представленную в исходном вопросе. Автор сравнивает данные двух регистров: ТоварыНаСкладах (складской учет) и ТоварыОрганизацийБУ (бухгалтерский или управленческий учет по организациям). Проблема заключается в том, что структура измерений в этих регистрах различается.

В первом варианте запроса автор использует временные таблицы с предварительной группировкой. Во втором варианте — ПОЛНОЕ СОЕДИНЕНИЕ виртуальных таблиц остатков напрямую. Проанализируем, в чем заключается фундаментальная ошибка второго подхода.

1. Декартово произведение и дублирование строк. Это самая главная причина расхождения итогов. В регистре ТоварыНаСкладах учет ведется в разрезе складов, а у каждого склада есть реквизит Подразделение. В регистре ТоварыОрганизацийБУ учет может идти сразу по подразделениям. Если в одном подразделении есть два склада, то при соединении по полю Подразделение без предварительной группировки по складам, данные из второго регистра «размножатся». На каждую строку склада из первой таблицы будет подобрана одна и та же строка из второй таблицы. В результате сумма по второму регистру в итоговом отчете будет завышена в несколько раз.

2. Разные параметры виртуальных таблиц. Как было замечено в обсуждении, во втором запросе автор забыл указать параметр &Период для одной из таблиц. РегистрНакопления.ТоварыОрганизацийБУ.Остатки без параметров берет текущие остатки на «сейчас», в то время как первая таблица берет остатки на конкретную дату. Это гарантированно приведет к разным результатам.

Обработка пустых значений (NULL)

При использовании ПОЛНОЕ СОЕДИНЕНИЕ крайне важно помнить о значениях NULL. Если товар есть в первом регистре, но отсутствует во втором, поля второго регистра будут содержать NULL. Рассмотрим, как ведет себя условие в блоке ГДЕ:


ГДЕ
    ТоварыНаСкладахОстатки.КоличествоОстаток - ТоварыОрганизацийБУОстатки.КоличествоОстаток <> 0

Проанализируем эту ситуацию: если одно из значений — NULL, то результат арифметической операции также будет NULL. В языке запросов 1С сравнение NULL <> 0 не возвращает «Истина», оно возвращает «Неизвестно», и такая строка просто не попадет в результат. Таким образом, классическое соединение без обработки NULL отсекает все позиции, которые есть только в одном из регистров, показывая только разницу в тех позициях, которые присутствуют в обоих. Чтобы этого избежать, необходимо использовать функцию ЕСТЬNULL().

Правильный подход: использование ОБЪЕДИНИТЬ ВСЕ

Опытные разработчики 1С знают, что для сверки двух источников данных метод ОБЪЕДИНИТЬ ВСЕ (UNION ALL) гораздо эффективнее и безопаснее, чем ПОЛНОЕ СОЕДИНЕНИЕ. Рассмотрим, почему это так:

  1. Производительность: Соединение таблиц требует от СУБД построения хэш-таблиц или вложенных циклов для сопоставления строк. Объединение — это линейная операция, которая просто «склеивает» результаты двух выборок.
  2. Отсутствие дублей: При правильной последующей группировке риск «размножения» строк из-за несовпадающих измерений сводится к нулю.
  3. Читаемость: Код становится более предсказуемым, так как нам не нужно беспокоиться о правилах JOIN.

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


ВЫБРАТЬ
    ТоварыНаСкладах.Склад.Подразделение КАК Подразделение,
    ТоварыНаСкладах.Номенклатура КАК Номенклатура,
    ТоварыНаСкладах.КоличествоОстаток КАК Количество
ПОМЕСТИТЬ ВТ_Данные
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК ТоварыНаСкладах

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ТоварыОрганизаций.Подразделение,
    ТоварыОрганизаций.Товар,
    -ТоварыОрганизаций.КоличествоОстаток
ИЗ
    РегистрНакопления.ТоварыОрганизацийБУ.Остатки(&Период, ) КАК ТоварыОрганизаций
;

Финальная группировка и условие ИМЕЮЩИЕ

Теперь проанализируем, как получить итоговую разницу. Нам нужно сгруппировать данные из временной таблицы. Важный нюанс: для отсечения строк, где данные совпали (сумма стала равна нулю), мы должны использовать конструкцию ИМЕЮЩИЕ (HAVING), а не ГДЕ (WHERE).

Разница между ними принципиальна: ГДЕ фильтрует строки до момента их суммирования, а ИМЕЮЩИЕ фильтрует уже результаты агрегатных функций (после группировки). Если мы хотим увидеть только реальные расхождения, наш запрос будет выглядеть так:


ВЫБРАТЬ
    ВТ.Подразделение КАК Подразделение,
    ВТ.Номенклатура КАК Номенклатура,
    СУММА(ВТ.Количество) КАК Разность
ИЗ
    ВТ_Данные КАК ВТ
СГРУППИРОВАТЬ ПО
    ВТ.Подразделение,
    ВТ.Номенклатура
ИМЕЮЩИЕ
    СУММА(ВТ.Количество) <> 0

В этом случае, если по номенклатуре «Стол» в первом регистре было 10 штук, а во втором -10 штук, сумма даст 0, и благодаря ИМЕЮЩИЕ эта строка не попадет в отчет, что нам и нужно.

Дополнительные нюансы сверки остатков

Выясним причины, которые могут приводить к расхождениям, даже если запрос написан технически верно:

Подводя итог, можно сказать: для получения корректных итогов при сравнении двух таблиц всегда отдавайте предпочтение методу ОБЪЕДИНИТЬ ВСЕ с последующей группировкой и фильтрацией через ИМЕЮЩИЕ. Это избавит вас от проблем с NULL, исключит риск декартова произведения строк и обеспечит высокую скорость работы отчета даже на больших объемах данных.

← На главную