Разберем нестандартную, но иногда крайне необходимую задачу: динамическое создание, выполнение и последующее удаление хранимых процедур на уровне SQL Server непосредственно из кода 1С. Хотя такой подход и расходится с рекомендованной политикой взаимодействия 1С с СУБД, существуют ситуации, когда прямое манипулирование схемой базы данных через 1С может быть единственным или наиболее эффективным решением. Мы детально рассмотрим, как реализовать этот функционал, используя объекты ADO (ActiveX Data Objects).
Прежде всего, давайте выясним, что такое ADO-объекты и почему именно они позволяют нам решать эту задачу. ADO (ActiveX Data Objects) — это набор COM-объектов, предоставляющих универсальный и высокопроизводительный доступ к данным из различных источников. В контексте 1С, работа с COM-объектами, такими как ADODB.Connection и ADODB.Command, является стандартным механизмом и не требует использования внешних компонент (ВК) в традиционном понимании 1С. Эти объекты доступны в системе при наличии установленных компонентов доступа к данным (например, MDAC или SQL Server Native Client).
Использование ADO позволяет нам выполнять произвольные SQL-запросы, включая DDL-операции (Data Definition Language), такие как создание, изменение и удаление объектов базы данных (таблиц, индексов, хранимых процедур, представлений), а также DML-операции (Data Manipulation Language) для выборки, вставки, обновления и удаления данных.
Первый шаг в нашей задаче — это установка стабильного соединения с базой данных SQL Server. Мы будем использовать объект ADODB.Connection для этой цели. Он является центральным элементом для любого взаимодействия с базой данных через ADO.
Давайте посмотрим на основные шаги и пример кода:
ADODB.Connection.Open().Рассмотрим пример кода 1С для установки соединения:
// Объявляем переменные для работы с ADO
Перем ADOСоединение;
Перем ADOКоманда;
Попытка
// 1. Создание объекта соединения
ADOСоединение = Новый COMОбъект("ADODB.Connection");
// 2. Формирование строки подключения
// Используем SQL Server Native Client для лучшей производительности и функциональности.
// Замените данные в строке подключения на свои!
СтрокаПодключения = "Provider=SQLNCLI11;" + // Или другой подходящий провайдер, например SQLOLEDB
"Server=ВашSQLСервер;" + // Имя или IP-адрес SQL Server
"Database=ИмяВашейБазыДанных;" + // Имя базы данных, к которой подключаемся
"UID=ВашSQLПользователь;" + // Логин SQL-пользователя
"PWD=ВашПарольSQLПользователя;"; // Пароль SQL-пользователя
// Важно: Убедитесь, что у указанного SQL-пользователя есть необходимые права!
// 3. Открытие соединения
ADOСоединение.Open(СтрокаПодключения);
Сообщить("Соединение с SQL Server успешно установлено.");
Исключение
Сообщить("Ошибка при установке соединения с SQL Server: " + ОписаниеОшибки());
Если ADOСоединение <> Неопределено Тогда
Попытка
ADOСоединение.Close();
Исключение
КонецПопытки;
КонецЕсли;
Возврат; // Выходим из процедуры в случае ошибки
КонецПопытка;
Обратите внимание на строку подключения. Она является критически важной. Вы можете использовать различные провайдеры, такие как SQLOLEDB (стандартный OLE DB Provider for SQL Server) или SQLNCLI11 (SQL Server Native Client 11.0, который часто предпочтительнее для более новых версий SQL Server из-за расширенной функциональности и лучшей производительности). Обязательно замените значения ВашSQLСервер, ИмяВашейБазыДанных, ВашSQLПользователь и ВашПарольSQLПользователя на актуальные данные вашей среды.
После того как соединение установлено, мы можем перейти к созданию хранимой процедуры. Для этого нам понадобится объект ADODB.Command. Этот объект позволяет нам определять SQL-команды и выполнять их через открытое ADO-соединение.
ADODB.Command.ActiveConnection.CommandText. В данном случае это будет оператор CREATE PROCEDURE.Execute().Давайте рассмотрим пример SQL-кода для создания простой хранимой процедуры, которая может принимать входные параметры и возвращать выходные:
// Пример SQL-кода для создания хранимой процедуры
// Предположим, процедура будет выполнять простую операцию, например, возвращать текстовое сообщение
// и устанавливать значение выходного параметра.
ТекстSQLЗапроса = "
IF OBJECT_ID('dbo.ТестоваяХранимаяПроцедура', 'P') IS NOT NULL
DROP PROCEDURE dbo.ТестоваяХранимаяПроцедура;
GO
CREATE PROCEDURE dbo.ТестоваяХранимаяПроцедура
@ВходнойПараметр NVARCHAR(255),
@ВыходнойПараметр INT OUTPUT
AS
BEGIN
SET NOCOUNT ON; -- Подавляет вывод количества затронутых строк
-- Выводим сообщение, используя входной параметр
SELECT 'Привет из хранимой процедуры! Получено: ' + @ВходнойПараметр AS Сообщение;
-- Устанавливаем значение выходного параметра
SET @ВыходнойПараметр = LEN(@ВходнойПараметр); -- Например, длина входной строки
END;
GO
";
В приведенном выше SQL-коде мы сначала проверяем существование процедуры с именем ТестоваяХранимаяПроцедура и удаляем ее, если она существует. Это обеспечивает идемпотентность нашего скрипта, позволяя повторно создавать процедуру без ошибок. Затем мы определяем саму процедуру с одним входным параметром @ВходнойПараметр и одним выходным параметром @ВыходнойПараметр.
Теперь интегрируем это в код 1С:
Попытка
// 1. Создание объекта команды
ADOКоманда = Новый COMОбъект("ADODB.Command");
// 2. Привязка команды к соединению
ADOКоманда.ActiveConnection = ADOСоединение;
// 3. Определение текста SQL-команды
// Важно: для выполнения DDL-операций, таких как CREATE PROCEDURE,
// ADO может потребовать разделения команд 'GO' на отдельные вызовы.
// Проще всего выполнить скрипт целиком, если провайдер поддерживает MultipleActiveResultSets=True
// или разбить его на части по 'GO'. Для простых CREATE/DROP обычно хватает одного Execute.
ТекстSQLЗапросаСозданияПроцедуры = "
IF OBJECT_ID('dbo.ТестоваяХранимаяПроцедура', 'P') IS NOT NULL
DROP PROCEDURE dbo.ТестоваяХранимаяПроцедура;
CREATE PROCEDURE dbo.ТестоваяХранимаяПроцедура
@ВходнойПараметр NVARCHAR(255),
@ВыходнойПараметр INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SELECT 'Привет из хранимой процедуры! Получено: ' + @ВходнойПараметр AS Сообщение;
SET @ВыходнойПараметр = LEN(@ВходнойПараметр);
END;
";
ADOКоманда.CommandText = ТекстSQLЗапросаСозданияПроцедуры;
// 4. Выполнение команды
// Для команд, не возвращающих результирующий набор, можно использовать adExecuteNoRecords
// для оптимизации, хотя в 1С это не всегда явно поддерживается через COM-объект напрямую.
// Простого вызова Execute() достаточно.
ADOКоманда.Execute();
Сообщить("Хранимая процедура 'ТестоваяХранимаяПроцедура' успешно создана.");
Исключение
Сообщить("Ошибка при создании хранимой процедуры: " + ОписаниеОшибки());
// Закрываем соединение в случае ошибки
Если ADOСоединение <> Неопределено Тогда
Попытка
ADOСоединение.Close();
Исключение
КонецПопытки;
КонецЕсли;
Возврат;
КонецПопытка;
Важный момент: Директива GO в T-SQL используется для разделения пакетов команд. Некоторые ADO-провайдеры могут некорректно обрабатывать скрипты, содержащие GO, как единый блок. В таком случае, вам может потребоваться разбить ваш SQL-скрипт на части по GO и выполнять каждую часть отдельным вызовом ADOКоманда.Execute(). Однако для простых CREATE PROCEDURE без GO внутри обычно достаточно одного вызова.
После успешного создания процедуры нам, конечно же, нужно ее выполнить. Выполнение хранимой процедуры через ADO может быть реализовано несколькими способами, в зависимости от того, возвращает ли процедура результирующие наборы данных и использует ли параметры. Мы рассмотрим вариант с использованием объекта ADODB.Command и параметрами.
ADODB.Command, но теперь настраиваем его для вызова хранимой процедуры.Parameters объекта ADOКоманда. Это включает в себя указание имени, типа, направления (входной, выходной, возвращаемое значение) и значения для входных параметров.Execute() вернет объект ADODB.Recordset.Рассмотрим пример кода 1С для выполнения созданной ранее процедуры:
Перем ADOЗапись; // Для получения результирующего набора
Попытка
// Снова используем наш объект команды
// Указываем, что тип команды - это вызов хранимой процедуры
ADOКоманда.CommandType = 4; // adCmdStoredProc = 4 (поиск по имени процедуры)
// adCmdText = 1 (для обычного SQL текста)
ADOКоманда.CommandText = "dbo.ТестоваяХранимаяПроцедура";
// Очищаем коллекцию параметров от предыдущих вызовов (если были)
ADOКоманда.Parameters.Clear();
// Добавляем входной параметр
// ADO Type constants: adVarWChar = 202 (для NVARCHAR), adInteger = 3 (для INT)
// ADO Direction constants: adParamInput = 1, adParamOutput = 2
ADOКоманда.Parameters.Append(ADOКоманда.CreateParameter("ВходнойПараметр", 202, 1, 255, "Пример строки для процедуры"));
// Добавляем выходной параметр
// Значение для выходного параметра устанавливать не нужно, оно будет заполнено процедурой.
ADOКоманда.Parameters.Append(ADOКоманда.CreateParameter("ВыходнойПараметр", 3, 2));
// Выполняем процедуру
ADOЗапись = ADOКоманда.Execute();
Сообщить("Хранимая процедура 'ТестоваяХранимаяПроцедура' успешно выполнена.");
// Обработка результирующего набора, если он есть
Если ADOЗапись.EOF = Ложь Тогда // Проверяем, есть ли записи
Пока ADOЗапись.EOF = Ложь Цикл
// Получаем значение поля "Сообщение"
Сообщить("Результат из процедуры: " + ADOЗапись.Fields("Сообщение").Value);
ADOЗапись.MoveNext(); // Переходим к следующей записи
КонецЦикла;
КонецЕсли;
// Получаем значение выходного параметра
Сообщить("Значение выходного параметра: " + ADOКоманда.Parameters("ВыходнойПараметр").Value);
Исключение
Сообщить("Ошибка при выполнении хранимой процедуры: " + ОписаниеОшибки());
Если ADOЗапись <> Неопределено Тогда
Попытка
ADOЗапись.Close();
Исключение
КонецПопытки;
КонецЕсли;
// Закрываем соединение в случае ошибки
Если ADOСоединение <> Неопределено Тогда
Попытка
ADOСоединение.Close();
Исключение
КонецПопытки;
КонецЕсли;
Возврат;
КонецПопытки;
В этом блоке кода мы сначала устанавливаем CommandType в значение 4 (adCmdStoredProc), чтобы указать ADO, что мы хотим выполнить хранимую процедуру по ее имени. Затем мы очищаем коллекцию параметров и добавляем каждый параметр по порядку, указывая его имя, ADO-тип данных, направление (входной adParamInput или выходной adParamOutput) и размер. После выполнения процедуры, если она возвращает результирующий набор, мы можем перебрать его с помощью объекта ADODB.Recordset. И, конечно, мы считываем значение выходного параметра.
В соответствии с нашей задачей, после использования хранимой процедуры нам необходимо ее уничтожить. Этот шаг также выполняется с помощью объекта ADODB.Command, но с SQL-оператором DROP PROCEDURE.
ADOКоманда.CommandText на DROP PROCEDURE.Execute().Пример кода 1С для удаления хранимой процедуры:
Попытка
// Снова настраиваем объект команды для выполнения SQL-текста
ADOКоманда.CommandType = 1; // adCmdText = 1
// Очищаем параметры, так как для DROP PROCEDURE они не нужны
ADOКоманда.Parameters.Clear();
// Текст SQL-команды для удаления процедуры
ТекстSQLЗапросаУдаленияПроцедуры = "DROP PROCEDURE dbo.ТестоваяХранимаяПроцедура;";
ADOКоманда.CommandText = ТекстSQLЗапросаУдаленияПроцедуры;
// Выполнение команды
ADOКоманда.Execute();
Сообщить("Хранимая процедура 'ТестоваяХранимаяПроцедура' успешно удалена.");
Исключение
Сообщить("Ошибка при удалении хранимой процедуры: " + ОписаниеОшибки());
КонецПопытка;
// Обязательно закрываем соединение и освобождаем объекты ADO в конце!
Если ADOЗапись <> Неопределено Тогда
Попытка
ADOЗапись.Close();
ADOЗапись = Неопределено;
Исключение
КонецПопытки;
КонецЕсли;
Если ADOСоединение <> Неопределено Тогда
Попытка
ADOСоединение.Close();
ADOСоединение = Неопределено;
Исключение
КонецПопытки;
КонецЕсли;
Если ADOКоманда <> Неопределено Тогда
ADOКоманда = Неопределено;
КонецЕсли;
Сообщить("Все ADO-объекты освобождены и соединение закрыто.");
После всех операций крайне важно закрыть соединение с базой данных методом Close() объекта ADODB.Connection и освободить все ADO-объекты (присвоить им значение Неопределено). Это гарантирует, что ресурсы базы данных будут освобождены, и не возникнет утечек памяти или блокировок.
Для успешного выполнения всех описанных операций (создания, выполнения и удаления хранимых процедур) у пользователя SQL Server, от имени которого устанавливается ADO-соединение, должны быть соответствующие права доступа. Давайте разберем этот аспект подробнее:
CREATE PROCEDURE): Для создания процедуры пользователь должен иметь разрешение CREATE PROCEDURE в базе данных, а также разрешение ALTER на схему, в которой создается процедура (например, dbo), или разрешение CONTROL на саму базу данных.EXECUTE): Для выполнения процедуры пользователь должен иметь разрешение EXECUTE на эту конкретную процедуру.DROP PROCEDURE): Для удаления процедуры пользователь должен иметь разрешение ALTER на схему, которой принадлежит процедура, или разрешение CONTROL на саму процедуру.Мы настоятельно рекомендуем предоставлять минимально необходимые права доступа (принцип наименьших привилегий). Избегайте использования ролей db_owner, sysadmin или других высокопривилегированных ролей для пользователя, через которого 1С подключается к SQL Server. Вместо этого создайте специализированного SQL-пользователя и предоставьте ему только те разрешения, которые абсолютно необходимы для выполнения ваших задач.
Хотя динамическое создание хранимых процедур из 1С через ADO технически возможно, это считается нестандартным подходом и требует тщательного анализа с точки зрения безопасности, сопровождаемости и производительности. Давайте проанализируем потенциальные преимущества и недостатки, а также сформулируем рекомендации.
В заключение, хотя мы и показали, что динамическое управление хранимыми процедурами SQL Server из 1С с помощью ADO-объектов технически возможно, мы должны подходить к этому решению с большой осторожностью, понимая все сопутствующие риски и обеспечивая должный уровень безопасности и сопровождаемости.