从行值执行表值函数

时间:2016-01-04 16:07:02

标签: sql sql-server sql-server-2012

给出如下表,其中fn包含现有表值的函数的名称,param包含要传递给函数的param

fn       | param
----------------
'fn_one' | 1001
'fn_two' | 1001
'fn_one' | 1002
'fn_two' | 1002

有没有办法通过使用基于集合的操作来获得这样的结果表? 结果表将包含第一个表中每一行的0- *行。

param | resultval
---------------------------
1001  | 'fn_one_result_a'
1001  | 'fn_one_result_b'
1001  | 'fn_two_result_one'
1002  | 'fn_two_result_one'

我以为我可以做一些像(伪)

的事情
select t1.param, t2.resultval
from table1 t1
cross join exec sp_executesql('select * from '+t1.fn+'('+t1.param+')') t2

但是在exec sp_executesql中出现语法错误。

目前我们正在使用游标循环遍历第一个表并使用exec sp_executesql插入第二个表。虽然这可以正确地完成工作,但它也是经常使用的存储过程中最重的部分,我试图对其进行优化。对数据模型的更改可能意味着对应用程序的大部分核心进行了更改,而且只需在sql server上抛出硬件就会花费更多。

2 个答案:

答案 0 :(得分:2)

您无法通过在SQL语句中引用其名称来访问对象。一种方法是使用case语句:

select t1.*,
       (case when fn = 'fn_one' then dbo.fn_one(t1.param)
             when fn = 'fn_two' then dbo.fn_two(t1.param)
        end) as resultval
from table1 t1 ;

有趣的是,您可以将case封装为另一个函数,然后执行:

select t1.*, dbo.fn_generic(t1.fn, t1.param) as resultval
from table1 t1 ;

但是,在SQL Server中,您不能在用户定义的函数(在T-SQL中定义)中使用动态SQL,因此您仍然需要使用case或类似的逻辑。

这些方法中的任何一种都可能比游标快得多,因为它们不需要发出多个查询。

答案 1 :(得分:2)

我相信这应该做你需要的,使用动态SQL生成一个语句,它可以为你提供结果,然后使用EXEC将它们放入你的表中。 FOR XML技巧是将VARCHAR值从多行连接在一起的常用技巧。它必须与AS [text()]一起编写才能生效。

--=========================================================
-- Set up
--=========================================================
CREATE TABLE dbo.TestTableFunctions (function_name VARCHAR(50) NOT NULL, parameter VARCHAR(20) NOT NULL)
INSERT INTO dbo.TestTableFunctions (function_name, parameter)
VALUES ('fn_one', '1001'), ('fn_two', '1001'), ('fn_one', '1002'), ('fn_two', '1002')

CREATE TABLE dbo.TestTableFunctionsResults (function_name VARCHAR(50) NOT NULL, parameter VARCHAR(20) NOT NULL, result VARCHAR(200) NOT NULL)
GO
CREATE FUNCTION dbo.fn_one
(
    @parameter VARCHAR(20)
)
RETURNS TABLE
AS
RETURN
    SELECT 'fn_one_' + @parameter AS result
GO
CREATE FUNCTION dbo.fn_two
(
    @parameter VARCHAR(20)
)
RETURNS TABLE
AS
RETURN
    SELECT 'fn_two_' + @parameter AS result
GO

--=========================================================
-- The important stuff
--=========================================================

DECLARE @sql VARCHAR(MAX)

SELECT @sql = 
(
    SELECT 'SELECT ''' + T1.function_name + ''', ''' + T1.parameter + ''', F.result FROM ' + T1.function_name + '(' + T1.parameter + ') F UNION ALL ' AS [text()]
    FROM
        TestTableFunctions T1
    FOR XML PATH ('')
)

SELECT @sql = SUBSTRING(@sql, 1, LEN(@sql) - 10)

INSERT INTO dbo.TestTableFunctionsResults
EXEC(@sql)

SELECT * FROM dbo.TestTableFunctionsResults

--=========================================================
-- Clean up
--=========================================================
DROP TABLE dbo.TestTableFunctions
DROP TABLE dbo.TestTableFunctionsResults
DROP FUNCTION dbo.fn_one
DROP FUNCTION dbo.fn_two
GO

第一个SELECT语句(忽略设置)构建一个字符串,该字符串具有运行表中所有函数的语法,将结果全部UNION一起返回。这样就可以使用EXEC运行字符串,这意味着您可以INSERT将这些结果放入您的表格中。

虽然有几个快速说明......首先,函数必须返回相同的结果集结构 - 具有相同数据类型的相同列数(从技术上讲,如果SQL Server可以,它们可能是不同的数据类型总是对它们进行隐式转换,但它确实不值得冒险)。其次,如果有人能够更新你的函数表,他们可以使用SQL注入来破坏你的系统。你需要严格控制它,我不会让用户只输入功能名称等。