2013-05-16 101 views
4

我知道的是,「合併多行到列表」的問題已經回答了無數次,這裏是一個真棒文章的引用:Concatenating row values in transact sql將多個行到列表中多列

我有必要以多行同時

ID | Col1 | Col2  ID | Col1 | Col2 
------------------ => ------------------ 
    1 A  X   1 A  X  
    2 B  Y   2 B,C Y,Z 
    2 C  Z 

我嘗試使用XML方法合併爲多列清單,但是這已經被證明是在大表非常慢

SELECT DISTINCT 
    [ID], 
    [Col1] = STUFF((SELECT ',' + t2.[Col1] 
        FROM #Table t2 
        WHERE t2.ID = t.ID 
        FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'),1,1,''), 
    [Col2] = STUFF((SELECT ',' + t2.[Col2] 
        FROM #Table t2 
        WHERE t2.ID = t.ID 
        FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'),1,1,''), 
FROM #Table t 

我目前的解決方案是使用一個存儲過程來分別構建每個ID行。我不知道是否還有另一種方法,我可以使用(不是使用循環等)

For each column, rank the rows to combine (partition by the key column) 

End up with a table like 
ID | Col1 | Col2 | Col1Rank | Col2Rank 
1  A  X  1   1 
2  B  Y  1   1 
2  C  Z  2   2 

Create a new table containing top rank columns for each ID 
ID | Col1Comb | Col2Comb 
1  A   X 
2  B   Y 

Loop through each remaining rank in increasing order (in this case 1 iteration) 
for irank = 0; irank <= 1; irank++ 
    update n set 
     n.col1Comb = n.Col1Comb + ',' + o.Col1, -- so append the rank 2 items 
     n.col2comb = n.Col2Comb + ',' + o.Col2 -- if they are not null 
    from #newtable n 
    join #oldtable o 
     on o.ID = n.ID 
    where o.col1rank = irank or o.col2rank = irank 

回答

3

CTE技巧可用於更新CTE的地方。

方法1:到的數據被複制,然後再連接一個新的並行表:

CREATE TABLE #Table1(ID INT, Col1 VARCHAR(1), Col2 VARCHAR(1), RowID INT IDENTITY(1,1)); 
CREATE TABLE #Table1Concat(ID INT, Col3 VARCHAR(MAX), Col4 VARCHAR(MAX), RowID INT); 
GO 

INSERT #Table1 VALUES(1,'A','X'), (2,'B','Y'), (2,'C','Z'); 
GO 
INSERT #Table1Concat 
SELECT * FROM #Table1; 
GO 
DECLARE @Cat1 VARCHAR(MAX) = ''; 
DECLARE @Cat2 VARCHAR(MAX) = ''; 
; WITH CTE AS (
    SELECT TOP 2147483647 t1.*, t2.Col3, t2.Col4, r = ROW_NUMBER()OVER(PARTITION BY t1.ID ORDER BY t1.Col1, t1.Col2) 
    FROM #Table1 t1 
    JOIN #Table1Concat t2 ON t1.RowID = t2.RowID 
    ORDER BY t1.ID, t1.Col1, t1.Col2 
) 
UPDATE CTE 
SET @Cat1 = Col3 = CASE r WHEN 1 THEN ISNULL(Col1,'') ELSE @Cat1 + ',' + Col1 END 
, @Cat2 = Col4 = CASE r WHEN 1 THEN ISNULL(Col2,'') ELSE @Cat2 + ',' + Col2 END; 
GO 

SELECT ID, Col3 = MAX(Col3) 
, Col4 = MAX(Col4) 
FROM #Table1Concat 
GROUP BY ID 

方法2:直接添加串聯列到原始表並連接所有的新列:

CREATE TABLE #Table1(ID INT, Col1 VARCHAR(1), Col2 VARCHAR(1), Col1Cat VARCHAR(MAX), Col2Cat VARCHAR(MAX)); 
GO 

INSERT #Table1(ID,Col1,Col2) VALUES(1,'A','X'), (2,'B','Y'), (2,'C','Z'); 
GO 

DECLARE @Cat1 VARCHAR(MAX) = ''; 
DECLARE @Cat2 VARCHAR(MAX) = ''; 
; WITH CTE AS (
    SELECT TOP 2147483647 t1.*, r = ROW_NUMBER()OVER(PARTITION BY t1.ID ORDER BY t1.Col1, t1.Col2) 
    FROM #Table1 t1 
    ORDER BY t1.ID, t1.Col1, t1.Col2 
) 
UPDATE CTE 
SET @Cat1 = Col1Cat = CASE r WHEN 1 THEN ISNULL(Col1,'') ELSE @Cat1 + ',' + Col1 END 
, @Cat2 = Col2Cat = CASE r WHEN 1 THEN ISNULL(Col2,'') ELSE @Cat2 + ',' + Col2 END; 
GO 

SELECT ID, Col1Cat = MAX(Col1Cat) 
, Col2Cat = MAX(Col2Cat) 
FROM #Table1 
GROUP BY ID; 
GO 
+0

這是很整潔......我會做一些性能測試對我相當快速存儲過程並回報。你能分享關於你如何找到這個解決方案的更多細節嗎?或者爲什麼需要訂單?更新是否總是根據CTE中使用的順序進行? – user1002479

+0

謝謝,我正準備對大型設備做一些測試。在字符串連接時,需要使用order by來控制分組。我知道四種聚合字符串的方法,這些方法可以在性能方面進行比較(這裏按照我懷疑會最慢到最快的順序列出):遊標循環,.net CLR,XML和CTE。 –

+0

由於數據集越來越大,這比我放在一起的循環方法慢得多。較之前幾秒緩慢,但仍然比XML方法快得多(對於120k行:12圈vs 14cte vs 29xml秒)。我試圖在ID列上添加一個索引,但這沒有幫助。任何改進這一個想法? – user1002479

0

一個解決方案,一個是至少語法簡單明瞭,就是用一個用戶定義的聚合,以「加入」價值在一起。這確實需要SQLCLR,雖然有些人不願意啓用它,但它確實提供了基於集合的方法,無需每個列重新查詢基表。連接與Splitting相反,將創建一個以逗號分隔的單行列表。

下面是一個簡單的例子,它使用了一個名爲Agg_Join()的用戶定義聚合附帶的SQL#(SQLsharp)庫,它完全可以在這裏提出要求。您可以從http://www.SQLsharp.com/以及標準系統視圖中的示例SELECTs下載免費版本的SQL#。 (公平地說,我是SQL#的作者,但是這個功能是免費的)。

SELECT sc.[object_id], 
     OBJECT_NAME(sc.[object_id]) AS [ObjectName], 
     SQL#.Agg_Join(sc.name) AS [ColumnNames], 
     SQL#.Agg_Join(DISTINCT sc.system_type_id) AS [DataTypes] 
FROM sys.columns sc 
GROUP BY sc.[object_id] 

我建議測試此與目前的解決方案(S),看看這是最快的數據你期望在未來至少一年或兩年的音量。

1

嘗試這一個 -

查詢1:

DECLARE @temp TABLE 
(
     ID INT 
    , Col1 VARCHAR(30) 
    , Col2 VARCHAR(30) 
) 

INSERT INTO @temp (ID, Col1, Col2) 
VALUES 
    (1, 'A', 'X'), 
    (2, 'B', 'Y'), 
    (2, 'C', 'Z') 

SELECT 
     r.ID 
    , Col1 = STUFF(REPLACE(REPLACE(CAST(d.x.query('/t1/a') AS VARCHAR(MAX)), '<a>', ','), '</a>', ''), 1, 1, '') 
    , Col2 = STUFF(REPLACE(REPLACE(CAST(d.x.query('/t2/a') AS VARCHAR(MAX)), '<a>', ','), '</a>', ''), 1, 1, '') 
FROM (
    SELECT DISTINCT ID 
    FROM @temp 
) r 
OUTER APPLY (
    SELECT x = CAST((
     SELECT 
       [t1/a] = t2.Col1 
       , [t2/a] = t2.Col2 
     FROM @temp t2 
     WHERE r.ID = t2.ID 
     FOR XML PATH('') 
    ) AS XML) 
) d 

查詢2:

SELECT 
     r.ID 
    , Col1 = STUFF(REPLACE(CAST(d.x.query('for $a in /a return xs:string($a)') AS VARCHAR(MAX)), ' ,', ','), 1, 1, '') 
    , Col2 = STUFF(REPLACE(CAST(d.x.query('for $b in /b return xs:string($b)') AS VARCHAR(MAX)), ' ,', ','), 1, 1, '') 
FROM (
    SELECT DISTINCT ID 
    FROM @temp 
) r 
OUTER APPLY (
    SELECT x = CAST((
     SELECT 
       [a] = ',' + t2.Col1 
       , [b] = ',' + t2.Col2 
     FROM @temp t2 
     WHERE r.ID = t2.ID 
     FOR XML PATH('') 
    ) AS XML) 
) d 

輸出:

ID   Col1  Col2 
----------- ---------- ---------- 
1   A   X 
2   B,C  Y,Z 
+0

這似乎沒有提供比OP的查詢更好的性能改進,至少根據預計執行計劃的比較結果。 –

+0

感謝您的回答。今晚我會進行性能測試。即使速度較慢,知道如何使用xml「hacks」來做它真的很酷! – user1002479