2014-01-16 39 views
4

我有以下用户定义的函数:传递一个表表达式为表参数化函数

CREATE FUNCTION dbo.GetConcatenatedWithKeyAsInt32(@Values dbo.IndexValue READONLY) 
RETURNS TABLE 
WITH SCHEMABINDING 
AS 
RETURN (
    SELECT 
    [Id], 
    -- The definition of [Names] is not really important to this question! 
    [Names] = stuff((
    select ', ' + Value 
    from @Values AS xt 
    where xt.Id = t.Id 
    for xml path(''), TYPE 
).value('.[1]','varchar(max)'), 1, 2, '') 
    FROM @Values AS t GROUP BY t.Id); 

的参数是一个用户定义的表型的:

CREATE TYPE IndexValue AS TABLE (Id int, Value VARCHAR(max)); 

我挣扎调用这个函数。

我发现人们在实际的物理表(或视图)上调用这样一个函数的例子,但肯定可以直接在select表达式中使用它,不是吗?

我想:

SELECT * 
FROM dbo.GetConcatenatedWithKeyAsInt32(
    SELECT c.Id AS Id, a.City AS value 
    FROM Customers c 
    JOIN Addresses a ON c.Id = a.CustomerId 
); 

SQL Server不喜欢这样的:

Incorrect syntax near the keyword 'SELECT'. 
Incorrect syntax near ')' 

这可能吗?如果是这样,那么正确的语法是什么?

还是我真的需要创建

SELECT c.Id AS Id, a.City AS value 
FROM Customers c 
JOIN Addresses a ON c.Id = a.CustomerId 

第一个临时表或视图?

+1

