在函数中执行存储过程或动态 SQL
Execute Stored Procedure or Dynamic SQL in a Function
我正在使用 SQL Server 2012 并尝试将临时 table 中的数据转换为另一个临时 table,在我从一个 [=53] 复制信息时转换值=] 给另一个。这是输入 table:
的示例
#IMPORT_DATA
SSN Plan StartDate
123-45-6789 Basic Life Insurance 1/1/2015
123-45-6789 Vision 1/1/2015
123-45-6789 Dental 1/1/2015
我需要将此数据转换成如下形式:
#STAGE_DATA
SSN Plan StartDate
123456789 BLI 20150101
123456789 VIS 20150101
123456789 DTL 20150101
我有一个翻译 table,它定义了一个 SQL 脚本,该脚本将转换数据,但在从函数内执行动态 SQL 时遇到问题。
我正在使用 table 值函数加载 #STAGE_DATA
,将 #IMPORT_DATA
作为 XML 传递。 table函数是:
create function dcBPI.TranslateBenefitsStagingTable_FN
(
@XmlData xml
, @TranslationType varchar(50)
, @Company varchar(3) = null
)
returns @ReturnTable table (
[RowID] [int]
, [SSN] [varchar](9) NULL
, [Plan] [varchar](3) NULL
, [StartDate] [varchar](8) NULL
)
as
begin
insert into @ReturnTable ([RowID], [EmpSSN])
select [Table].[Column].value('ID[1]', '[int]') as 'RowID' -- no translation for RowID
, dcBPI.TranslateSourceValue_FN('Benefit', 'SSN', [Table].[Column].value('SSN[1]', '[varchar](11)'), @Company) as 'SSN'
, dcBPI.TranslateSourceValue_FN('Benefit', 'Plan', [Table].[Column].value('Plan[1]', ' [varchar](3)'), @Company) as 'Plan'
, dcBPI.TranslateSourceValue_FN('Benefit', 'StartDate', [Table].[Column].value('StartDate[1]', ' [varchar](10)'), @Company) as 'StartDate'
from @XmlData.nodes('//row') as [Table]([Column])
return
end
go
翻译函数为:
create function dcBPI.TranslateSourceValue_FN
(
@TranslationType varchar(50)
, @DestinationHeader varchar(255)
, @SourceValue varchar(255)
, @Company varchar(3) = null
)
returns varchar(255)
as
begin
declare @DestinationValue varchar(255) = 'Missing'
, @sql nvarchar(max) = ''
if len(rtrim(@Company)) = 0 set @Company = null
select @sql =
select
distinct td.DestinationValue
from dcBPI.TranslationData td
where (
td.Company = @Company
or td.Company = 'All'
)
and td.TranslationType = @TranslationType
and td.DestinationHeader = @DestinationHeader
and td.SourceValue = @SourceValue
)
exec sp_executesql @sql, N'@OutputValue nvarchar(max) OUTPUT', @OutputValue = @DestinationValue OUTPUT
return @DestinationValue
end
go
当我执行 table 值函数(它又执行 TranslateSourceValue_FN
)时,我收到一条错误消息:
Only functions and some extended stored procedures can be executed
from within a function.
我从其他文章中了解到我无法从函数中调用 Dynamic SQL,那么有没有其他方法可以完成我正在尝试做的事情?
编辑(添加@sql示例)
要使用的 SQL 是简单的 select 语句,例如:
select replace('123-45-6789' '-', '')
select PlanCode from CodeTable where Plan = @Plan'
由于你只需要执行一些动态SQL,而动态SQL是简单的SELECT语句,你可以使用下面的SQLCLR代码来替换您拨打 sp_excutesql
的电话仅:
SET @DestinationValue = dbo.SimpleSelect(@sql);
这个简单函数的 C# 代码是:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public class TheFunction
{
[return: SqlFacet(MaxSize = 4000)]
[Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read,
SystemDataAccess = SystemDataAccessKind.Read)]
public static SqlString SimpleSelect(
[SqlFacet(MaxSize = 4000)] SqlString TheQuery)
{
string _Output = null;
if (TheQuery.Value.Trim() == String.Empty)
{
return new SqlString(_Output);
}
using (SqlConnection _Connection =
new SqlConnection("Context Connection = true;"))
{
using (SqlCommand _Command = _Connection.CreateCommand())
{
_Command.CommandType = CommandType.Text;
_Command.CommandText = TheQuery.Value;
_Connection.Open();
_Output = _Command.ExecuteScalar().ToString();
}
}
return new SqlString(_Output);
}
}
因为它使用进程内上下文连接,程序集可以保持标记为 SAFE
并且数据库 不需要 需要设置为 TRUSTWORTHY ON
.但这也意味着您受到 T-SQL 函数所具有的所有其他限制的约束,例如:不能使用 NEWID()
、不能更改数据库的状态、不能'不要使用 RAISERROR
或 SET
语句等
下面的代码是引用上面显示的 C# 方法的 T-SQL 包装器对象。请注意第 2 行末尾的 RETURNS NULL ON NULL INPUT
。这 不是 Visual Studio / SSDT 允许您设置的东西,因此必须通过 运行 宁下面的代码手动完成。
CREATE FUNCTION [dbo].[SimpleSelect](@TheQuery [nvarchar](4000))
RETURNS [nvarchar](4000) WITH EXECUTE AS CALLER, RETURNS NULL ON NULL INPUT
AS EXTERNAL NAME [DynamicSqlForFunctions].[TheFunction].[SimpleSelect];
例子
下面两个例子都是returnNULL
。请注意,显示的第一个示例传入 NULL
,C# 方法中没有对 TheQuery.IsNull
的处理,但也没有 "Object not set to a reference..." 错误。这要归功于 CREATE FUNCTION
.
中指定的 RETURNS NULL ON NULL INPUT
选项的魔力
SELECT dbo.SimpleSelect(NULL);
-- NULL
SELECT dbo.SimpleSelect('');
-- NULL
以下示例表明您可以 select 几乎任何数据类型,并且不需要将其转换为 NVARCHAR
以使其工作。
SELECT dbo.SimpleSelect('SELECT GETDATE();');
-- 10/12/2015 10:25:33 AM
下面是传入多语句查询:
DECLARE @SQL NVARCHAR(4000);
SET @SQL = N'
DECLARE @TempSum INT;
SET @TempSum = 0;
SELECT TOP(80) @TempSum = so.[object_id]
FROM [master].[sys].[objects] so
SELECT @TempSum;
';
SELECT dbo.SimpleSelect(@SQL);
-- 133575514
下面的例子说明我们可以通过这个函数执行一个简单的只读存储过程。这不是可以在 T-SQL 函数中完成的事情。但是,像 T-SQL 函数一样,它不能 运行 副作用语句,这就是存储过程中没有 SET NOCOUNT ON;
的原因。
所以首先 运行 这个:
CREATE PROCEDURE #SimpleProcTest
AS
SELECT TOP(5) so.[name], so.[type_desc]
FROM [master].[sys].[objects] so
ORDER BY so.[name] ASC;
GO
然后,如果你想测试它,运行这个:
EXEC #SimpleProcTest;
最后,我们在这里进行完整测试,包括创建一个 table 变量,将存储过程的结果转储到该 table 变量中。
DECLARE @TestSQL NVARCHAR(4000) = N'
DECLARE @Bob TABLE (
[name] sysname,
[type_desc] NVARCHAR(60)
);
INSERT INTO @Bob
EXEC #SimpleProcTest;
SELECT COUNT(*)
FROM @Bob
WHERE PATINDEX(N''%[0-9]%'', [name]) > 0;
';
SELECT dbo.SimpleSelect(@TestSQL);
-- 2
对于最后一个示例,我们再次看到我们受到 T-SQL 函数对它们施加的大多数相同限制的约束。
SELECT dbo.SimpleSelect('SELECT NEWID();');
-- Msg 6522, Level 16, State 1, Line 1
-- Invalid use of a side-effecting operator 'SELECT WITHOUT QUERY' within a function.
我正在使用 SQL Server 2012 并尝试将临时 table 中的数据转换为另一个临时 table,在我从一个 [=53] 复制信息时转换值=] 给另一个。这是输入 table:
的示例#IMPORT_DATA
SSN Plan StartDate
123-45-6789 Basic Life Insurance 1/1/2015
123-45-6789 Vision 1/1/2015
123-45-6789 Dental 1/1/2015
我需要将此数据转换成如下形式:
#STAGE_DATA
SSN Plan StartDate
123456789 BLI 20150101
123456789 VIS 20150101
123456789 DTL 20150101
我有一个翻译 table,它定义了一个 SQL 脚本,该脚本将转换数据,但在从函数内执行动态 SQL 时遇到问题。
我正在使用 table 值函数加载 #STAGE_DATA
,将 #IMPORT_DATA
作为 XML 传递。 table函数是:
create function dcBPI.TranslateBenefitsStagingTable_FN
(
@XmlData xml
, @TranslationType varchar(50)
, @Company varchar(3) = null
)
returns @ReturnTable table (
[RowID] [int]
, [SSN] [varchar](9) NULL
, [Plan] [varchar](3) NULL
, [StartDate] [varchar](8) NULL
)
as
begin
insert into @ReturnTable ([RowID], [EmpSSN])
select [Table].[Column].value('ID[1]', '[int]') as 'RowID' -- no translation for RowID
, dcBPI.TranslateSourceValue_FN('Benefit', 'SSN', [Table].[Column].value('SSN[1]', '[varchar](11)'), @Company) as 'SSN'
, dcBPI.TranslateSourceValue_FN('Benefit', 'Plan', [Table].[Column].value('Plan[1]', ' [varchar](3)'), @Company) as 'Plan'
, dcBPI.TranslateSourceValue_FN('Benefit', 'StartDate', [Table].[Column].value('StartDate[1]', ' [varchar](10)'), @Company) as 'StartDate'
from @XmlData.nodes('//row') as [Table]([Column])
return
end
go
翻译函数为:
create function dcBPI.TranslateSourceValue_FN
(
@TranslationType varchar(50)
, @DestinationHeader varchar(255)
, @SourceValue varchar(255)
, @Company varchar(3) = null
)
returns varchar(255)
as
begin
declare @DestinationValue varchar(255) = 'Missing'
, @sql nvarchar(max) = ''
if len(rtrim(@Company)) = 0 set @Company = null
select @sql =
select
distinct td.DestinationValue
from dcBPI.TranslationData td
where (
td.Company = @Company
or td.Company = 'All'
)
and td.TranslationType = @TranslationType
and td.DestinationHeader = @DestinationHeader
and td.SourceValue = @SourceValue
)
exec sp_executesql @sql, N'@OutputValue nvarchar(max) OUTPUT', @OutputValue = @DestinationValue OUTPUT
return @DestinationValue
end
go
当我执行 table 值函数(它又执行 TranslateSourceValue_FN
)时,我收到一条错误消息:
Only functions and some extended stored procedures can be executed from within a function.
我从其他文章中了解到我无法从函数中调用 Dynamic SQL,那么有没有其他方法可以完成我正在尝试做的事情?
编辑(添加@sql示例)
要使用的 SQL 是简单的 select 语句,例如:
select replace('123-45-6789' '-', '')
select PlanCode from CodeTable where Plan = @Plan'
由于你只需要执行一些动态SQL,而动态SQL是简单的SELECT语句,你可以使用下面的SQLCLR代码来替换您拨打 sp_excutesql
的电话仅:
SET @DestinationValue = dbo.SimpleSelect(@sql);
这个简单函数的 C# 代码是:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public class TheFunction
{
[return: SqlFacet(MaxSize = 4000)]
[Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read,
SystemDataAccess = SystemDataAccessKind.Read)]
public static SqlString SimpleSelect(
[SqlFacet(MaxSize = 4000)] SqlString TheQuery)
{
string _Output = null;
if (TheQuery.Value.Trim() == String.Empty)
{
return new SqlString(_Output);
}
using (SqlConnection _Connection =
new SqlConnection("Context Connection = true;"))
{
using (SqlCommand _Command = _Connection.CreateCommand())
{
_Command.CommandType = CommandType.Text;
_Command.CommandText = TheQuery.Value;
_Connection.Open();
_Output = _Command.ExecuteScalar().ToString();
}
}
return new SqlString(_Output);
}
}
因为它使用进程内上下文连接,程序集可以保持标记为 SAFE
并且数据库 不需要 需要设置为 TRUSTWORTHY ON
.但这也意味着您受到 T-SQL 函数所具有的所有其他限制的约束,例如:不能使用 NEWID()
、不能更改数据库的状态、不能'不要使用 RAISERROR
或 SET
语句等
下面的代码是引用上面显示的 C# 方法的 T-SQL 包装器对象。请注意第 2 行末尾的 RETURNS NULL ON NULL INPUT
。这 不是 Visual Studio / SSDT 允许您设置的东西,因此必须通过 运行 宁下面的代码手动完成。
CREATE FUNCTION [dbo].[SimpleSelect](@TheQuery [nvarchar](4000))
RETURNS [nvarchar](4000) WITH EXECUTE AS CALLER, RETURNS NULL ON NULL INPUT
AS EXTERNAL NAME [DynamicSqlForFunctions].[TheFunction].[SimpleSelect];
例子
下面两个例子都是return
中指定的NULL
。请注意,显示的第一个示例传入NULL
,C# 方法中没有对TheQuery.IsNull
的处理,但也没有 "Object not set to a reference..." 错误。这要归功于CREATE FUNCTION
.RETURNS NULL ON NULL INPUT
选项的魔力SELECT dbo.SimpleSelect(NULL); -- NULL SELECT dbo.SimpleSelect(''); -- NULL
以下示例表明您可以 select 几乎任何数据类型,并且不需要将其转换为
NVARCHAR
以使其工作。SELECT dbo.SimpleSelect('SELECT GETDATE();'); -- 10/12/2015 10:25:33 AM
下面是传入多语句查询:
DECLARE @SQL NVARCHAR(4000); SET @SQL = N' DECLARE @TempSum INT; SET @TempSum = 0; SELECT TOP(80) @TempSum = so.[object_id] FROM [master].[sys].[objects] so SELECT @TempSum; '; SELECT dbo.SimpleSelect(@SQL); -- 133575514
下面的例子说明我们可以通过这个函数执行一个简单的只读存储过程。这不是可以在 T-SQL 函数中完成的事情。但是,像 T-SQL 函数一样,它不能 运行 副作用语句,这就是存储过程中没有
SET NOCOUNT ON;
的原因。所以首先 运行 这个:
CREATE PROCEDURE #SimpleProcTest AS SELECT TOP(5) so.[name], so.[type_desc] FROM [master].[sys].[objects] so ORDER BY so.[name] ASC; GO
然后,如果你想测试它,运行这个:
EXEC #SimpleProcTest;
最后,我们在这里进行完整测试,包括创建一个 table 变量,将存储过程的结果转储到该 table 变量中。
DECLARE @TestSQL NVARCHAR(4000) = N' DECLARE @Bob TABLE ( [name] sysname, [type_desc] NVARCHAR(60) ); INSERT INTO @Bob EXEC #SimpleProcTest; SELECT COUNT(*) FROM @Bob WHERE PATINDEX(N''%[0-9]%'', [name]) > 0; '; SELECT dbo.SimpleSelect(@TestSQL); -- 2
对于最后一个示例,我们再次看到我们受到 T-SQL 函数对它们施加的大多数相同限制的约束。
SELECT dbo.SimpleSelect('SELECT NEWID();'); -- Msg 6522, Level 16, State 1, Line 1 -- Invalid use of a side-effecting operator 'SELECT WITHOUT QUERY' within a function.