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

Формирование запроса Dynamic SQL

При использовании dynamic sql запрос формируется как строка:

SET @SQLQuery='SELECT @Customer_Svc_ID='+@Table_Name+'_id FROM Customers WHERE Customer_ID='+CONVERT(nvarchar(10),@Customer_ID);

Выполнение запроса Dynamic SQL

Использование динамических запросов возможно только из хранимых процедур и невозможно из функций. При попытке выполнить динамический запрос из функции вы получите сообщение:

Only functions and some extended stored procedures can be executed from within a function.

Формирование динамического запроса, выполнение его и возврат результата включает этапы:

  1. Вызов хранимой процедуры с параметрами, один из которых (необязательно) - переменная куда будет записан результат.
  2. В процедуре формируется строка динамического запроса, включающая в себя переданные в процедуру параметры.
  3. Динамический запрос выполняется и возвращает результат в хранимую процедуру.
  4. Хранимая процедура возвращает результат в вызвавший её код (приложение или другая хранимая процедура)

Динамические запросы выполняются с помощью ключевого слова EXEC/EXECUTE или хранимой процедуры sp_executesql. Исполнение динамических запросов с помощью EXEC/EXECUTE считается довольно неудобным и очень небезопасным. Поэтому для исполнения динамических запросов рекомендуется использовать хранимую процедуру sp_executesql.
В моем случае мне нужно было выполнить пару динамических запросов для получения нужного результата:

