2015-10-09 41 views
1

我正在使用SQL Server 2012,并试图将临时表中的数据转换为另一个临时表,转换值时将信息从一个表复制到另一个表。这里是输入表的例子:在函数中执行存储过程或动态SQL

#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),我得到一个错误信息:

只有函数和一些扩展存储过程可以从函数内部执行 。

我从其他文章中知道我不能从函数调用动态SQL,那么是否有另一种方法来完成我正在尝试做的事情?

编辑(加@sql例子)
的SQL是使用简单的SELECT语句,如:

  • select replace('123-45-6789' '-', '')
  • select PlanCode from CodeTable where Plan = @Plan'
+0

@srutzky见编辑为'@ sql'例子。 – BrianKE

回答

0

既然你只需要执行一些动态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包装器对象。请注意第二行末尾的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. 以下两个例子都返回NULL。请注意,显示的第一个示例通过了NULL,并且在C#方法中没有对TheQuery.IsNull的处理,但没有“对象未设置为引用...”错误。这都要归功于CREATE FUNCTION中指定的RETURNS NULL ON NULL INPUT选项的魔力。

    SELECT dbo.SimpleSelect(NULL); 
    -- NULL 
    
    SELECT dbo.SimpleSelect(''); 
    -- NULL 
    
  2. 下面的例子表明,你可以选择几乎任何数据类型,并且不要求它隐蔽到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; 
    

    最后,我们在这里做了全面测试,包括创建一个表变量,将存储过程的结果转储到该表变量中。

    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.