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