SQL Server 中的自定义聚合函数 (concat)

Posted

技术标签:

【中文标题】SQL Server 中的自定义聚合函数 (concat)【英文标题】:Custom aggregate function (concat) in SQL Server 【发布时间】:2011-05-21 10:57:04 【问题描述】:

问题:我想编写一个自定义聚合函数,将字符串连接到 group by。

这样我就可以做一个

SELECT SUM(FIELD1) as f1, MYCONCAT(FIELD2)  as f2
FROM TABLE_XY
GROUP BY FIELD1, FIELD2

我发现的只是 SQL CRL 聚合函数,但我需要 SQL,没有 CLR。​​

编辑:1 查询应如下所示:

   SELECT SUM(FIELD1) as f1, MYCONCAT(FIELD2)  as f2
    FROM TABLE_XY
    GROUP BY FIELD0

编辑 2: 确实,没有 CLR 是不可能的。 但是,可以修改 asstander 的子选择答案,使其不对特殊字符进行 XML 编码。

对此的细微改变是在“FOR XML PATH”之后添加: ,

 TYPE 
                  ).value('.[1]', 'nvarchar(MAX)') 

这里有几个例子

DECLARE @tT table([A] varchar(200), [B] varchar(200));

INSERT INTO @tT VALUES ('T_A', 'C_A');
INSERT INTO @tT VALUES ('T_A', 'C_B');
INSERT INTO @tT VALUES ('T_B', 'C_A');
INSERT INTO @tT VALUES ('T_C', 'C_A');
INSERT INTO @tT VALUES ('T_C', 'C_B');
INSERT INTO @tT VALUES ('T_C', 'C_C');

SELECT 
      A AS [A]
      ,
      ( 
            STUFF 
            ( 
                    ( 
                             SELECT DISTINCT 
                                   ', ' + tempT.B AS wtf 
                             FROM @tT AS tempT 
                             WHERE (1=1) 
                             --AND tempT.TT_Status = 1 
                             AND tempT.A = myT.A 
                             ORDER BY wtf 
                             FOR XML PATH, TYPE 
                    ).value('.[1]', 'nvarchar(MAX)') 
                    , 1, 2, '' 
            ) 
      ) AS [B] 
FROM @tT AS myT
GROUP BY A 





SELECT 
      ( 
            SELECT 
                  ',äöü<>' + RM_NR AS [text()] 
            FROM T_Room 
            WHERE RM_Status = 1 
            ORDER BY RM_NR 
            FOR XML PATH('') 

      ) AS XmlEncodedNoNothing  


      ,
      SUBSTRING
      (
            (
                  SELECT 
                        ',äöü<>' + RM_NR  AS [data()] 
                  FROM T_Room 
                  WHERE RM_Status = 1 
                  ORDER BY RM_NR 
                  FOR XML PATH('')
            )
            ,2
            ,10000
      ) AS XmlEncodedSubstring  


      ,
      ( 
            STUFF 
            ( 
                  ( 
                        SELECT ',äöü<>' + RM_NR + CHAR(10) 
                        FROM T_Room 
                        WHERE RM_Status = 1 
                        ORDER BY RM_NR 
                        FOR XML PATH, TYPE 
                  ).value('.[1]', 'nvarchar(MAX)') 
                  , 1, 1, '' 
            ) 
      ) AS XmlDecodedStuffInsteadSubstring   

【问题讨论】:

在您的示例代码中,FIELD2 将只有一个值(GROUP BY),因此您不需要该函数。我猜你的例子是错误的。 啊哈哈哈,好一个 - 该死的,你是对的。 Field0 将是一个 UID(分组依据),field1 和 field2 不应该在 group 子句中...... 【参考方案1】:

您不能在 CLR 之外编写自定义聚合。

您可以在纯 T-SQL 中编写的唯一类型的函数是标量和表值函数。

