Как исправить ошибку «Ожидается выражение ")"» в запросе 1С при переносе кода печатной формы?

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

При переносе кода внешних печатных форм и обработок между различными конфигурациями (например, при переходе с устаревшей «Управление торговлей 10.3» на современную «Управление торговлей 11.4») разработчики часто сталкиваются со сбоями в динамически формируемых запросах. Одна из самых коварных ошибок, способных запутать даже опытного программиста — это «Ожидается выражение ")"».

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

В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем синтаксические промахи в динамическом коде и рассмотрим эффективные методики отладки внешних печатных форм.

Выясняем причину: почему парсер запросов требует скобку?

Когда мы собираем текст запроса из отдельных строковых переменных (псевдодинамическое конструирование), очень легко допустить ошибку на стыке соединяемых строк. Парсер платформы ожидает увидеть строго определенную последовательность ключевых слов. Если эта последовательность нарушается, система пытается интерпретировать некорректный участок кода как часть предыдущего выражения, что и приводит к ложным сообщениям об отсутствии закрывающей скобки ")".

Рассмотрим детально три критические ошибки, которые были допущены при формировании динамического запроса в коде печатной формы:

  1. Пропуск ключевого слова соединения (JOIN).

    В исходном коде автора переменная ТекстИсточникАртикула была объявлена следующим образом:

    
    ТекстИсточникАртикула = " | РегистрСведений.НоменклатураКонтрагентовБЭД КАК НоменклатураКонтрагентовБЭД | ПО ... ";
    

    При сложении строк в тексте запроса получался такой фрагмент:

    
    ИЗ Документ.ЗаказПоставщику.Товары КАК ЗаказПоставщику РегистрСведений.НоменклатураКонтрагентовБЭД КАК НоменклатураКонтрагентовБЭД ПО ...
    

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

  2. Лишние запятые в секции связи таблиц (ПО).

    Это самая распространенная причина ошибки «Ожидается выражение ")"». В коде автора условия связи были записаны так:

    
    ПО НоменклатураКонтрагентов.Номенклатура = ЗаказПоставщику.Номенклатура, 
    И НоменклатураКонтрагентов.ХарактеристикаНоменклатуры = ЗаказПоставщику.ХарактеристикаНоменклатуры, 
    И НоменклатураКонтрагентов.Контрагент = &Контрагент
    

    В чем ошибка: В секции ПО условия соединения таблиц должны объединяться исключительно с помощью логических операторов И (AND) или ИЛИ (OR). Использование запятых в качестве разделителей здесь категорически недопустимо. Запятая заставляет парсер думать, что описание условий связи завершено и начинается новое поле или новая секция запроса, из-за чего он выдает нерелевантную ошибку о скобках.

  3. Рассогласование псевдонимов (алиасов) таблиц.

    Обратите внимание на объявление таблицы источника и обращение к ее полям:

    
    РегистрСведений.НоменклатураКонтрагентовБЭД КАК НоменклатураКонтрагентовБЭД
    ПО НоменклатураКонтрагентов.Номенклатура = ЗаказПоставщику.Номенклатура
    

    В чем ошибка: Таблице регистра присваивается псевдоним НоменклатураКонтрагентовБЭД, однако в условиях связи поля адресуются через имя НоменклатураКонтрагентов (без суффикса «БЭД»). Платформа не сможет выполнить такой запрос, так как таблица с именем без суффикса ей неизвестна в текущем контексте.

Для исправления этих ошибок необходимо полностью отказаться от запятых в условиях связи, добавить пропущенное ключевое слово ЛЕВОЕ СОЕДИНЕНИЕ и привести к единому виду все псевдонимы таблиц.

Как отлаживать динамические запросы во внешних печатных формах?

Основная сложность при работе с внешними печатными формами (ВПФ) заключается в том, что они запускаются в безопасном режиме на стороне сервера. Из-за этого стандартная точка останова в конфигураторе по нажатию клавиши F9 часто не срабатывает, прерывая выполнение ошибкой еще до того, как разработчик сможет проанализировать содержимое переменных.

Рассмотрим три надежных метода, которые помогут перехватить и отладить динамический текст запроса.

Способ 1. Временный принудительный вывод текста запроса в ошибку

Если отладчик упорно не хочет останавливаться на нужной строке серверного кода внешней обработки, мы можем заставить платформу саму показать нам собранный текст запроса. Для этого непосредственно перед строкой выполнения запроса ЗапросТовары = Запрос.Выполнить().Выгрузить(); добавьте команду генерации исключения:


