Как выгрузить макет табличного документа в таблицу значений в 1С и исправить ошибку «Ожидается ключевое слово ВЫБРАТЬ»?

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

При разработке в среде «1С:Предприятие» часто возникает задача загрузки или обработки данных из табличных документов. Это может быть как макет, встроенный в конфигурацию (например, классификатор, тарифная сетка или заполненный шаблон), так и внешний файл Excel, загруженный в объект типа ТабличныйДокумент. Одним из самых популярных и лаконичных способов конвертации табличного документа в полноценную таблицу значений является использование механизма ПостроительОтчета или ПостроительЗапроса в сочетании с объектом ОписаниеИсточникаДанных. Этот способ называют «кодом в пять строк» за его простоту и быстродействие.

Однако при его использовании разработчики регулярно сталкиваются с крайне неприятной и неочевидной ошибкой: «Ожидается ключевое слово ВЫБРАТЬ» (или {(1, 1)}: Ожидается выражение "ВЫБРАТЬ"). Ситуация усложняется тем, что этот сбой происходит на этапе выполнения метода Выполнить(), хотя никакого явного SQL-запроса программист не пишет.

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

Выясняем причину: почему возникает ошибка «Ожидается ключевое слово ВЫБРАТЬ»?

Для того чтобы понять суть проблемы, нам необходимо заглянуть под капот платформы «1С:Предприятие». Когда мы инициализируем объект ОписаниеИсточникаДанных, передавая в него область табличного документа, платформа пытается автоматически распознать структуру этой таблицы.

Происходит следующий процесс:

  1. Платформа берет самую первую строку из переданной области табличного документа и пытается прочитать из нее заголовки колонок.
  2. Текстовые значения ячеек первой строки преобразуются в имена полей для конструирования внутреннего виртуального запроса (что-то вроде ВЫБРАТЬ Поле1, Поле2 ИЗ Источник).
  3. Если вся первая строка переданной области оказывается пустой (например, макет начинается с пустой декоративной строки, шапки отчета или объединенной области), построитель не находит ни одного имени для формирования полей выборки.
  4. В результате генерируется некорректный пустой запрос, парсер СУБД дает сбой, а разработчик получает системное сообщение об ошибке синтаксиса: «Ожидается ключевое слово ВЫБРАТЬ».

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

Решение проблемы с помощью правильной настройки ПостроителяОтчета

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

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


// 1. Получаем макет из справочника (или используем готовый ТабличныйДокумент)
Макет = Справочники.КлассификаторЕдиницИзмерения.ПолучитьМакет("КлассификаторЕдиницИзмерения");

// 2. Определяем точные координаты области данных.
// Предположим, что первая строка макета пустая или занята названием отчета, 
// а реальные заголовки колонок (Код, Наименование, УсловноеОбозначение) находятся во 2-й строке.
СтрокаЗаголовка = 2; 

// Задаем область: левый верхний угол (СтрокаЗаголовка, 1) и правый нижний (Высота, Ширина)
ОбластьДанных = Макет.Область(СтрокаЗаголовка, 1, Макет.ВысотаТаблицы, Макет.ШиринаТаблицы);

// 3. Создаем источник данных и инициализируем построитель
ПостроительОтчета = Новый ПостроительОтчета;
ПостроительОтчета.ИсточникДанных = Новый ОписаниеИсточникаДанных(ОбластьДанных);

// 4. Выполняем построение запроса и выгружаем результат в Таблицу Значений
ПостроительОтчета.Выполнить();
Тз = ПостроительОтчета.Результат.Выгрузить();

Обратите внимание на объект, который используется для выгрузки. В оригинальном коде автора изначально фигурировал ПостроительЗапроса. Хотя в некоторых старых версиях платформы или при определенных условиях это могло работать, штатным и наиболее стабильным инструментом для разбора табличных документов является именно ПостроительОтчета. Он специально спроектирован для анализа областей ячеек и генерации макетов вывода на их основе.

Важные нюансы автоматической выгрузки через Построитель

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

  1. Правила именования колонок: Имена колонок в полученной ТаблицаЗначений будут созданы на основе текста из ячеек первой строки области. При этом платформа автоматически выполняет нормализацию имен, чтобы они соответствовали правилам именования переменных 1С (без пробелов, знаков препинания и спецсимволов). Например, заголовок «Код ОКЕИ» превратится в имя колонки Код_ОКЕИ, а «Вес, кг» — в Вес__кг. Платформа заменяет все недопустимые знаки на символ подчеркивания.
  2. Пустые ячейки в шапке: Если в вашей строке заголовков некоторые ячейки окажутся пустыми, построитель не выдаст ошибку, но назначит этим колонкам технические имена по умолчанию (например, _1, _2 и так далее). Это может затруднить дальнейшее обращение к колонкам по именам.
  3. Объединенные ячейки: Если в макете используются объединенные ячейки (по вертикали или горизонтали) в зоне заголовков, помните, что реальное текстовое значение хранится только в самой левой верхней ячейке объединения. Остальные ячейки, входящие в объединенную область, будут восприняты построителем как пустые, что приведет к генерации системных имен колонок для этих столбцов.
  4. Контекст исполнения (Клиент-Сервер): Объекты ПостроительОтчета и ОписаниеИсточникаДанных доступны только на сервере. Если вы разрабатываете конфигурацию на управляемых формах, этот код нельзя выполнять непосредственно в клиентских процедурах. Вам необходимо передать ваш ТабличныйДокумент на сервер (например, в функцию с директивой &НаСервереБезКонтекста), выполнить конвертацию там, а затем вернуть результат обратно.