比较 CREATE AGGREGATE 的页面,它只列出 CLR 样式选项,CREATE FUNCTION,它显示 T-SQL 和 CLR 选项。

【讨论】:

问题是我不能将 CLR 功能提供给客户进行安装,尤其是在受限安全区域内...但无论如何,“不可能”回答了我的问题。 @Quandary 只是一个建议:如果您告诉数据库管理员他需要在 sql 中启用 clr 并加载您的程序集,那么他当然会说“哦,这是一个安全风险”。但是,如果您让您的应用完成所有这些工作,并且只说“该应用需要这些权限”(或者更好的是,只需将它放在安装程序中,因此只有安装程序需要这些权限),那么您更有可能获得批准。也许根本不是你的选择(我意识到这是一个老问题),但我想我会把这个想法扔在那里。 @Brandon Moore: ;-) 是的,你成功地发现了字里行间的问题——建议政治来拯救。但是,我非常怀疑任何足够聪明的数据库管理员是否会给任何应用程序更改数据库范围设置的权利...... @Quandary 这一切都在定价中。鼓励他们为您打开 CLR,如果他们拒绝,那么至少值得您花时间以另一种方式进行;) @Brandon Moore:当工作得到报酬时,我值得。否则,这只是一项没有人支付的多余工作/支出。底线是这不在商定的定价范围内。【参考方案2】:

看看类似的东西。这不是一个聚合函数。如果你想实现你自己的聚合函数,它必须是 CLR...

DECLARE @Table TABLE(
        ID INT,
        Val VARCHAR(50)
)
INSERT INTO @Table (ID,Val) SELECT 1, 'A'
INSERT INTO @Table (ID,Val) SELECT 1, 'B'
INSERT INTO @Table (ID,Val) SELECT 1, 'C'
INSERT INTO @Table (ID,Val) SELECT 2, 'B'
INSERT INTO @Table (ID,Val) SELECT 2, 'C'

--Concat
SELECT  t.ID,
        SUM(t.ID),
        stuff(
                (
                    select  ',' + t1.Val
                    from    @Table t1
                    where   t1.ID = t.ID
                    order by t1.Val
                    for xml path('')
                ),1,1,'') Concats
FROM    @Table t
GROUP BY t.ID

【讨论】:

以表格变量为例...将其替换为您要聚合的表格。 @Quandary:我真的不明白你的抱怨。临时表只是为了展示它是如何工作的。我不知道 xml 路径,但是如果您为您的问题找到了一个简短、简单、有效的解决方案,那么它是否不可读、不直观等又有什么关系呢? +1 @astander 我尝试您的解决方案越多,我就越喜欢它。它立即生效,无需任何准备。直到现在,我还没有看到一个完整的可立即使用的 CLR 解决方案。 (我的第一印象是它很慢,但这是由一些人工制品造成的) @bernd_k:它并不慢,但它确实对一些字母进行了 XML 编码,然后你必须对结果进行 1000 次替换(实际上大约是 4,但问题出在括号,如果我喜欢丰富的括号,我会用 LISP 编程)。 @bernd_k:找到了一种更简单的方法来解决这个问题。在edit2中添加。【参考方案3】:

从 2017 年开始,有内置的连接聚合函数 STRING_AGG :)

https://docs.microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql?view=sql-server-2017

【讨论】:

问题被标记为 SQL Server 2008。大多数人还没有使用 SQL Server 2017。 @bitoolean,很多人考虑迁移到 Azure 中没有 CLR 和扩展 SP 的托管 SQL 数据库 既然您提到了这一点,那就说得通了。谢谢你的解释!在这种情况下,您的回答值得投票。【参考方案4】:

在连接周围发现了这个link,它涵盖了类似的方法

项目数未知时的连接值

递归 CTE 方法 黑盒 XML 方法 使用公共语言运行时 带有递归的标量 UDF 带有 WHILE 循环的表值 UDF 动态 SQL 光标方法

