Как получить единственное значение из базы данных MS SQL в 1С без ошибок?

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

При реализации прямой интеграции между платформой 1С:Предприятие и СУБД MS SQL Server разработчики часто сталкиваются с классической технической проблемой. При попытке вызвать хранимую процедуру или выполнить SQL-скрипт для получения одного единственного значения (например, идентификатора созданной записи) через технологию ADO, программа возвращает ошибку выполнения: «Произошла исключительная ситуация (ADODB.Recordset): Операция не допускается, если объект закрыт».

В этой статье мы подробно разберем природу возникновения данной ошибки, рассмотрим несколько способов ее устранения — от простого изменения текста SQL-запроса до использования профессионального объектного подхода — и обсудим современные альтернативы, которые предлагает сама платформа 1С.

Почему возникает ошибка «Операция не допускается, если объект закрыт»?

Давайте выясним причину такого поведения СУБД и механизма ADO. Когда мы выполняем пакетный SQL-запрос или хранимую процедуру, содержащую инструкции модификации данных (такие как INSERT, UPDATE, DELETE, или даже простое объявление переменных DECLARE), MS SQL Server по умолчанию отправляет клиенту служебные сообщения о количестве обработанных строк (например, «1 row(s) affected»).

Для технологии ADO (ActiveX Data Objects) каждое такое служебное сообщение интерпретируется как отдельный набор записей (Recordset). Однако этот набор является техническим, он пуст и закрыт для чтения данных. Когда наш код в 1С обращается к свойствам полученного объекта, например RS.EOF или RS.Fields, он натыкается на самый первый пустой технический пакет. Поскольку реальных строк данных в нем нет, система генерирует исключение, сообщая, что объект закрыт.

Ниже мы подробно разберем четыре практических метода решения этой проблемы.

Решение №1: Использование инструкции SET NOCOUNT ON (Быстрый обход проблемы)

Самый простой и быстрый способ заставить СУБД не присылать лишние технические пакеты — использовать директиву SET NOCOUNT ON; в самом начале вашего SQL-кода. Эта инструкция запрещает серверу отправлять сообщения о количестве затронутых строк, благодаря чему ADO сразу получает содержательный набор данных.

Также важно не просто запустить процедуру, а принудительно вернуть результат работы в виде таблицы с одной строкой и одной колонкой с помощью ключевого слова SELECT.

Проанализируем пример кода на стороне 1С:


// Инициализируем подключение к MS SQL Server
Connection = Новый COMОбъект("ADODB.Connection");
ConnectionString = "Provider=SQLOLEDB.1;Data Source=SQL_SERVER;Initial Catalog=TRADE_DB;User ID=sa;Password=myPassword;";

Попытка
    Connection.Open(ConnectionString);
Исключение
    Сообщить("Ошибка подключения: " + ОписаниеОшибки());
    Возврат;
КонецПопытки;

// Формируем текст запроса с обязательным SET NOCOUNT ON и SELECT в конце
ТекстЗапроса = "
|SET NOCOUNT ON;
|DECLARE @num_id INT;
|EXECUTE @num_id = [RT].[UD_INT_CREATE_] 'Some string', 'E', 200, 'Some string';
|SELECT @num_id AS num_id;
|";

// Выполняем запрос
RecordSet = Connection.Execute(ТекстЗапроса);

// Проверяем, получены ли данные
Если НЕ RecordSet.EOF Тогда
    РезультатИД = RecordSet.Fields("num_id").Value;
    Сообщить("Получен ID: " + Строка(РезультатИД));
Иначе
    Сообщить("Запрос не вернул данных!");
КонецЕсли;

// Закрываем соединения
RecordSet.Close();
Connection.Close();

Важное примечание: при таком подходе метод Connection.Execute() возвращает самый простой тип курсора — «только вперед, только чтение» (Forward-Only, Read-Only). Для получения одного скалярного значения это самый быстрый и наименее ресурсоемкий вариант.

Решение №2: Использование объекта ADODB.Command и параметров (Профессиональный путь)

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

Профессиональный и максимально безопасный подход для вызова хранимых процедур — использование объекта ADODB.Command и коллекции параметров Parameters. При использовании этого метода нам **не требуется** модифицировать SQL-код, прописывать SET NOCOUNT ON или искусственно дописывать SELECT.

Рассмотрим пример реализации этого сценария:


Connection = Новый COMОбъект("ADODB.Connection");
Connection.Open("Provider=SQLOLEDB.1;Data Source=SQL_SERVER;Initial Catalog=TRADE_DB;User ID=sa;Password=myPassword;");

Command = Новый COMОбъект("ADODB.Command");
Command.ActiveConnection = Connection;
Command.CommandText = "[RT].[UD_INT_CREATE_]"; // Имя хранимой процедуры
Command.CommandType = 4; // 4 означает adCmdStoredProc (вызов хранимой процедуры)

