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