Почему НАЧАЛОПЕРИОДА в запросе 1С выдает ошибку при совместном использовании с ЕстьNULL и NULL, и как это исправить?

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

Каждый разработчик 1С рано или поздно сталкивается с необходимостью динамического построения запросов. Часто для этого используются временные таблицы или вложенные запросы-«пустышки», которые служат для инициализации структуры полей (например, при объединении таблиц в цикле). Однако при попытке обработать такие поля стандартными функциями работы с датами можно столкнуться с крайне непонятным поведением платформы. Программа выдает ошибку компиляции запроса, хотя, казалось бы, все проверки на NULL выполнены корректно.

В этой статье мы подробно разберем, почему возникает ошибка «Неверные параметры НАЧАЛОПЕРИОДА» при передаче туда результата функции ЕстьNULL, выясним внутренние механизмы работы транслятора запросов 1С и рассмотрим три надежных способа обхода этой проблемы.

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

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


ВЫБРАТЬ
    БигС.Док КАК Док,
    БигС.Номер КАК Номер,
    БигС.Дата КАК Дата,
    ЕстьNULL(БигС.Дата, ДАТАВРЕМЯ(0001, 01, 01, 0, 0, 0)) КАК Поле1,
    НАЧАЛОПЕРИОДА(ЕстьNULL(NULL, ДАТАВРЕМЯ(0001, 01, 01, 0, 0, 0)), ДЕНЬ) КАК Пр
ИЗ
    (ВЫБРАТЬ NULL КАК Док, NULL КАК Номер, NULL КАК Дата) КАК БигС

Этот запрос выполняется абсолютно успешно и возвращает строку, заполненную значениями NULL и корректными пустыми датами в полях Поле1 и Пр.

Однако стоит нам изменить пятую колонку и попытаться передать в функцию НАЧАЛОПЕРИОДА не явный литерал NULL, а поле из нашего виртуального подзапроса:


НАЧАЛОПЕРИОДА(ЕстьNULL(БигС.Дата, ДАТАВРЕМЯ(0001, 01, 01, 0, 0, 0)), ДЕНЬ) КАК Пр

Как тут же платформа возвращает ошибку синтаксического контроля:

Неверные параметры "НАЧАЛОПЕРИОДА", НАЧАЛОПЕРИОДА(<<?>>ЕстьNULL(БигС.Дата, ДАТАВРЕМЯ(0001,01,01,0,0,0)), ДЕНЬ) КАК Пр

Возникает логичный вопрос: почему конструкция с явным ЕстьNULL(NULL, ...) работает без нареканий, а при замене первого аргумента на поле БигС.Дата (которое также содержит NULL) транслятор запросов выдает ошибку? Давайте разберем этот механизм изнутри.

Выясняем причину: механизмы статической типизации 1С

В отличие от СУБД (например, MS SQL Server или PostgreSQL), которые выполняют динамическую типизацию непосредственно в момент исполнения кода, платформа «1С:Предприятие» использует встроенный транслятор запросов со строгой статической проверкой типов.

Платформа анализирует типы полей еще до того, как запрос будет отправлен в СУБД. Для функций работы с датами, таких как НАЧАЛОПЕРИОДА, КОНЕЦПЕРИОДА или ДОБАВИТЬКДАТЕ, компилятор жестко требует, чтобы аргумент имел гарантированный тип Дата (или составной тип, включающий в себя этот тип данных).

Посмотрим, как компилятор видит наши выражения:

  1. Выражение ЕстьNULL(NULL, ДАТАВРЕМЯ(...)): Здесь компилятор видит в качестве первого параметра константный литерал NULL. Ему заранее известно, что значение первого параметра всегда пустое, поэтому результатом функции гарантированно станет второй параметр. Тип второго параметра — жестко определенная Дата. Компилятор без проблем одобряет этот аргумент для функции НАЧАЛОПЕРИОДА.
  2. Выражение ЕстьNULL(БигС.Дата, ДАТАВРЕМЯ(...)): Здесь ситуация принципиально иная. Источником данных выступает поле БигС.Дата из подзапроса. Поскольку во вложенном запросе поле определено просто как NULL КАК Дата, компилятор регистрирует в метаданных этой временной выборки колонку с единственным системным типом NULL (в системе 1С NULL — это полноценный самостоятельный тип данных).