// 1. Создаем параметр для получения RETURN-значения процедуры.
// Важно: возвращаемый параметр всегда должен добавляться в коллекцию самым ПЕРВЫМ!
// Параметры метода CreateParameter: Имя, ТипДанных, Направление, Размер, Значение
// Тип: 3 = adInteger (целое число)
// Направление: 4 = adParamReturnValue (возвращаемое значение)
ParamReturn = Command.CreateParameter("ReturnValue", 3, 4);
Command.Parameters.Append(ParamReturn);

// 2. Добавляем входные параметры (Направление: 1 = adParamInput)
// Тип: 200 = adVarChar (строка переменной длины)
Command.Parameters.Append(Command.CreateParameter("Param1", 200, 1, 255, "Some string"));
Command.Parameters.Append(Command.CreateParameter("Param2", 200, 1, 1, "E"));
// Тип: 3 = adInteger (целое число)
Command.Parameters.Append(Command.CreateParameter("Param3", 3, 1, , 200));
Command.Parameters.Append(Command.CreateParameter("Param4", 200, 1, 255, "Some string"));

// Выполняем команду. Нам не нужно получать Recordset, так как данные вернутся в параметр ReturnValue
Command.Execute();

// Извлекаем значение напрямую из параметров команды
РезультатИД = Command.Parameters("ReturnValue").Value;

Сообщить("Успешно получено значение через параметры: " + Строка(РезультатИД));

Connection.Close();

Использование ADODB.Command гарантирует автоматическую типизацию параметров и защищает базу данных от некорректных запросов.

Решение №3: Обход закрытых пакетов с помощью метода NextRecordset

Что делать, если хранимая процедура находится в закрытом системном модуле или принадлежит стороннему программному продукту, и мы не можем отредактировать ее код, чтобы добавить SET NOCOUNT ON, а объект ADODB.Command по какой-то причине использовать неудобно?

Мы можем обойти пустые технические пакеты программно на стороне 1С с помощью встроенного метода NextRecordset(). Этот метод последовательно переключает объект Recordset на следующий пакет данных, возвращенный сервером.

Давайте посмотрим, как реализовать этот алгоритм:


Connection = Новый COMОбъект("ADODB.Connection");
Connection.Open("Provider=SQLOLEDB.1;Data Source=SQL_SERVER;Initial Catalog=TRADE_DB;User ID=sa;Password=myPassword;");

ТекстЗапроса = "EXECUTE [RT].[UD_INT_CREATE_] 'Some string', 'E', 200, 'Some string'";
RecordSet = Connection.Execute(ТекстЗапроса);

// Цикл для пропуска пустых (закрытых) наборов данных
Пока RecordSet <> Неопределено Цикл
    // Состояние 1 (adStateOpen) означает, что набор записей открыт и готов к чтению
    Если RecordSet.State = 1 Тогда 
        Прервать; // Мы нашли нужный нам пакет с данными
    КонецЕсли;
    
    // Переходим к следующему набору данных
    RecordSet = RecordSet.NextRecordset();
КонецЦикла;

// Читаем значение из открытого Recordset
Если RecordSet <> Неопределено Тогда
    Если НЕ RecordSet.EOF Тогда
        РезультатИД = RecordSet.Fields(0).Value;
        Сообщить("Значение, полученное через обход пакетов: " + Строка(РезультатИД));
    КонецЕсли;
    RecordSet.Close();
Иначе
    Сообщить("Сервер не вернул ни одного открытого набора данных!");
КонецЕсли;

Connection.Close();

Решение №4: Современная альтернатива — Внешние источники данных

Важно помнить, что использование COM-объектов ADODB имеет критический архитектурный недостаток — сильную привязку к операционной системе Windows. Если ваш сервер 1С:Предприятие функционирует под управлением операционной системы Linux, любая попытка инициализации Новый COMОбъект приведет к аварийному завершению работы системы.

Начиная с версии платформы 8.2.14, фирма 1С добавила встроенный декларативный механизм — Внешние источники данных. С его помощью вы можете:

  1. Подключить внешнюю СУБД (MS SQL, PostgreSQL, Oracle) непосредственно в дереве метаданных вашей конфигурации в конфигураторе.
  2. Импортировать таблицы, представления и хранимые процедуры сторонней базы данных прямо в 1С.
  3. Вызывать хранимые процедуры нативно на встроенном языке 1С без написания низкоуровневых скриптов и заботы о типах данных.

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


// Вызов хранимой процедуры как обычного метода 1С
Результат = ВнешниеИсточникиДанных.ВнешняяБазаSQL.Процедуры.UD_INT_CREATE_(
    "Some string", 
    "E", 
    200, 
    "Some string"
);

// Платформа 1С автоматически преобразует результат в нужный тип данных (Число)
Сообщить("Результат выполнения внешней процедуры: " + Строка(Результат));

Этот способ является наиболее безопасным, кроссплатформенным и простым в последующей поддержке.

Рекомендации по оптимизации работы с внешними СУБД

Для того чтобы интеграция работала максимально стабильно, рекомендуем придерживаться следующих правил:

← На главную