Почему запрос в 1С возвращает пустой результат при отсутствии авансов и как правильно обработать NULL?

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

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

Анализ типичных ошибок в запросе

Рассмотрим основные причины, из-за которых итоговая выборка оказывается пустой при отсутствии движений по одному из регистров:

  1. Условия на присоединяемую таблицу в секции ГДЕ. Если во внешнем соединении (левом, правом или полном) запись во второй таблице отсутствует, все ее поля принимают значение NULL. Проверка вида ГДЕ Таблица2.Регистратор ССЫЛКА Документ.ПлатежноеПоручениеВходящее при значении NULL вычисляется как ЛОЖЬ. В результате СУБД отсекает всю результирующую строку целиком, превращая внешнее соединение во внутреннее.
  2. Использование декартова произведения или внутреннего соединения. Если таблицы перечисляются через запятую в секции ИЗ без явного соединения либо связываются через ВНУТРЕННЕЕ СОЕДИНЕНИЕ, отсутствие строк хотя бы в одном источнике полностью очищает результат запроса.
  3. Путаница между NULL и Неопределено. В языке запросов 1С отсутствующие записи при соединениях всегда возвращают тип NULL. Значение Неопределено встречается только как значение реквизитов составного типа или параметров. Функция ЕСТЬNULL() заменяет исключительно NULL и никак не влияет на Неопределено или на факт полного отсутствия строк в выборке.
  4. Неоптимальный выбор виртуальной таблицы. Использование таблицы ОстаткиИОбороты вместо Обороты приводит к лишней нагрузке на СУБД, так как платформа обращается одновременно к таблицам остатков и оборотов.

Решение 1: Запрос с левым соединением и параметрами виртуальных таблиц

Проанализируем классический вариант: за основу берем данные по заказам (или договорам), а суммы авансов подтягиваем через ЛЕВОЕ СОЕДИНЕНИЕ. При этом условия фильтрации по субконто и счету обязательно выносим внутрь параметров виртуальной таблицы, а проверку регистратора переносим в условие соединения ПО.


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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Заказы.ДоговорКонтрагента КАК Договор,
    СУММА(ЕСТЬNULL(ВТ_Заказы.СуммаДоговора, 0)) КАК СуммаДоговора,
    СУММА(ЕСТЬNULL(ХозрасчетныйОбороты.СуммаОборотКт, 0)) КАК СуммаАвансов
ИЗ
    ВТ_Заказы КАК ВТ_Заказы
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.Обороты(
                ,
                ,
                Регистратор,
                Счет = ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.РасчетыПоАвансамПолученным),
                ,
                Субконто2 = &ДоговорКонтрагента,
                ,
                ) КАК ХозрасчетныйОбороты
        ПО ВТ_Заказы.ДоговорКонтрагента = ХозрасчетныйОбороты.Субконто2
            И (ХозрасчетныйОбороты.Регистратор ССЫЛКА Документ.ПлатежноеПоручениеВходящее)
СГРУППИРОВАТЬ ПО
    ВТ_Заказы.ДоговорКонтрагента

В этом варианте, даже если по регистру бухгалтерии не найдется ни одной проводки, строка из временной таблицы ВТ_Заказы не пропадет, а поле СуммаАвансов корректно заполнится нулем благодаря ЕСТЬNULL().

Решение 2: Схема через ОБЪЕДИНИТЬ ВСЕ (наиболее надежный вариант)

Рассмотрим альтернативную схему, которая считается лучшей практикой в 1С при сборе данных из разнородных регистров. Вместо внешних соединений мы объединяем выборки по вертикали и выполняем группировку. Это полностью исключает потерю строк и проблемы с NULL.


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

ОБЪЕДИНИТЬ ВСЕ

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

Обернем полученную таблицу во внешний запрос с группировкой:


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

Обработка результата запроса в коде 1С

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


Результат = Запрос.Выполнить();

СуммаДоговора = 0;
СуммаАвансов  = 0;

Если НЕ Результат.Пустой() Тогда
    Выборка = Результат.Выбрать();
    Если Выборка.Следующий() Тогда
        СуммаДоговора = Выборка.СуммаДоговора;
        СуммаАвансов  = Выборка.СуммаАвансов;
    КонецЕсли;
КонецЕсли;

Таким образом, при отсутствии данных переменные гарантированно сохранят числовое значение 0, предотвращая ошибки типа Преобразование к типу Число не может быть выполнено при дальнейших вычислениях.

← На главную