我希望[这些搜索结果]有一些(https://www.google.com/search?q=sql+call+user+defined+function&oq=sql+call+user+de&aqs=chrome.1.69i57j0l5。 2627j0j8&sourceid = chrome&espv = 210&es_sm = 93&ie = UTF-8)有帮助 –

+4

@AndersonGreen谷歌搜索显然是我做的第一件事。 – John

回答

8

在回答您的具体问题:

这可能吗?如果是这样,那么正确的语法是什么?或者我真的需要先创建一个临时表或视图?

你试图做到这一点的方式不被SQL Server支持。该方式将分配用户定义类型的变量,并SELECT你的价值观到该变量:

declare @values as dbo.IndexValue 

insert into @values 
SELECT c.Id AS Id, a.City AS value 
    FROM Customers c 
    JOIN Addresses a ON c.Id = a.CustomerId 

select * from dbo.GetConcatenatedWithKeyAsInt32(@values) 

我相信这是支持与SQL Server 2008

它可能与创建临时表类似,但我相信这是实现您要求的唯一方法。

+0

这就是我所害怕的。这有点令人惭愧(临时表,而不是语法) - 我相信在msdn的某个地方可以实现IEnumerable在CLR中实现IEnumerable函数,并且在SQL Server中托管时可以参与被延迟评估的查询计划(而不是在函数调用之前和之后创建临时表)。也许我误解了这一点,但这就是为什么我可以正确地进行这种重构。感谢您的回答,这是我认为应该的。 – John

16

不,不可能通过引用传递查询表达式而不是通过值传递(您尝试使用的语法完全不受支持)。所以,如果这是你的问题的要点,那么答案是没有

但请允许我添加一些其他建议,告诉您如何才能和/或应该完成您希望完成的任务。

首先,创建或引用对象时请always use the schema prefix

CREATE TYPE dbo.IndexValue AS TABLE (Id int, Value VARCHAR(max)); 

接下来,使用内联表值函数,而不是多语句表值函数,使用SCHEMABINDINGalways specify a length for variable-length types like nvarchar(虽然不知道为什么你在函数中使用nvarchar当因为是不可能的输入):虽然我不得不怀疑

DECLARE @x dbo.IndexValue; 
INSERT @x VALUES(1,'hoobah'),(1,'floobah'),(2,'a'),(2,'x'),(2,'y'),(3,'me'); 
SELECT Id, Names FROM dbo.GetConcatenatedWithKeyAsInt32(@x); 

疗法:

CREATE FUNCTION dbo.GetConcatenatedWithKeyAsInt32 
(
    @Values dbo.IndexValue READONLY 
) 
RETURNS TABLE 
WITH SCHEMABINDING 
AS 
    RETURN (SELECT 
    [Id], 
    [Names] = stuff((
    select ', ' + CAST(Id AS nvarchar(255)) as [text()] 
    from @Values AS xt 
    where xt.Id = t.Id 
    for xml path('') 
    ), 1, 2, '') 
    FROM @Values AS t GROUP BY t.Id); 
GO 

现在你可以使用这个功能没问题e是函数中的另一个逻辑错误,那就是您应该将XML操作应用于值列,而不是ID。这个输出是:

Id Names 
---- ------- 
1 1,1 
2 2,2,2 
3 3 

如果这是你真正的意思做,则函数应该有进一步的变化,最明显的是纠正输入来自不安全的实体处理,并保护数据XML(例如>)。

ALTER FUNCTION dbo.GetConcatenatedWithKeyAsInt32 
(
    @Values dbo.IndexValue READONLY 
) 
RETURNS TABLE 
WITH SCHEMABINDING 
AS 
    RETURN (SELECT 
    [Id], 
    [Names] = stuff((
    select ', ' + Value 
    from @Values AS xt 
    where xt.Id = t.Id 
    for xml path(''), TYPE 
    ).value('.[1]','varchar(max)'), 1, 2, '') 
    FROM @Values AS t GROUP BY t.Id); 
GO 

现在这个工程好得多:

DECLARE @x dbo.IndexValue; 
INSERT @x VALUES(1,'hoo&bah'),(1,'floo<b>ah'),(2,'a'),(2,'x'),(2,'y'),(3,'me'); 
SELECT Id, Names FROM dbo.GetConcatenatedWithKeyAsInt32(@x); 

输出:

Id Names 
---- ------------------ 
1 hoo&bah, floo<b>ah 
2 a, x, y 
3 me 

至于错误信息,@Lamak是正确的,你不能传递一个查询到的参数函数,你需要在查询中包含函数调用,例如使用CROSS APPLY或OUTER APPLY。

至于更新后的代码示例,您正试图开始工作,我不知道为什么要使用此功能。为什么不干脆:

SELECT DISTINCT c.Id, Names = STUFF((SELECT ', ' + a.City 
    FROM dbo.Addresses AS a 
    WHERE a.CustomerId = c.Id GROUP BY a.City 
    FOR XML PATH(''), TYPE 
).value('.[1]','varchar(max)'), 1, 2, '') 
FROM dbo.Customers AS c; 

如果你想使通用的,那么我想你可以恢复到你的低效多语句TVF。下面是语法来做到这一点:

CREATE FUNCTION dbo.GetConcatenatedWithKeyAsInt32_b 
(
    @Values dbo.IndexValue READONLY 
) 
RETURNS @ret TABLE 
(
    Id int PRIMARY KEY NOT NULL, 
    value nvarchar(max) NOT NULL 
) 
WITH SCHEMABINDING 
AS 
BEGIN 
    INSERT @ret SELECT 
    Id, Names = STUFF((SELECT ', ' + Value 
    FROM @Values AS xt 
    WHERE xt.Id = t.Id GROUP BY Value 
    FOR XML PATH(''), TYPE 
    ).value('.[1]','varchar(max)'), 1, 2, '') 
    FROM @Values AS t GROUP BY t.id; 

    RETURN; 
END 
GO 

然后,你可以把它 - 再次,既然你想要的通用表类型作为输入 - 馅的加入到申报表变量后的结果。有没有办法只是将您的SQL查询传递给函数,对不起。你需要传递一个表格,而不是查询。

DECLARE @x dbo.IndexValue; 

INSERT @x(Id, Value) 
    SELECT c.Id, a.City 
    FROM dbo.Addresses AS a 
    INNER JOIN dbo.Customers AS c 
    ON a.CustomerId = c.Id; 

SELECT * INTO #x FROM dbo.GetConcatenatedWithKeyAsInt32_b(@x); 

除了混淆掉XML的逻辑,这并不能真正获得你什么,可能极大地降低了该查询。你似乎经历了很多额外的层,没有明显的原因(将连接的结果插入到表类型中,然后使用表类型来调用函数,然后将这些行插入函数内的声明表中,然后对该表执行XML操作)。

不要看执行计划,看看哪一个“更快” - 它分配各种近似成本,不知道如何正确评估和计算某些XML操作的成本。相反,只需要时间!

SET NOCOUNT ON; 
SELECT GETUTCDATE(); 
GO 

    SELECT c.Id, Names = STUFF((SELECT ', ' + a.City 
     FROM dbo.Addresses AS a 
     WHERE a.CustomerId = c.Id GROUP BY a.City 
     FOR XML PATH(''), TYPE 
    ).value('.[1]','varchar(max)'), 1, 2, '') 
    INTO #x 
    FROM dbo.Customers AS c; 

    DROP TABLE #x; 

GO 5000 
SELECT GETUTCDATE(); 
GO 
    DECLARE @x dbo.IndexValue; 

    INSERT @x(Id, Value) 
     SELECT c.Id, a.City 
     FROM dbo.Addresses AS a 
     INNER JOIN dbo.Customers AS c 
     ON a.CustomerId = c.Id; 

    SELECT * INTO #x FROM dbo.GetConcatenatedWithKeyAsInt32_b(@x); 
    DROP TABLE #x; 
GO 5000 
SELECT GETUTCDATE(); 
GO 

结果:

XML:   16,780 ms 
Your approach: 32,230 ms 

即使我改:

INSERT @x(Id, Value) 
     SELECT c.Id, a.City 

要:

INSERT @x(Id, Value) 
     SELECT DISTINCT c.Id, a.City 

(这样函数具有较少的数据处理,如果有重复的),我只刮了一点时间FF(你仍然需要GROUP BY在功能方面,恕我直言,以保护自己免受将喂它查询):

XML:   16,824 ms 
Your approach: 29,576 ms 

我没有做的东西,当我告诉你,DRY并不总是有益的SQL服务器。函数不仅仅是指针,它们还有大量的开销。

相关问题