1000项组合记录的表现从1分40秒上升到次秒。对于需要在旧版SQL Server上执行此操作而不使用XML的用户,请参见: DECLARE @STRUCTURED_VALUES TABLE ( ID INT

1000项组合记录的表现从1分40秒上升到次秒。对于需要在旧版SQL Server上执行此操作而不使用XML的用户,请参见: DECLARE @STRUCTURED_VALUES TABLE ( ID INT ,sql,sql-server,azure,aggregate-functions,azure-sql-database,Sql,Sql Server,Azure,Aggregate Functions,Azure Sql Database,1000项组合记录的表现从1分40秒上升到次秒。对于需要在旧版SQL Server上执行此操作而不使用XML的用户,请参见: DECLARE @STRUCTURED_VALUES TABLE ( ID INT ,VALUE VARCHAR(MAX) NULL ,VALUENUMBER BIGINT ,VALUECOUNT INT ); INSERT INTO @STRUCT

1000项组合记录的表现从1分40秒上升到次秒。对于需要在旧版SQL Server上执行此操作而不使用XML的用户,请参见:

DECLARE @STRUCTURED_VALUES TABLE (
     ID                 INT
    ,VALUE              VARCHAR(MAX) NULL
    ,VALUENUMBER        BIGINT
    ,VALUECOUNT         INT
);

INSERT INTO @STRUCTURED_VALUES
SELECT   ID
        ,VALUE
        ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY VALUE) AS VALUENUMBER
        ,COUNT(*) OVER (PARTITION BY ID)    AS VALUECOUNT
FROM    RAW_VALUES_TABLE;

WITH CTE AS (
    SELECT   SV.ID
            ,SV.VALUE
            ,SV.VALUENUMBER
            ,SV.VALUECOUNT
    FROM    @STRUCTURED_VALUES SV
    WHERE   VALUENUMBER = 1

    UNION ALL

    SELECT   SV.ID
            ,CTE.VALUE + ' ' + SV.VALUE AS VALUE
            ,SV.VALUENUMBER
            ,SV.VALUECOUNT
    FROM    @STRUCTURED_VALUES SV
    JOIN    CTE 
        ON  SV.ID = CTE.ID
        AND SV.VALUENUMBER = CTE.VALUENUMBER + 1

)
SELECT   ID
        ,VALUE
FROM    CTE
WHERE   VALUENUMBER = VALUECOUNT
ORDER BY ID
;


在什么情况下
for xml
对您不起作用?它确实起作用,但我查看了执行计划,每个
for xml
在查询性能方面显示了25%的使用率(大部分查询!)有不同的方法执行
for xml path
查询。有些人比其他人快。这可能取决于您的数据,但根据我的经验,使用
distinct
的数据要比使用
groupby
的数据慢。如果您使用
.value('.',nvarchar(max))
来获取连接的值,那么您应该将其更改为
.value('../text()[1]',nvarchar(max))
您接受的答案类似于我的答案,我认为它比XML更快。不要被查询成本所愚弄,您需要足够的数据来查看哪个更快。XML速度更快,这恰好是@MikaelEriksson在同一平台上的速度。选择XML方法请在此处投票选择本机解决方案:一旦表的大小出现问题,不要忘记在
id
列上放置索引。在阅读stuff/for XML path如何工作()之后,我相信这是一个很好的解决方案,尽管XML的名称是:)@slackterman取决于要操作的记录的数量。我认为与CTE相比,XML在低计数方面是不足的,但在较高的卷计数方面,如果操作正确且简洁,则会减轻递归限制,并且更易于导航。对于XML路径方法,如果数据中有表情符号或特殊/代理字符,则会爆炸!!!此代码生成xml编码的文本(
&
切换到
&
,依此类推)。为xml提供了一个更正确的
解决方案。我对照xmlpath检查了这种方法的时间消耗,我达到了大约4毫秒,而不是大约54毫秒。因此,xmplath方法尤其在大型情况下更好。我将在一个单独的答案中编写比较代码。它要好得多,因为这种方法最多只适用于100个值。@romano zumbé使用MAXRECURSION将CTE限制设置为您需要的任何值。令人惊讶的是,CTE对我来说慢得多。比较了一系列技术,似乎与我的结果一致。对于记录超过100万条的表,此解决方案不起作用。此外,我们对递归深度有一个限制,我刚刚测试了它,它就像Azure SQL数据库中的一个符咒一样工作
STRING\u AGG()
据说在SQL Server 2017中可以在任何兼容级别上使用。对STRING_AGG在SQL Server 2016中不可用。我刚刚测试了它,现在它在Azure SQL数据库中运行良好。
STRING_AGG
被推迟到2017年。2016年不可用。感谢您,Aamir和Morgan Thrapp对SQL Server版本的更改。更新。(在撰写本文时,它声称在2016版中得到支持。)+1,你是(黑暗艺术)的QMaster,你是!我得到了一个更显著的差异(在SQL Server 2008 R2上约3000毫秒的CTE,在Windows Server 2008 R2上约70毫秒的XML,在Intel Xeon E5-2630 v4@2.20 GHZ x2 w/~1 GB免费)。唯一的建议是:1)在两个版本中使用OP或(最好)通用术语,2)由于OP的Q.是如何“连接/聚合字符串”,并且这仅适用于字符串(相对于数值),通用术语太通用。只需使用“GroupNumber”和“StringValue”,3)声明并使用“Delimiter”变量,并使用“Len(Delimiter)”对“2”。+1用于不将特殊字符扩展为XML编码(例如,&“不会像其他许多低级解决方案那样扩展为”&;)请指定支持SQL方言或版本的时间。这在SQL Server 2012中有效。请注意,可以使用
select@test+=name+',from names
创建逗号分隔的列表。这使用未定义的行为,不安全。如果查询中有一个
ORDER BY,则这很可能会给出奇怪/不正确的结果。您应该使用列出的备选方案之一。这种类型的查询从未定义过行为,在SQL Server 2019中,我们发现它的错误行为比以前的版本更加一致。不要使用这种方法。
;WITH Partitioned AS
(
    SELECT 
        ID,
        Name,
        ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name) AS NameNumber,
        COUNT(*) OVER (PARTITION BY ID) AS NameCount
    FROM dbo.SourceTable
),
Concatenated AS
(
    SELECT 
        ID, 
        CAST(Name AS nvarchar) AS FullName, 
        Name, 
        NameNumber, 
        NameCount 
    FROM Partitioned 
    WHERE NameNumber = 1

    UNION ALL

    SELECT 
        P.ID, 
        CAST(C.FullName + ', ' + P.Name AS nvarchar), 
        P.Name, 
        P.NameNumber, 
        P.NameCount
    FROM Partitioned AS P
        INNER JOIN Concatenated AS C 
                ON P.ID = C.ID 
                AND P.NameNumber = C.NameNumber + 1
)
SELECT 
    ID,
    FullName
