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