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