FROM Concatenated
WHERE NameNumber = NameCount
INSERT dbo.SourceTable (ID, Name)
VALUES 
(1, 'Matt'),
(1, 'Rocks'),
(2, 'Stylus'),
(3, 'Foo'),
(3, 'Bar'),
(3, 'Baz')
ID          FullName
----------- ------------------------------
2           Stylus
3           Bar, Baz, Foo
1           Matt, Rocks
create table #t (id int, name varchar(20))

insert into #t
values (1, 'Matt'), (1, 'Rocks'), (2, 'Stylus')

select  id
        ,Names = stuff((select ', ' + name as [text()]
        from #t xt
        where xt.id = t.id
        for xml path('')), 1, 2, '')
from #t t
group by id
DECLARE @startTime datetime2;
DECLARE @endTime datetime2;
DECLARE @counter INT;
SET @counter = 1;

set nocount on;

declare @YourTable table (ID int, Name nvarchar(50))

WHILE @counter < 1000
BEGIN
    insert into @YourTable VALUES (ROUND(@counter/10,0), CONVERT(NVARCHAR(50), @counter) + 'CC')
    SET @counter = @counter + 1;
END

SET @startTime = GETDATE()

;WITH Partitioned AS
(
    SELECT 
        ID,
        Name,
        ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name) AS NameNumber,
        COUNT(*) OVER (PARTITION BY ID) AS NameCount
    FROM @YourTable
),
Concatenated AS
(
    SELECT ID, CAST(Name AS nvarchar) AS FullName, Name, NameNumber, NameCount FROM Partitioned WHERE NameNumber = 1

    UNION ALL

    SELECT 
        P.ID, CAST(C.FullName + ', ' + P.Name AS nvarchar), P.Name, P.NameNumber, P.NameCount
    FROM Partitioned AS P
        INNER JOIN Concatenated AS C ON P.ID = C.ID AND P.NameNumber = C.NameNumber + 1
)
SELECT 
    ID,
    FullName
