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