Альтернативный вариант: Надежный ручной обход в цикле

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

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

Проанализируем пример такого алгоритма, который работает как на клиенте, так и на сервере:


// Функция для безопасного преобразования любого текста в валидное имя колонки ТЗ
Функция ПолучитьБезопасноеИмяКолонки(ИсходныйТекст)
    // Разрешенные символы для имен колонок в 1С
    РазрешенныеСимволы = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
                       + "абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"
                       + "0123456789_";
    
    Результат = "";
    Для Поз = 1 По СтрДлина(ИсходныйТекст) Цикл
        Символ = Сред(ИсходныйТекст, Поз, 1);
        Если Найти(РазрешенныеСимволы, Символ) > 0 Тогда
            // Имя колонки не должно начинаться с цифры
            Если Поз = 1 И Найти("0123456789", Символ) > 0 Тогда
                Результат = "_" + Символ;
            Иначе
                Результат = Результат + Символ;
            КонецЕсли;
        Иначе
            Результат = Результат + "_";
        КонецЕсли;
    КонецЦикла;
    
    Если ПустаяСтрока(Результат) Тогда
        Результат = "ИмяКолонкиНеОпределено";
    КонецЕсли;
    
    Возврат Результат;
EndФункции

// Основная процедура конвертации табличного документа в ТЗ
Функция КонвертироватьТабДокВТЗ(ТабДок, СтрокаШапки = 1, СтрокаНачалаДанных = 2) Экспорт
    
    ТЗ = Новый ТаблицаЗначений;
    ШиринаТаблицы = ТабДок.ШиринаТаблицы;
    ВысотаТаблицы = ТабДок.ВысотаТаблицы;
    
    // 1. Формируем колонки таблицы значений на основе указанной строки шапки
    Для НомерКолонки = 1 По ШиринаТаблицы Цикл
        ТекстЯчейки = ТабДок.Область(СтрокаШапки, НомерКолонки).Текст;
        ИмяКолонки = ПолучитьБезопасноеИмяКолонки(ТекстЯчейки);
        
        // Защита от дублирования имен колонок
        Счетчик = 1;
        БазовоеИмя = ИмяКолонки;
        Пока ТЗ.Колонки.Найти(ИмяКолонки) <> Неопределено Цикл
            ИмяКолонки = БазовоеИмя + "_" + Счетчик;
            Счетчик = Счетчик + 1;
        КонецЦикла;
        
        // Добавляем колонку, сохраняя оригинальный текст в качестве синонима
        ТЗ.Колонки.Добавить(ИмяКолонки, , ТекстЯчейки);
    КонецЦикла;
    
    // 2. Заполняем таблицу значений строками с данными
    Для НомерСтроки = СтрокаНачалаДанных По ВысотаТаблицы Цикл
        
        // Проверяем, не является ли вся строка пустой (чтобы не плодить пустые строки в ТЗ)
        СтрокаПустая = Истина;
        Для НомерКолонки = 1 По ШиринаТаблицы Цикл
            Если ЗначениеЗаполнено(ТабДок.Область(НомерСтроки, НомерКолонки).Текст) Тогда
                СтрокаПустая = Ложь;
                Прервать;
            КонецЕсли;
        КонецЦикла;
        
        Если СтрокаПустая Тогда
            Продолжить; // Пропускаем пустую строку
        КонецЕсли;
        
        НоваяСтрока = ТЗ.Добавить();
        
        Для НомерКолонки = 1 По ШиринаТаблицы Цикл
            Ячейка = ТабДок.Область(НомерСтроки, НомерКолонки);
            ИмяТекущейКолонки = ТЗ.Колонки[НомерКолонки - 1].Имя;
            
            // Если ячейка содержит типизированное значение (например, число или дату),
            // считываем именно его, а не просто текст форматирования.
            Если Ячейка.СодержитЗначение Тогда
                НоваяСтрока[ИмяТекущейКолонки] = Ячейка.Значение;
            Иначе
                НоваяСтрока[ИмяТекущейКолонки] = Ячейка.Текст;
            КонецЕсли;
        КонецЦикла;
        
    КонецЦикла;
    
    Возврат ТЗ;
КонецФункции

Преимущества этого ручного подхода очевидны:

Использование Библиотеки стандартных подсистем (БСП)

Если вы ведете разработку в современных типовых конфигурациях (таких как «Управление торговлей 11», «ERP Управление предприятием», «Комплексная автоматизация» или «Бухгалтерия предприятия 3.0»), вам нет необходимости писать велосипеды для разбора табличных документов.

В состав Библиотеки стандартных подсистем (БСП) входит мощная и удобная подсистема «Загрузка данных из файла». Она предоставляет готовый интерфейс для сопоставления колонок, автоматической валидации типов, поиска ссылочных данных (например, элементов справочников по наименованию или ИНН) и обработки ошибок. Использование стандартных механизмов БСП сокращает время разработки программного кода и обеспечивает привычный интерфейс для конечных пользователей системы.

Резюме

Подводя итоги, отметим ключевые выводы нашей работы:

← На главную