// Временный код для перехвата текста запроса
ВызватьИсключение Запрос.Текст;
ЗапросТовары = Запрос.Выполнить().Выгрузить();

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

Способ 2. Запись сформированного запроса во внешний файл

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


ПутьКФайлу = "C:\temp\debug_query.txt"; // Убедитесь, что у службы сервера 1С есть доступ к этой папке
ТекстФайла = Новый ЗаписьТекста(ПутьКФайлу, КодировкаТекста.UTF8);
ТекстФайла.ЗаписатьСтроку(Запрос.Текст);
ТекстФайла.Закрыть();

После вызова печати достаточно открыть файл debug_query.txt любым текстовым редактором и изучить результат сборки строк.

Способ 3. Отладка внешней обработки через прямое открытие

Чтобы обойти ограничения БСП по запуску печатных форм в безопасном режиме, адаптируйте обработку для прямого открытия через меню Файл -> Открыть. Для этого выполните следующие шаги:

  1. Создайте у внешней обработки основную форму.
  2. Добавьте на форму кнопку «Выполнить отладку» и реквизит «СсылкаНаДокумент» с типом ссылки нужного документа (например, ДокументСсылка.ЗаказПоставщику).
  3. В обработчике нажатия кнопки вызовите экспортную функцию печати, передав туда ссылку на выбранный документ.
  4. Запустите 1С:Предприятие в режиме отладки, откройте обработку как обычный файл, выберите документ и нажмите кнопку. Точки останова будут срабатывать гарантированно!

Полный пример исправленного кода формирования запроса

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


Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Контрагент", СсылкаНаДок.Контрагент);
Запрос.УстановитьПараметр("ТекущийДокумент", СсылкаНаДок);

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

ТекстПоляТовараДляУслуг = 
" | ВЫБОР КОГДА (ВЫРАЗИТЬ (НаименованиеНоменклатурыКонтрагента КАК Строка(100)) ЕСТЬ NULL ИЛИ ВЫРАЗИТЬ (НаименованиеНоменклатурыКонтрагента КАК Строка(100)) = """") 
  |     ТОГДА ВЫРАЗИТЬ (ЗаказПоставщику.Номенклатура.НаименованиеПолное КАК Строка(100)) 
  |     ИНАЧЕ ВЫРАЗИТЬ (НаименованиеНоменклатурыКонтрагента КАК Строка(100)) 
  | КОНЕЦ КАК Товар,";

ТекстПоляАртикула = ", | АртикулКонтрагента";
ТекстВыборкиАртикула = ", | АртикулНоменклатурыКонтрагента КАК АртикулКонтрагента";
ТекстВыборкиНоменклатуры = ", | ВЫРАЗИТЬ(НаименованиеНоменклатурыКонтрагента КАК Строка(100)) КАК НаименованиеКонтрагента";

// ИСПРАВЛЕНО: Добавлен оператор ЛЕВОЕ СОЕДИНЕНИЕ, убраны лишние запятые, исправлены алиасы
ТекстИсточникАртикула = 
" | ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НоменклатураКонтрагентовБЭД КАК НоменклатураКонтрагентовБЭД 
  |     ПО НоменклатураКонтрагентовБЭД.Номенклатура = ЗаказПоставщику.Номенклатура 
  |     И НоменклатураКонтрагентовБЭД.ХарактеристикаНоменклатуры = ЗаказПоставщику.ХарактеристикаНоменклатуры 
  |     И НоменклатураКонтрагентовБЭД.Контрагент = &Контрагент";

ТекстИсточникАртикулаБезХарактеристики = 
" | ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НоменклатураКонтрагентовБЭД КАК НоменклатураКонтрагентовБЭД 
  |     ПО НоменклатураКонтрагентовБЭД.Номенклатура = ЗаказПоставщику.Номенклатура 
  |     И НоменклатураКонтрагентовБЭД.Контрагент = &Контрагент";

ТекстГруппировкиАртикулаИНоменклатуры = ", | АртикулНоменклатурыКонтрагента, | ВЫРАЗИТЬ(НаименованиеНоменклатурыКонтрагента КАК Строка(100))";

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

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

Применение этого структурированного и исправленного варианта кода полностью решает проблему с компиляцией запроса. Теперь печатная форма без проблем откроется в конструкторе запросов и будет успешно выполняться в среде 1С:Предприятие 8.3.

← На главную