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