【问题标题】:Create a User defined function like SQL server 2017 STRING_AGG on earlier versions在早期版本上创建用户定义的函数,如 SQL server 2017 STRING_AGG
【发布时间】:2018-07-24 18:59:13
【问题描述】:

我尝试创建一个通用函数,可以像 this example 在 SQL Server 2017 上使用新的 string_agg 内置函数一样使用

内部实现可以像下面这样

with tbl as(
   select a.Id, c.Desc
     from TableA a
     join TableB b on b.aId = a.Id
     join TableC c on c.Code = b.bCode 
)  
select distinct ID
     , STUFF(( select ', ' + Desc from tbl t where t.ID = tbl.ID 
            for xml path(''),TYPE).value('.','VARCHAR(MAX)'),1,2,'') Desc   
from tbl

但是如何接收字段键、要连接的字段、分隔符字符和范围选择上下文? 和Inline or Multi-Statement Table-Valued Functions有关系吗?

【问题讨论】:

  • 可以通过 DLL 导入聚合函数。您必须用 C# 或其他 .NET 语言编写类,然后将其导入 SSMS。但是你不需要这样的功能,有一个很好的例子,如何使用for xml 子句聚合字符串。
  • 无法在 T-SQL 中完成。您需要动态 SQL,而函数不能使用它。将STRING_AGG 添加到语言中是有原因的。自定义 CLR 聚合可以做到这一点,但在这种情况下,您无需重新发明轮子;网络上有很多这样的例子。
  • @JeroenMostert,好吧,做不到并不完全正确:-D(尽管我不推荐我的“解决方案”: -D)
  • @JeroenMostert 将子选择作为隐式转换的 XML 传递的想法可能非常有用。在this answer 中,我提供了一个通用函数,从您传入的任何SELECT 中创建一个HTML 表。对于给定的情况,最好在语句中直接使用STUFF-FOR XML-hack,而不是调用 UDF...

标签: sql-server user-defined-functions string-aggregation


【解决方案1】:

好的.. 所以在@MichałTurczyn 的第一条评论中,我遇到了thisMicrosoft 关于CLR 用户定义的聚合 - 调用函数的文章

将代码编译成 SrAggFunc.dll 后,我试图在 SQL Server 中注册聚合,如下所示:

CREATE ASSEMBLY [STR_AGG] FROM 'C:\tmp\STR_AGG.dll'; 
GO

但我收到以下错误。

消息 6501,第 16 级,状态 7,第 1 行
CREATE ASSEMBLY 失败,因为它无法打开物理文件 'C:\tmp\SrAggFunc.dll': 3(系统找不到指定的路径。)。

所以我使用了@SanderRijken 代码的this 优秀部分,然后将命令更改为

CREATE ASSEMBLY [STR_AGG] 
FROM 0x4D5A90000300000004000000FF......000; --from GetHexString function
GO

然后,

CREATE AGGREGATE [STR_AGG] (@input nvarchar(200)) RETURNS nvarchar(max) 
EXTERNAL NAME [STR_AGG].C_STRING_AGG;`

现在已经完成了。

您可以在您的数据库下看到它 -> SSMS 上的可编程性

并像这样使用:

SELECT a.Id, [dbo].[STR_AGG](c.Desc) cDesc
FROM TableA a
JOIN TableB b on b.aId = a.Id
JOIN TableC c on c.Code = b.bCode 
GROUP BY a.Id

谢谢大家=)

【讨论】:

  • 很高兴您找到了一个好的解决方案!您的路径问题可能来自这样一个事实,即 SqlServer 在许多安装中看到的 c: 与您不同
  • 第一个问题:排序顺序呢?您可以为此添加一个参数,否则这可以是随机的
【解决方案2】:

好吧,这是一个丑陋的黑客,我现在必须去洗手,但它有效(在某种程度上:-D)

CREATE FUNCTION dbo.MyStringAgg(@SelectForXmlAuto XML,@Delimiter NVARCHAR(10))
RETURNS NVARCHAR(MAX)
AS
BEGIN
    RETURN STUFF((
             SELECT @Delimiter + A.nd.value(N'(@*)[1]',N'nvarchar(max)')
             FROM @SelectForXmlAuto.nodes(N'/*') AS A(nd)
             FOR XML PATH(''),TYPE
           ).value(N'.',N'nvarchar(max)'),1,LEN(@Delimiter),'');
END
GO

DECLARE @tbl TABLE(GroupId INT,SomeValue NVARCHAR(10));
INSERT INTO @tbl VALUES(1,'A1'),(1,'A2'),(2,'B1'),(3,'C1'),(3,'C2'),(3,'C3');

SELECT GroupId
      ,dbo.MyStringAgg((SELECT SomeValue 
                        FROM @tbl AS t2 
                        WHERE t2.GroupId=t.GroupId 
                        FOR XML AUTO), N', ')
FROM @tbl AS t
GROUP BY GroupId;
GO

DROP FUNCTION dbo.MyStringAgg;

结果

1    A1, A2
2    B1
3    C1, C2, C3

参数是括号内的FOR XML 子选择。这会将子选择结果作为 XML 隐式传递给函数。

说实话:我自己不会用这个……

这样的查询

SELECT GroupId
      ,STUFF((SELECT N', ' + SomeValue 
              FROM @tbl AS t2 
              WHERE t2.GroupId=t.GroupId 
              FOR XML PATH,TYPE).value(N'.','nvarchar(max)'),1,2,'')
FROM @tbl AS t
GROUP BY GroupId;

产生相同的结果并且输入的数量几乎相同 - 但应该比调用慢速 UDF 更快...

【讨论】:

    猜你喜欢
    • 2018-08-27
    • 1970-01-01
    • 1970-01-01
    • 2014-01-06
    • 1970-01-01
    • 2023-01-02
    • 2018-06-30
    相关资源
    最近更新 更多