多列串联

Bij*_*ose 6 sql-server-2008 sql-server t-sql

如何将多列连接成一行?例如:

id   name    car
1    sam     dodge
1    ram     maserati
1    john    benz
1    NULL    mazda
2    kirk    lexus
2    Jim     rolls
1            GMC
Run Code Online (Sandbox Code Playgroud)

预期的结果集是:

ID  name               car
1   sam,ram,john       dodge,maserati,benz,mazda,GMC
2   kirk,jim           lexus,rolls
Run Code Online (Sandbox Code Playgroud)

使用我在 Stack Overflow 上找到的解决方案

SELECT * FROM (
SELECT t.id,stuff([m].query('/name').value('/', 'varchar(max)'),1,1,'') AS [SomeField_Combined1],
stuff([m].query('/car').value('/', 'varchar(max)'),1,1,'') AS [SomeField_Combined2]
FROM dbo.test t
OUTER apply(SELECT (

SELECT id, ','+name AS name
,','+car AS car
FROM test WHERE test.id=t.id
FOR XML PATH('') ,type)
             AS  M) A)S
GROUP BY id,somefield_combined1,somefield_combined2 
Run Code Online (Sandbox Code Playgroud)

有没有更好的解决方案?内部选择来自昂贵的多表连接(不是上面显示的单表“测试”)。查询在一个内联 TVF 中,所以我不能使用临时表。

此外,如果有一个空白列,结果将产生额外的逗号,如

ID  name                car
1   sam,ram,john,,      dodge,maserati,benz,mazda,GMC
2   kirk,jim           lexus,rolls
Run Code Online (Sandbox Code Playgroud)

有什么办法可以防止这种情况吗?

Mar*_*ith 6

CLR 聚合几乎肯定是执行此操作的最快方法。但也许你不想因为任何原因使用一个......

你说这个来源是一个昂贵的查询。

我会#temp首先将其具体化为一个表格,以确保它只评估一次。

CREATE TABLE #test
(
ID INT,
name NVARCHAR(128),
car  NVARCHAR(128)
);

CREATE CLUSTERED INDEX ix ON #test(ID);
Run Code Online (Sandbox Code Playgroud)

我为问题中的查询获得的执行计划首先对外部查询中的每一行进行串联,然后通过id, SomeField_Combined1, SomeField_Combined2.

这是非常浪费的。下面的重写避免了这种情况。

SELECT t.id,
       stuff([M].query('/name').value('/', 'varchar(max)'), 1, 1, '') AS [SomeField_Combined1],
       stuff([M].query('/car').value('/', 'varchar(max)'), 1, 1, '')  AS [SomeField_Combined2]
FROM   (SELECT DISTINCT id
        FROM   #test) t
       OUTER APPLY(SELECT (SELECT id,
                                  ',' + name AS name,
                                  ',' + car  AS car
                           FROM   #test
                           WHERE  #test.id = t.id
                           FOR XML PATH(''), type) AS M) M 
Run Code Online (Sandbox Code Playgroud)

但是对于以下测试数据(1000 个 id,每个 id 有 2156 行)

INSERT INTO #test
SELECT v.number, o.name, o.type_desc
FROM   sys.all_objects o
       INNER JOIN master..spt_values v
         ON v.type = 'P' AND v.number BETWEEN 1 AND 1000 
Run Code Online (Sandbox Code Playgroud)

我仍然发现 Kenneth 的解决方案有两个XML PATH调用更快,资源占用更少。

+-----------------+--------------------+------------------------+------------------+---------------------+-------------------------+-----------------------------+
|                 | CPU Time (Seconds) | Elapsed Time (Seconds) | #test Scan Count | #test Logical Reads | Worktable logical reads | Worktable lob logical reads |
+-----------------+--------------------+------------------------+------------------+---------------------+-------------------------+-----------------------------+
| Single XML PATH | 51.077             | 15.521                 | 1,005            | 60,165              | 51,161                  | 1,329,207                   |
| Double XML PATH | 3.1720             | 3.010                  | 2,005            | 92,088              | 14,951                  |   233,681                   |
+-----------------+--------------------+------------------------+------------------+---------------------+-------------------------+-----------------------------+
Run Code Online (Sandbox Code Playgroud)

对于其中的每个不同id#test它执行两个操作而不是一个操作,但此操作比构造 XML 然后重新解析它要便宜得多。


Ken*_*her 5

我使用略多于 600 万行的行进行了一些测试。在 ID 列上有一个索引。

这是我想出的。

您的初始查询:

SELECT * FROM (
    SELECT t.id,
            stuff([M].query('/name').value('/', 'varchar(max)'),1,1,'') AS [SomeField_Combined1],
            stuff([M].query('/car').value('/', 'varchar(max)'),1,1,'') AS [SomeField_Combined2]
    FROM dbo.test t
    OUTER APPLY(SELECT (
                    SELECT id, ','+name AS name
                    ,','+car AS car
                    FROM test WHERE test.id=t.id
                    FOR XML PATH('') ,type)
                 AS  M) 
            M ) S
GROUP BY id, SomeField_Combined1, SomeField_Combined2 
Run Code Online (Sandbox Code Playgroud)

这个运行了大约 23 分钟。

我运行了这个版本,这是我第一次学习的版本。在某些方面,它似乎需要更长的时间,但事实并非如此。

SELECT test.id,
    STUFF((SELECT ', ' + ThisTable.name
            FROM   test ThisTable
            WHERE  test.id = ThisTable.id
            AND    ThisTable.name <> ''
            FOR XML PATH ('')),1,2,'') AS ConcatenatedSomeField,
    STUFF((SELECT ', ' + car
            FROM   test ThisTable
            WHERE  test.id = ThisTable.id
            AND    ThisTable.car <> ''
            FOR XML PATH ('')),1,2,'') AS ConcatenatedSomeField2
FROM test 
GROUP BY id
Run Code Online (Sandbox Code Playgroud)

这个版本只运行了 2 分钟多一点。