Как получить документ по значению реквизита шапки в прямом SQL-запросе через 1С++?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При переходе с типовых механизмов выборки 1С:Предприятия 7.7 на прямые SQL-запросы с использованием компоненты 1С++ разработчики часто сталкиваются с синтаксическими ошибками Transact-SQL, а также с вопросами корректной подстановки параметров различных типов данных. Разберем типичные ошибки формирования запроса, выясним причины их появления и рассмотрим правильные варианты реализации выборки документов — как по единичному значению, так и по целому списку идентификаторов.

Анализ синтаксической ошибки: лишняя запятая перед предложением FROM

Рассмотрим исходный текст запроса, который вызвал сбой со стороны сервера баз данных:


Запрос = СоздатьОбъект("ODBCRecordset");
ТЗ = "
|select
|    ДокИЗ.ID as [Дк $Документ.ИнтернетЗаказ],
|
|from
|    $Документ.ИнтернетЗаказ as ДокИЗ
|where
|    $ДокИЗ.ИДнаСайте = Айдишник";

При выполнении этого кода SQL Server возвращает ошибку Incorrect syntax near the keyword 'from'. Выясним причину такого поведения. В блоке SELECT после псевдонима поля указана запятая (ДокИЗ.ID as [Дк $Документ.ИнтернетЗаказ],), после которой сразу следует ключевое слово FROM. В грамматике SQL запятая является разделителем колонок, и если после нее нет следующего поля или выражения, парсер T-SQL выдает синтаксическую ошибку.

Кроме того, в выражении ДокИЗ.ID допущена неточность: идентификатор документа в таблицах 1С 7.7 хранится в поле IDDOC, а не ID.

Также имя переменной Айдишник написано прямо в строке SQL-запроса. SQL Server ничего не знает о переменных встроенного языка 1С, поэтому воспринимает это слово как имя несуществующего столбца таблицы.

Чтобы быстро обнаружить такие проблемы, проанализируем полезную возможность отладки компоненты 1С++: перед выполнением запроса следует вызвать метод Запрос.Отладка(1);. Это позволяет перехватить результирующий SQL-текст после того, как парсер 1С++ обработал все метаимена (знаки доллара $), и увидеть запрос ровно в том виде, в котором он уходит на SQL Server.

Способ 1. Использование именованных параметров через УстановитьТекстовыйПараметр

Самым правильным и безопасным способом передачи единичных значений в текст запроса является параметризация. Рассмотрим, как исправить исходный запрос с использованием метода УстановитьТекстовыйПараметр:


Запрос = СоздатьОбъект("ODBCRecordset");

ТекстЗапроса = "
|SELECT
|    ДокИЗ.IDDoc as [Док $Документ.ИнтернетЗаказ]
|FROM
|    $Документ.ИнтернетЗаказ as ДокИЗ
|WHERE
|    $ДокИЗ.ИДнаСайте = :Айдишник
|";

Запрос.УстановитьТекстовыйПараметр("Айдишник", ТЕ.АйДиНаСайте);
ТабРезультат = Запрос.ВыполнитьИнструкцию(ТекстЗапроса);

Если ТабРезультат.КоличествоСтрок() > 0 Тогда
    Сообщить("Найден документ: " + ТабРезультат.ПолучитьЗначение(1, 1));
КонецЕсли;

Разберем по шагам, что изменилось:

  1. Удалена лишняя запятая перед ключевым словом FROM.
  2. Использовано верное системное поле идентификатора документа: IDDoc.
  3. В секции WHERE вместо прямой вставки имени переменной указан именованный параметр с двоеточием — :Айдишник.
  4. Метод УстановитьТекстовыйПараметр("Айдишник", ...) выполняет корректную подстановку значения нужного типа данных (включая числовой тип) в текст запроса, предотвращая синтаксические сбои.

Способ 2. Оптимизация пакетной выборки: исключение антипаттерна N+1

Проанализируем распространенную ошибку архитектуры: вызов запроса внутри цикла обхода строк исходной таблицы. Создание нового объекта ODBCRecordset и отправка отдельного запроса к SQL Server на каждой итерации цикла приводит к колоссальным накладным расходам, многократно увеличивает время выполнения обработки и нагружает сервер.

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

Для передачи набора числовых значений во временную таблицу SQL в 1С++ предусмотрен специализированный метод УложитьСписокЗначений. Рассмотрим пример реализации:


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

Запрос = СоздатьОбъект("ODBCRecordset");

// Выгружаем список чисел во временную SQL-таблицу #ВТ_ИД
// Для чисел указываем разрядность и точность
Запрос.УложитьСписокЗначений(СписИД, "#ВТ_ИД", "Число", 19, 0);

ТекстЗапроса = "
|SELECT
|    ДокИЗ.IDDoc as [Док $Документ.ИнтернетЗаказ],
|    $ДокИЗ.ИДнаСайте as ИДнаСайте
|FROM
|    #ВТ_ИД as ВрТаб
|INNER JOIN
|    $Документ.ИнтернетЗаказ as ДокИЗ ON $ДокИЗ.ИДнаСайте = ВрТаб.Val
|";

ТабРезультат = Запрос.ВыполнитьИнструкцию(ТекстЗапроса);

// Очищаем временную таблицу после выполнения
Запрос.ВыполнитьИнструкцию("DROP TABLE #ВТ_ИД");

Разберем подробнее ключевые моменты этого решения:

  1. Метод УложитьСписокЗначений(Список, ИмяВТ, ТипДанных, [Длина], [Точность]) поддерживает выгрузку не только ссылочных объектов 1С, но и примитивных типов данных, таких как "Число".
  2. В созданной временной таблице поле со значениями получает имя по умолчанию Val.
  3. Вместо множества запросов в цикле выполняется единый SQL-запрос с соединением INNER JOIN, что отрабатывает на стороне сервера за минимальное время.
  4. После завершения работы освобождаем ресурсы с помощью команды DROP TABLE.

Если исходные данные изначально находятся в таблице значений 1С, аналогичную операцию можно выполнить с помощью метода УложитьТаблицуЗначений(ТЗ, "#ВТ_Имя"), который автоматически создаст на SQL-сервере таблицу с колонками нужных типов.

Способ 3. Передача небольшого списка через оператор IN

Если количество отбираемых документов невелико (в пределах нескольких десятков или сотен значений), можно собрать идентификаторы в строку через запятую и передать их в секцию IN (...):


СтрИД = "";
ТЕ.ВыбратьСтроки();
Пока ТЕ.ПолучитьСтроку() = 1 Цикл
    СтрИД = СтрИД + ?(СтрИД = "", "", ", ") + ТЕ.АйДиНаСайте;
КонецЦикла;

Если ПустаяСтрока(СтрИД) = 0 Тогда
    Запрос = СоздатьОбъект("ODBCRecordset");
    
    ТекстЗапроса = "
    |SELECT
    |    ДокИЗ.IDDoc as [Док $Документ.ИнтернетЗаказ]
    |FROM
    |    $Документ.ИнтернетЗаказ as ДокИЗ
    |WHERE
    |    $ДокИЗ.ИДнаСайте IN (" + СтрИД + ")
    |";
    
    ТабРезультат = Запрос.ВыполнитьИнструкцию(ТекстЗапроса);
КонецЕсли;

Важно: этот способ подходит только для коротких списков. При передаче тысяч идентификаторов оператор IN перегружает оптимизатор запросов SQL Server и может упереться в лимит максимальной длины пакета. Для больших массивов данных всегда следует отдавать предпочтение временным таблицам.

← На главную