При переходе с типовых механизмов выборки 1С:Предприятия 7.7 на прямые SQL-запросы с использованием компоненты 1С++ разработчики часто сталкиваются с синтаксическими ошибками Transact-SQL, а также с вопросами корректной подстановки параметров различных типов данных. Разберем типичные ошибки формирования запроса, выясним причины их появления и рассмотрим правильные варианты реализации выборки документов — как по единичному значению, так и по целому списку идентификаторов.
Рассмотрим исходный текст запроса, который вызвал сбой со стороны сервера баз данных:
Запрос = СоздатьОбъект("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.
Самым правильным и безопасным способом передачи единичных значений в текст запроса является параметризация. Рассмотрим, как исправить исходный запрос с использованием метода УстановитьТекстовыйПараметр:
Запрос = СоздатьОбъект("ODBCRecordset");
ТекстЗапроса = "
|SELECT
| ДокИЗ.IDDoc as [Док $Документ.ИнтернетЗаказ]
|FROM
| $Документ.ИнтернетЗаказ as ДокИЗ
|WHERE
| $ДокИЗ.ИДнаСайте = :Айдишник
|";
Запрос.УстановитьТекстовыйПараметр("Айдишник", ТЕ.АйДиНаСайте);
ТабРезультат = Запрос.ВыполнитьИнструкцию(ТекстЗапроса);
Если ТабРезультат.КоличествоСтрок() > 0 Тогда
Сообщить("Найден документ: " + ТабРезультат.ПолучитьЗначение(1, 1));
КонецЕсли;
Разберем по шагам, что изменилось:
FROM.IDDoc.WHERE вместо прямой вставки имени переменной указан именованный параметр с двоеточием — :Айдишник.УстановитьТекстовыйПараметр("Айдишник", ...) выполняет корректную подстановку значения нужного типа данных (включая числовой тип) в текст запроса, предотвращая синтаксические сбои.Проанализируем распространенную ошибку архитектуры: вызов запроса внутри цикла обхода строк исходной таблицы. Создание нового объекта ODBCRecordset и отправка отдельного запроса к SQL Server на каждой итерации цикла приводит к колоссальным накладным расходам, многократно увеличивает время выполнения обработки и нагружает сервер.
Если требуется получить документы сразу для набора идентификаторов, задачу следует решать пакетно — за один запрос к базе данных.
Для передачи набора числовых значений во временную таблицу SQL в 1С++ предусмотрен специализированный метод УложитьСписокЗначений. Рассмотрим пример реализации:
// Сформируем список идентификаторов для поиска
СписИД = СоздатьОбъект("СписокЗначений");
ТЕ.ВыбратьСтроки();
Пока ТЕ.ПолучитьСтроку() = 1 Цикл
СписИД.ДобавитьЗначение(ТЕ.АйДиНаСайте);
КонецЦикла;
Запрос = СоздатьОбъект("ODBCRecordset");
// Выгружаем список чисел во временную SQL-таблицу #ВТ_ИД
// Для чисел указываем разрядность и точность
Запрос.УложитьСписокЗначений(СписИД, "#ВТ_ИД", "Число", 19, 0);
ТекстЗапроса = "
|SELECT
| ДокИЗ.IDDoc as [Док $Документ.ИнтернетЗаказ],
| $ДокИЗ.ИДнаСайте as ИДнаСайте
|FROM
| #ВТ_ИД as ВрТаб
|INNER JOIN
| $Документ.ИнтернетЗаказ as ДокИЗ ON $ДокИЗ.ИДнаСайте = ВрТаб.Val
|";
ТабРезультат = Запрос.ВыполнитьИнструкцию(ТекстЗапроса);
// Очищаем временную таблицу после выполнения
Запрос.ВыполнитьИнструкцию("DROP TABLE #ВТ_ИД");
Разберем подробнее ключевые моменты этого решения:
УложитьСписокЗначений(Список, ИмяВТ, ТипДанных, [Длина], [Точность]) поддерживает выгрузку не только ссылочных объектов 1С, но и примитивных типов данных, таких как "Число".Val.INNER JOIN, что отрабатывает на стороне сервера за минимальное время.DROP TABLE.Если исходные данные изначально находятся в таблице значений 1С, аналогичную операцию можно выполнить с помощью метода УложитьТаблицуЗначений(ТЗ, "#ВТ_Имя"), который автоматически создаст на SQL-сервере таблицу с колонками нужных типов.
Если количество отбираемых документов невелико (в пределах нескольких десятков или сотен значений), можно собрать идентификаторы в строку через запятую и передать их в секцию IN (...):
СтрИД = "";
ТЕ.ВыбратьСтроки();
Пока ТЕ.ПолучитьСтроку() = 1 Цикл
СтрИД = СтрИД + ?(СтрИД = "", "", ", ") + ТЕ.АйДиНаСайте;
КонецЦикла;
Если ПустаяСтрока(СтрИД) = 0 Тогда
Запрос = СоздатьОбъект("ODBCRecordset");
ТекстЗапроса = "
|SELECT
| ДокИЗ.IDDoc as [Док $Документ.ИнтернетЗаказ]
|FROM
| $Документ.ИнтернетЗаказ as ДокИЗ
|WHERE
| $ДокИЗ.ИДнаСайте IN (" + СтрИД + ")
|";
ТабРезультат = Запрос.ВыполнитьИнструкцию(ТекстЗапроса);
КонецЕсли;
Важно: этот способ подходит только для коротких списков. При передаче тысяч идентификаторов оператор IN перегружает оптимизатор запросов SQL Server и может упереться в лимит максимальной длины пакета. Для больших массивов данных всегда следует отдавать предпочтение временным таблицам.