在函数中执行存储过程或动态 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 语句,例如:

由于你只需要执行一些动态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()、不能更改数据库的状态、不能'不要使用 RAISERRORSET 语句等

下面的代码是引用上面显示的 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];

例子

  1. 下面两个例子都是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
    
  2. 以下示例表明您可以 select 几乎任何数据类型,并且不需要将其转换为 NVARCHAR 以使其工作。

    SELECT dbo.SimpleSelect('SELECT GETDATE();');
    -- 10/12/2015 10:25:33 AM
    
  3. 下面是传入多语句查询:

    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
    
  4. 下面的例子说明我们可以通过这个函数执行一个简单的只读存储过程。这不是可以在 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
    
  5. 对于最后一个示例,我们再次看到我们受到 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.