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