ALTER PROCEDURE [dbo].[get_svc_quantity]
(	@Customer_ID int,
	@Svc_Name nvarchar(30),
	@Month nvarchar(30)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @SQLQuery nvarchar(1000),
		@Billing_Event_Name nvarchar(200),
		@Customer_Svc_ID nvarchar(100),
		@Table_Name nvarchar(20),
		@Quantity int

SELECT @Billing_Event_Name = Billing_Event_Name FROM HZN_Subscription_Services WHERE Name=@Svc_Name
SELECT @Table_Name = Table_Name FROM HZN_Subscription_Services WHERE Name=@Svc_Name
----------------
SET @SQLQuery='SELECT @Customer_Svc_ID='+@Table_Name+'_id FROM Customers WHERE Customer_ID='+CONVERT(nvarchar(10),@Customer_ID);
exec sp_executesql @SQLQuery, N' @Customer_Svc_ID nvarchar(100) OUTPUT', @Customer_Svc_ID OUTPUT
----------------------
SET @SQLQuery='SELECT @Quantity = Quantity FROM '+ @Table_Name + ' WHERE id=@Customer_Svc_ID AND Month=@Month AND Billing_Event_Name=@Billing_Event_Name AND id=@Customer_Svc_ID'
exec sp_executesql @SQLQuery, N'@Customer_Svc_ID nvarchar(100), @Month nvarchar(30), @Billing_Event_Name nvarchar(200), @Quantity int OUTPUT', @Customer_Svc_ID=@Customer_Svc_ID, @Month=@Month, @Billing_Event_Name=@Billing_Event_Name, @Quantity = @Quantity OUTPUT
SELECT @Quantity AS Quantity
RETURN
END

Формирование самого запроса обычно проблем не вызывает. Это обычная конкатенация строк. При использовании sp_executesql переменные в запросе не нужно как-то дополнительно обрабатывать с помощью кавычек. Просто на месте переменной пишем ее имя. Например - @Customer_Svc_ID.
При вызове sp_executesql этой процедуре нужно передать текст запроса, список переменных и их типов и значения переменных.

DECLARE @sqlCommand nvarchar(1000)
DECLARE @columnList varchar(75)
DECLARE @city varchar(75)
SET @columnList = 'CustomerID, ContactName, City'
SET @city = 'London'
SET @sqlCommand = 'SELECT ' + @columnList + ' FROM customers WHERE City = @city'
EXECUTE sp_executesql @sqlCommand, N'@city nvarchar(75)', @city = @city

Использование значений, возвращаемых динамическим запросом

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

SELECT @Variable = .....

А в списке переменных обозначить эту переменную как OUTPUT.

SET @SQLQuery='SELECT @Quantity = Quantity FROM '+ @Table_Name + ' WHERE id=@Customer_Svc_ID AND Month=@Month AND Billing_Event_Name=@Billing_Event_Name AND id=@Customer_Svc_ID'
exec sp_executesql @SQLQuery, N'@Customer_Svc_ID nvarchar(100), @Month nvarchar(30), @Billing_Event_Name nvarchar(200), @Quantity int OUTPUT', @Customer_Svc_ID=@Customer_Svc_ID, @Month=@Month, @Billing_Event_Name=@Billing_Event_Name, @Quantity = @Quantity OUTPUT

В конце хранимой процедуры, которая должна возвращать значение нужно явно выполнить SELECT AS, чтобы значение (или значения) возвращалось как таблица и к элементам можно было обращаться по имени. В моем случае:

SELECT @Quantity AS Quantity

Вызов процедуры, содержащей динамический запрос, выполняется так:

EXEC get_svc_quantity 3, 'VOIP_Ext_BB', 'April 2018'

То есть указывается ключевое слово EXEC, затем имя процедуры и список параметров через запятую.

Использование результатов, возвращаемых процедурой с динамическими запросами

В коде C# вызов процедуры и обработка результата выполняется также как и любой другой запрос:

SqlCommand get_selected_month_quantity = new SqlCommand("EXEC get_svc_quantity " + HZN_Subscriptions_Customers.SelectedValue.ToString() + ", '" +
                                                                servicename + "', '" + Month_DropDownList.SelectedValue.ToString() + " " +
                                                                Year_DropDownList.SelectedValue.ToString() + "';", conn);
//EXEC get_svc_quantity 3, 'VOIP_Ext_BB', 'April 2018'
using (SqlDataReader selected_month_quantity = get_selected_month_quantity.ExecuteReader())
{
    selected_month_quantity.Read();
    Cells[2].Text = selected_month_quantity["Quantity"].ToString();
}

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

ALTER PROCEDURE [dbo].[get_svc_quantity_]
(	@Customer_ID int,
	@Svc_Name nvarchar(30),
	@Month nvarchar(30),
	@Quantity int OUTPUT
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @SQLQuery nvarchar(1000),
		@Billing_Event_Name nvarchar(200),
		@Customer_Svc_ID nvarchar(100),
		@Table_Name nvarchar(20)

SELECT @Billing_Event_Name = Billing_Event_Name FROM HZN_Subscription_Services WHERE Name=@Svc_Name
SELECT @Table_Name = Table_Name FROM HZN_Subscription_Services WHERE Name=@Svc_Name
----------------
SET @SQLQuery='SELECT @Customer_Svc_ID='+@Table_Name+'_id FROM Customers WHERE Customer_ID='+CONVERT(nvarchar(10),@Customer_ID);
exec sp_executesql @SQLQuery, N' @Customer_Svc_ID nvarchar(100) OUTPUT', @Customer_Svc_ID OUTPUT
----------------------
SET @SQLQuery='SELECT @Quantity = Quantity FROM '+ @Table_Name + ' WHERE id=@Customer_Svc_ID AND Month=@Month AND Billing_Event_Name=@Billing_Event_Name AND id=@Customer_Svc_ID'
exec sp_executesql @SQLQuery, N'@Customer_Svc_ID nvarchar(100), @Month nvarchar(30), @Billing_Event_Name nvarchar(200), @Quantity int OUTPUT', @Customer_Svc_ID=@Customer_Svc_ID, @Month=@Month, @Billing_Event_Name=@Billing_Event_Name, @Quantity = @Quantity OUTPUT

RETURN
END

И вызов ее можно делать так:

DECLARE @q int
EXEC get_svc_quantity_ 3, 'VOIP_Ext_BB', 'April 2018', @q OUTPUT

PRINT @q

https://www.mssqltips.com/sqlservertip/1160/execute-dynamic-sql-commands-in-sql-server/
https://docs.microsoft.com/en-us/sql/relational-databases/stored-procedures/return-data-from-a-stored-procedure?view=sql-server-2017

Enter your comment. Wiki syntax is allowed:
 
  • ms_windows_ms_sql/how_to_use_dynamic_sql_querys.txt
  • Last modified: 2019/02/11 09:13
  • by 127.0.0.1