FROM Concatenated
WHERE NameNumber = NameCount

SET @endTime = GETDATE();

SELECT DATEDIFF(millisecond,@startTime, @endTime)
--Take about 54 milliseconds
DECLARE @startTime datetime2;
DECLARE @endTime datetime2;
DECLARE @counter INT;
SET @counter = 1;

set nocount on;

declare @YourTable table (RowID int, HeaderValue int, ChildValue varchar(5))

WHILE @counter < 1000
BEGIN
    insert into @YourTable VALUES (@counter, ROUND(@counter/10,0), CONVERT(NVARCHAR(50), @counter) + 'CC')
    SET @counter = @counter + 1;
END

SET @startTime = GETDATE();

set nocount off
SELECT
    t1.HeaderValue
        ,STUFF(
                   (SELECT
                        ', ' + t2.ChildValue
                        FROM @YourTable t2
                        WHERE t1.HeaderValue=t2.HeaderValue
                        ORDER BY t2.ChildValue
                        FOR XML PATH(''), TYPE
                   ).value('.','varchar(max)')
                   ,1,2, ''
              ) AS ChildValues
    FROM @YourTable t1
    GROUP BY t1.HeaderValue

SET @endTime = GETDATE();

SELECT DATEDIFF(millisecond,@startTime, @endTime)
--Take about 4 milliseconds
select Id
, STRING_AGG(Name, ', ') Names 
from Demo
group by Id
SELECT id, STRING_AGG(name, ', ') AS names
FROM some_table
GROUP BY id
declare @test nvarchar(max)
set @test = ''
select @test += name from names
DECLARE @STRUCTURED_VALUES TABLE (
     ID                 INT
    ,VALUE              VARCHAR(MAX) NULL
    ,VALUENUMBER        BIGINT
    ,VALUECOUNT         INT
);

INSERT INTO @STRUCTURED_VALUES
SELECT   ID
        ,VALUE
        ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY VALUE) AS VALUENUMBER
        ,COUNT(*) OVER (PARTITION BY ID)    AS VALUECOUNT
FROM    RAW_VALUES_TABLE;

WITH CTE AS (
    SELECT   SV.ID
            ,SV.VALUE
            ,SV.VALUENUMBER
            ,SV.VALUECOUNT
    FROM    @STRUCTURED_VALUES SV
    WHERE   VALUENUMBER = 1

    UNION ALL

    SELECT   SV.ID
            ,CTE.VALUE + ' ' + SV.VALUE AS VALUE
            ,SV.VALUENUMBER
            ,SV.VALUECOUNT
    FROM    @STRUCTURED_VALUES SV
    JOIN    CTE 
        ON  SV.ID = CTE.ID
        AND SV.VALUENUMBER = CTE.VALUENUMBER + 1

)
SELECT   ID
        ,VALUE
FROM    CTE
WHERE   VALUENUMBER = VALUECOUNT
ORDER BY ID
;