我有以下用户定义的功能:
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
第一
答案 0 :(得分:16)
不可以通过引用传递查询表达式而不是通过值传递(您尝试使用的语法完全不受支持)。所以,如果这是你问题的要点,那么答案是不。
但请允许我补充一些关于你如何能够和/或应该完成你想要完成的事情的其他建议。
首先,在创建或引用对象时请always use the schema prefix。
CREATE TYPE dbo.IndexValue AS TABLE ( Id int, Value VARCHAR(max) );
接下来,使用内联表值函数而不是多语句表值函数,使用SCHEMABINDING
和always specify a length for variable-length types like nvarchar
(虽然不确定为什么使用nvarchar
在函数中,当输入不可能时:)
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
现在你可以使用这个功能没问题:
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);
虽然我不得不怀疑你的函数中还有另一个逻辑错误,那就是你应该将XML操作应用于Value列,而不是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
(因此该函数处理的数据较少,如果有重复),我只需要休息一下(你仍需要函数中的GROUP BY
,恕我直言,以保护自己免受查询会喂它):
XML: 16,824 ms
Your approach: 29,576 ms
当我告诉你DRY在SQL Server中并不总是有益时,我并没有把事情搞砸。函数不仅仅是指针,它们还有很大的开销。
答案 1 :(得分: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起支持此功能。
它可能与创建临时表类似,但我相信这是实现您所要求的唯一方法。