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