Почему запрос к остаткам возвращает пустоту, а с группировкой — единицу вместо нуля?

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

В разработке на платформе 1С часто возникают ситуации, которые на первый взгляд кажутся нелогичными. Одна из таких — разное поведение запроса к остаткам регистра накопления с группировкой и без нее. Давайте разберем, почему запрос без группировки может ничего не возвращать для товара с нулевым остатком, а тот же запрос с функцией СУММА() и оператором СГРУППИРОВАТЬ ПО вдруг выдает результат "1".

Проанализируем два примера запроса, которые привели к путанице.

Запрос 1: Без группировки (не возвращает ничего)


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

Запрос 2: С группировкой (возвращает "1")


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

Выясним причину такого поведения по шагам.

Причина №1: Скрытые механизмы виртуальной таблицы "Остатки"

Ключ к разгадке кроется в том, как платформа 1С обрабатывает запросы к виртуальным таблицам, таким как РегистрНакопления.ИмяРегистра.Остатки. Эти таблицы не хранятся в базе данных в готовом виде, а формируются "на лету" в момент выполнения запроса. Для оптимизации в них встроены скрытые механизмы.

  1. Неявная группировка. Виртуальная таблица остатков уже предоставляет сгруппированные данные. Она показывает итоговые значения ресурсов (например, КоличествоОстаток) в разрезе измерений (Номенклатура, Характеристика, Склад и т.д.) на указанный момент времени.

  2. Неявный отбор по ненулевым ресурсам. Это самый важный момент. По умолчанию платформа 1С автоматически отсекает все записи, у которых значения всех числовых ресурсов равны нулю. Это сделано для того, чтобы в выборку не попадала "бесконечная" номенклатура, по которой никогда не было движений или текущий остаток которой равен нулю. Именно поэтому ваш первый запрос ничего не возвращает: если остаток по номенклатуре &Ном равен нулю, неявный фильтр просто удаляет эту строку из результата.

Таким образом, первый запрос отрабатывает корректно с точки зрения логики платформы: нет ненулевых остатков — нет строк в результате.

Причина №2: Влияние явной группировки "СГРУППИРОВАТЬ ПО"

Когда вы добавляете в запрос явную конструкцию СГРУППИРОВАТЬ ПО, вы переопределяете стандартное поведение. Платформа видит, что вы хотите управлять агрегацией данных самостоятельно, и в некоторых случаях это приводит к отключению неявных механизмов виртуальной таблицы.

В вашем втором запросе явная группировка, скорее всего, отключает тот самый "скрытый" фильтр по ненулевым остаткам. Теперь запрос начинает "видеть" записи с нулевыми значениями, группирует их и пытается вычислить сумму.

Тайна единицы: почему СУММА(0) равна 1?

Это самое аномальное и нелогичное поведение. По всем правилам языка запросов и математики, сумма нулевых значений должна быть равна нулю (0), а сумма по пустой выборке — NULL.

Появление единицы — это, как правило, недокументированная особенность или ошибка, которая может зависеть от конкретной версии платформы 1С или конфигурации. Как показало обсуждение на форуме, в конфигурации "Управление нашей фирмой" (УНФ) такое поведение наблюдается, а в учебных базах — нет.

Рассмотрим наиболее вероятные гипотезы этого феномена:

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

Как получить правильный и предсказуемый результат?

Чтобы избежать подобных сюрпризов и всегда получать корректные данные, включая нулевые остатки, следует использовать более надежные подходы.

Решение 1: Использование функции ЕСТЬNULL()

Чтобы гарантированно получить 0 вместо NULL, если по товару нет остатков, всегда оборачивайте агрегатную функцию в ЕСТЬNULL(). Это стандартная практика, делающая код более надежным. Однако она не спасет от аномального возврата "1". Ее главная задача — обработка пустых выборок.


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

Решение 2: Левое соединение со справочником (самый надежный способ)

Чтобы гарантированно получить список номенклатуры с ее остатками, даже если они нулевые, нужно "идти" не от таблицы остатков, а от справочника номенклатуры, присоединяя к нему остатки с помощью ЛЕВОГО СОЕДИНЕНИЯ.

Этот подход решает проблему в корне:

  1. Мы берем все нужные нам товары из справочника Номенклатура.
  2. Пытаемся присоединить к ним данные из виртуальной таблицы остатков.
  3. Если для товара есть ненулевой остаток, данные присоединятся.
  4. Если остаток нулевой, виртуальная таблица не вернет для него строку, но благодаря ЛЕВОМУ СОЕДИНЕНИЮ запись из справочника номенклатуры все равно останется в результате, а поля из таблицы остатков будут иметь значение NULL.
  5. Функция ЕСТЬNULL() заменяет итоговый NULL на 0.

Посмотрим на пример:


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

Этот запрос всегда вернет корректные остатки по указанному списку номенклатуры, показывая 0 для тех позиций, которых нет на складе.

← На главную