Почему в 1С возникает ошибка сравнения типов при проверке остатков в выборке запроса?

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

При разработке алгоритмов проведения документов с контролем остатков разработчики часто сталкиваются с ошибкой времени выполнения: «Операции сравнения на больше-меньше допустимы только для значений совпадающих примитивных типов (Булево, Число, Строка, Дата)». При этом в тексте запроса для числовых полей может быть явно прописана функция ЕСТЬNULL(..., 0), однако при отладке в переменной выборки все равно оказывается значение Неопределено. Разберем по шагам, почему так происходит и как правильно организовать выборку данных.

Анализ проблемы: почему в поле выборки появляется Неопределено

Проанализируем типовую ситуацию на примере запроса, формирующего итоги по группировке:


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

Когда мы вызываем метод РезультатЗапроса.Выбрать() без параметров, платформа 1С по умолчанию использует режим обхода ОбходРезультатаЗапроса.ПоГруппировкам. В этом случае первый уровень выборки содержит только итоговые записи по группировочному полю Номенклатура.

Выясним причину ошибки: для итоговых строк рассчитываются только те поля, которые явно перечислены в секции ИТОГИ с агрегатными функциями (СУММА, МИНИМУМ, МАКСИМУМ и др.). Поле КоличествоОстаток в секцию ИТОГИ не включено, поэтому на уровне группировки по номенклатуре платформа возвращает для него значение Неопределено. Конструкция ЕСТЬNULL в секции ВЫБРАТЬ обрабатывает значения только для детальных записей, но не заполняет нерассчитанные итоги группировок.

При выполнении условия Если Выборка.Количество > Выборка.КоличествоОстаток Тогда происходит попытка сравнить числовое значение поля Количество со значением Неопределено поля КоличествоОстаток, что приводит к аварийному завершению процедуры проведения.

Способ 1. Добавление агрегатной функции в секцию ИТОГИ

Если нам необходимо получать и сравнивать общий остаток товаров на уровне группировки по номенклатуре, включим расчет этого поля в итоги запроса. Рассмотрим обновленный фрагмент текста запроса:


|ИТОГИ 
|    МИНИМУМ(Количество), 
|    СУММА(КоличествоОстаток), 
|    МИНИМУМ(ОстатокНаСкладе), 
|    МИНИМУМ(НомерСтроки)
|ПО ДокТЧ.Номенклатура

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

Способ 2. Использование прямого обхода выборки

Если иерархия и группировка записей в коде модуля не требуются, а нужен последовательный перебор всех детальных строк результата, укажем явный тип обхода при вызове метода Выбрать():


Выборка = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.Прямой);

Пока Выборка.Следующий() Цикл
    Если Выборка.Количество > Выборка.КоличествоОстаток Тогда
        // Обработка нехватки остатка
    КонецЕсли;
КонецЦикла;

При прямом обходе секция ИТОГИ игнорируется, а выборка сразу возвращает детальные записи, в которых все поля из блока ВЫБРАТЬ заполнены корректными значениями.

Способ 3. Двухуровневый обход: группировки и детальные записи

Рассмотрим правильную структуру алгоритма при партионном списании, когда контроль остатка выполняется на уровне номенклатуры, а списание себестоимости партий — на детальном уровне:

  1. На верхнем уровне получаем агрегированные данные по номенклатуре и проверяем общий остаток на складе.
  2. Если товара достаточно, переходим к вложенной выборке детальных записей для распределения партий и расчета себестоимости.
  3. Обратим внимание на обращение к полям: детальные поля (Партия, СтоимостьОстаток) читаются из подчиненной выборки, а не из верхней группировки.

Посмотрим на пример реализации двухуровневого обхода:


ВыборкаНоменклатура = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаНоменклатура.Следующий() Цикл
    
    // 1. Проверка общего остатка на уровне номенклатуры
    Если ВыборкаНоменклатура.Количество > ВыборкаНоменклатура.ОстатокНаСкладе Тогда
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "Недостаточно товара: " + ВыборкаНоменклатура.Номенклатура;
        Сообщение.Сообщить();
        Отказ = Истина;
        Продолжить;
    КонецЕсли;
    
    // 2. Обход детальных записей для списания партий
    ОсталосьСписать = ВыборкаНоменклатура.Количество;
    ВыборкаПартии = ВыборкаНоменклатура.Выбрать();
    
    Пока ВыборкаПартии.Следующий() И ОсталосьСписать > 0 Цикл
        Списать = Мин(ОсталосьСписать, ВыборкаПартии.КоличествоОстаток);
        Себестоимость = ?(ВыборкаПартии.КоличествоОстаток = 0, 0, 
            Списать / ВыборкаПартии.КоличествоОстаток * ВыборкаПартии.СтоимостьОстаток);
        
        Движение = Движения.ОстаткиТоваров.Добавить();
        Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
        Движение.Период = Дата;
        Движение.Номенклатура = ВыборкаНоменклатура.Номенклатура;
        Движение.Партия = ВыборкаПартии.Партия;
        Движение.Количество = Списать;
        Движение.Стоимость = Себестоимость;
        
        ОсталосьСписать = ОсталосьСписать - Списать;
    КонецЦикла;
    
КонецЦикла;

Важно: при расчете себестоимости всегда обращайтесь к полям подчиненной выборки (ВыборкаПартии.СтоимостьОстаток, ВыборкаПартии.Партия), а также проверяйте деление на ноль, если остаток по партии равен нулю.

← На главную