Когда компилятор пытается определить тип результата функции ЕстьNULL(БигС.Дата, ...), он ориентируется на тип исходного проверяемого поля (первого аргумента). Поскольку тип этого поля строго NULL (в нем нет описания типа Дата), компилятор считает, что результат всего выражения не может быть приведен к дате. В итоге проверка типов для функции НАЧАЛОПЕРИОДА проваливается на этапе синтаксического анализа.

Важное примечание: По этой же причине не сработает и конструкция ВЫБОР:


ВЫБОР 
    КОГДА БигС.Дата Есть NULL ТЕМ БОЛЕЕ ДАТАВРЕМЯ(0001,01,01,0,0,0) 
    ИНАЧЕ НАЧАЛОПЕРИОДА(БигС.Дата, ДЕНЬ) 
КОНЕЦ

Компилятор анализирует типы данных во всех ветках оператора ВЫБОР, даже если логически ветка ИНАЧЕ никогда не выполнится для значения NULL. Видя несовместимый тип поля БигС.Дата внутри НАЧАЛОПЕРИОДА, система сразу же заблокирует исполнение запроса.

Решения проблемы: как правильно типизировать поля

Для того чтобы обойти ограничения статического анализатора типов, нам необходимо явно подсказать платформе, какой тип данных ожидается в полях. Рассмотрим три практических способа реализации этой задачи.

Способ 1. Явная типизация «пустышек» во вложенном запросе (Рекомендуемый подход)

Самым грамотным и архитектурно правильным решением является явное определение типов всех пустых колонок еще на этапе их объявления во вложенном запросе. Для этого мы используем встроенную функцию ВЫРАЗИТЬ:


ВЫБРАТЬ
    БигС.Док КАК Док,
    БигС.Номер КАК Номер,
    БигС.Дата КАК Дата,
    НАЧАЛОПЕРИОДА(ЕстьNULL(БигС.Дата, ДАТАВРЕМЯ(0001, 01, 01, 0, 0, 0)), ДЕНЬ) КАК Пр
ИЗ
    (ВЫБРАТЬ 
        ВЫРАЗИТЬ(NULL КАК Справочник.Ссылка.Номенклатура) КАК Док, 
        ВЫРАЗИТЬ(NULL КАК Строка(10)) КАК Номер, 
        ВЫРАЗИТЬ(NULL КАК Дата) КАК Дата
    ) КАК БигС

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

Способ 2. Локальное приведение типов во внешнем запросе

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


НАЧАЛОПЕРИОДА(ВЫРАЗИТЬ(ЕстьNULL(БигС.Дата, ДАТАВРЕМЯ(0001, 01, 01, 0, 0, 0)) КАК Дата), ДЕНЬ) КАК Пр

Также жизнеспособен и более лаконичный вариант, предложенный участниками обсуждения:

НАЧАЛОПЕРИОДА(ВЫРАЗИТЬ(БигС.Дата КАК Дата), ДЕНЬ) КАК Пр

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

Способ 3. Использование типизированных параметров запроса

При динамическом построении запросов в коде конфигурации хорошей практикой является уход от использования жестко прописанных текстовых констант в пользу типизированных параметров:


// В модуле на встроенном языке:
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   НАЧАЛОПЕРИОДА(ЕстьNULL(БигС.Дата, &ПустаяДата), ДЕНЬ) КАК Пр
    |ИЗ
    |   (ВЫБРАТЬ &ПустаяДата КАК Дата) КАК БигС";

Запрос.УстановитьПараметр("ПустаяДата", '00010101000000'); // Передаем пустую дату нужного типа

В данном случае платформа автоматически определит тип передаваемого параметра как Дата, создаст соответствующее типизированное поле в подзапросе, и никаких ошибок компиляции не возникнет.

Изучив эти несложные правила типизации в запросах 1С, вы сможете проектировать более надежные и отказоустойчивые алгоритмы, избегая неприятных сюрпризов при работе с пустыми значениями.

← На главную