不可靠的方法

带有 t-SQL 更新扩展的标量 UDF 在 SELECT 中具有变量连接的标量 UDF

虽然它不包括 aggerate 函数,但其​​中的连接可能有一些用途,可以帮助您解决问题。

【讨论】:

@Quandary - 完成此任务的两种最佳方法 XML PATHCLR AGGREGATES。如果您忽略这两个,那么您将在上面列表中的更糟糕的剩余解决方案中挣扎。没有其他方法。 @Martin Smith:终于找到了问题的正确解决方案。见编辑 2。【参考方案5】:

此解决方案无需从 Visual Studio 部署或服务器中的 dll 文件即可工作。

复制粘贴即可!

https://github.com/orlando-colamatteo/ms-sql-server-group-concat-sqlclr

dbo.GROUP_CONCAT(VALUE )
dbo.GROUP_CONCAT_D(VALUE ), DELIMITER )  
dbo.GROUP_CONCAT_DS(VALUE , DELIMITER , SORT_ORDER )
dbo.GROUP_CONCAT_S(VALUE , SORT_ORDER )

【讨论】:

这仍然需要在 SQL Server 中启用 CLR 并部署 DLL。 @thomas 是正确的。该页面上的副标题甚至是这样写的——“SQL Server CLR 用户定义的聚合”…… 这个库是在 GNU/GPL 许可下发布的,所以在商业应用中使用时要小心。【参考方案6】:

您可以像我在下面所做的那样在纯 T-SQL 中创建自定义聚合连接函数。显然,我使用了硬编码的表名并按列分组,但它应该说明该方法。可能有某种方法可以使用从输入参数构造的动态 TSQL 使其成为真正的通用函数。

/*
User defined function to help perform concatenations as an aggregate function
Based on AdventureWorks2008R2 SalesOrderDetail table
*/

--select * from sales.SalesOrderDetail 

IF EXISTS (SELECT * 
        FROM   sysobjects 
        WHERE  name = N'fnConcatenate')
    DROP FUNCTION fnConcatenate
GO

CREATE FUNCTION fnConcatenate
 (
      @GroupByValue int
        )                       
returnS varchar(8000)
as

BEGIN


    DECLARE @SqlString varchar(8000)
    Declare @TempStore varchar(25)
    select @SqlString =''

    Declare @MyCursor as Cursor
          SET @MyCursor = CURSOR FAST_FORWARD 
          FOR 
          Select ProductID 
          From sales.SalesOrderDetail  where SalesOrderID  = @GroupByValue
          order by SalesOrderDetailID asc


      OPEN @MyCursor 

         FETCH NEXT FROM @MyCursor
         INTO @TempStore

        WHILE @@FETCH_STATUS = 0 
        BEGIN 


          select @SqlString = ltrim(rtrim(@TempStore )) +',' + ltrim(rtrim(@SqlString))
          FETCH NEXT FROM @MyCursor INTO @TempStore

        END 

CLOSE @MyCursor
DEALLOCATE @MyCursor

RETURN @SqlString

END
GO


select  SalesOrderID, Sum(OrderQty),  COUNT(*) as DetailCount , dbo.fnConcatenate(salesOrderID) as ConCatenatedProductList
from sales.SalesOrderDetail 
where salesOrderID= 56805 
group by SalesOrderID 

【讨论】:

@Quandary;什么动态SQL?

以上是关于SQL Server 中的自定义聚合函数 (concat)的主要内容,如果未能解决你的问题,请参考以下文章

SqlServer如何用Sql语句自定义聚合函数

sql server 2012 自定义聚合函数(MAX_O3_8HOUR_ND) 计算最大的臭氧8小时滑动平均值

SQL 中的自定义聚合

sql server中啥是聚合函数

95-910-148-源码-FlinkSQL-Flink SQL自定义聚合函数

sql server 中的自定义函数应该如何调用?