我正在使用 SQL Server 2012 并尝试将一个临时表中的数据转换为另一个临时表,在我将信息从一个表复制到另一个表时转换值。这是输入表的示例:
#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
我有一个转换表,它定义了一个将转换数据的 SQL 脚本,但在从函数内执行动态 SQL 时遇到了问题。
我正在使用表值函数加载 #STAGE_DATA
,将 #IMPORT_DATA
作为 XML 传递。表函数为:
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
当我执行表值函数(它依次执行 TranslateSourceValue_FN
)时,我收到一条错误消息:
Only functions and some extended stored procedures can be executed from within a function.
我从其他文章中了解到我无法从函数调用动态 SQL,那么是否有其他方法可以完成我正在尝试做的事情?
编辑(添加@sql示例)
要使用的 SQL 是简单的选择语句,例如:
选择替换('123-45-6789' '-', '')
从代码表中选择 PlanCode,其中 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 包装器对象。请注意第二行末尾的 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];
示例
下面两个例子都返回
NULL
。请注意,显示的第一个示例传入了一个NULL
,并且在 C# 方法中没有对TheQuery.IsNull
的处理,但也没有“Object not set to a引用...”错误。这要归功于CREATE FUNCTION
中指定的RETURNS NULL ON NULL INPUT
选项的魔力。SELECT dbo.SimpleSelect(NULL); -- NULL SELECT dbo.SimpleSelect(''); -- NULL
以下示例表明您几乎可以选择任何数据类型,并且无需将其转换为
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;
最后,我们在这里进行完整的测试,包括创建一个表变量,将存储过程的结果转储到该表变量中。
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 - 在函数中执行存储过程或动态 SQL,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33041179/