2016-06-07 3 views
4

Предположим, эту таблицу:Как группировать конкатенацию нескольких столбцов?

PruchaseID | Customer | Product | Method 
-----------|----------|----------|-------- 
1   | John  | Computer | Credit 
2   | John  | Mouse | Cash 
3   | Will  | Computer | Credit 
4   | Will  | Mouse | Cash 
5   | Will  | Speaker | Cash 
6   | Todd  | Computer | Credit 

Я хочу, чтобы сгенерировать отчет по каждому клиенту, что они купили, и их способы оплаты.
Но я хочу, что отчет будет одна строка для каждого клиента, например:

Customer | Products     | Methods 
---------|--------------------------|-------------- 
John | Computer, Mouse   | Credit, Cash 
Will | Computer, Mouse, Speaker | Credit, Cash 
Todd | Computer     | Credit 

То, что я нашел до сих пор является групповой СЦЕПИТЬ с использованием метода XML PATH, такие как:

SELECT 
    p.Customer, 
    STUFF(
     SELECT ', ' + xp.Product 
     FROM Purchases xp 
     WHERE xp.Customer = p.Customer 
     FOR XML PATH('')), 1, 1, '') AS Products, 
    STUFF(
     SELECT ', ' + xp.Method 
     FROM Purchases xp 
     WHERE xp.Customer = p.Customer 
     FOR XML PATH('')), 1, 1, '') AS Methods 
FROM Purchases 

Это дает мне результат, но меня беспокоит скорость этого.
На первый взгляд здесь происходит три разных выбора, по два будут умножаться на количество строк, которые имеют Покупки. В конечном итоге это будет замедляться.

Итак, есть ли способ сделать это с лучшей производительностью?
Я хочу добавить еще больше столбцов для агрегации, должен ли я делать этот блок STUFF() для каждого столбца? Это звучит недостаточно быстро для меня.

Siggestions?

+0

Ну, вы денормализуете свои данные, чтобы сделать это, так что производительность будет потенциальной проблемой. Метод XML - лучший способ денормализации данных в список с разделителями. –

+0

Будьте осторожны при использовании 'for xml path', это может вас удивить, если у вас есть, например,' & 'в ваших данных. Аарон Бертран сделал [сравнение] (http://sqlperformance.com/2014/08/t-sql-queries/sql-server-grouped-concatenation) различных методов, которые вы, возможно, захотите проверить. –

ответ

4

Просто идея:

DECLARE @t TABLE (
    Customer VARCHAR(50), 
    Product VARCHAR(50), 
    Method VARCHAR(50), 
    INDEX ix CLUSTERED (Customer) 
) 

INSERT INTO @t (Customer, Product, Method) 
VALUES 
    ('John', 'Computer', 'Credit'), 
    ('John', 'Mouse', 'Cash'), 
    ('Will', 'Computer', 'Credit'), 
    ('Will', 'Mouse', 'Cash'), 
    ('Will', 'Speaker', 'Cash'), 
    ('Todd', 'Computer', 'Credit') 

SELECT t.Customer 
    , STUFF(CAST(x.query('a/text()') AS NVARCHAR(MAX)), 1, 2, '') 
    , STUFF(CAST(x.query('b/text()') AS NVARCHAR(MAX)), 1, 2, '') 
FROM (
    SELECT DISTINCT Customer 
    FROM @t 
) t 
OUTER APPLY (
    SELECT DISTINCT [a] = CASE WHEN id = 'a' THEN ', ' + val END 
        , [b] = CASE WHEN id = 'b' THEN ', ' + val END 
    FROM @t t2 
    CROSS APPLY (
     VALUES ('a', t2.Product) 
      , ('b', t2.Method) 
    ) t3 (id, val) 
    WHERE t2.Customer = t.Customer 
    FOR XML PATH(''), TYPE 
) t2 (x) 

Выход:

Customer Product     Method  
---------- -------------------------- ------------------ 
John  Computer, Mouse   Cash, Credit 
Todd  Computer     Credit 
Will  Computer, Mouse, Speaker Cash, Credit 

Еще одна идея с большим количеством преимуществ по производительности:

IF OBJECT_ID('tempdb.dbo.#EntityValues') IS NOT NULL 
    DROP TABLE #EntityValues 

DECLARE @Values1 VARCHAR(MAX) 
     , @Values2 VARCHAR(MAX) 

SELECT Customer 
    , Product 
    , Method 
    , RowNum = ROW_NUMBER() OVER (PARTITION BY Customer ORDER BY 1/0) 
    , Values1 = CAST(NULL AS VARCHAR(MAX)) 
    , Values2 = CAST(NULL AS VARCHAR(MAX)) 
INTO #EntityValues 
FROM @t 

UPDATE #EntityValues 
SET 
     @Values1 = Values1 = 
     CASE WHEN RowNum = 1 
      THEN Product 
      ELSE @Values1 + ', ' + Product 
     END 
    , @Values2 = Values2 = 
     CASE WHEN RowNum = 1 
      THEN Method 
      ELSE @Values2 + ', ' + Method 
     END 

SELECT Customer 
     , Values1 = MAX(Values1) 
     , Values2 = MAX(Values2) 
FROM #EntityValues 
GROUP BY Customer 

Но с некоторыми ограничениями:

Customer  Values1      Values2 
------------- ----------------------------- ---------------------- 
John   Computer, Mouse    Credit, Cash 
Todd   Computer      Credit 
Will   Computer, Mouse, Speaker  Credit, Cash, Cash 

Также проверьте мой старый пост о агрегации строки:

http://www.codeproject.com/Articles/691102/String-Aggregation-in-the-World-of-SQL-Server

+0

Полезно знать альтернативный путь. – niksofteng

+1

Привет, Devart, мне это нравится! – Shnugo

+0

@ Шнуго спасибо :) очень благодарен ... – Devart

1

Это один из вариантов использования для рекурсивного КТРА (Common табличных выражений). Вы можете узнать больше здесь https://technet.microsoft.com/en-us/library/ms190766(v=sql.105).aspx

; 
WITH CTE1 (PurchaseID, Customer, Product, Method, RowID) 
AS 
(
    SELECT 
     PurchaseID, Customer, Product, Method, 
     ROW_NUMBER() OVER (PARTITION BY Customer ORDER BY Customer) 
    FROM 
     @tbl 
     /* This table holds source data. I ommited declaring and inserting 
     data into it because that's not important. */ 
) 
, CTE2 (PurchaseID, Customer, Product, Method, RowID) 
AS 
(
    SELECT 
     PurchaseID, Customer, 
     CONVERT(VARCHAR(MAX), Product), 
     CONVERT(VARCHAR(MAX), Method), 
     1 
    FROM 
     CTE1 
    WHERE 
     RowID = 1 
    UNION ALL 
    SELECT 
     CTE2.PurchaseID, CTE2.Customer, 
     CONVERT(VARCHAR(MAX), CTE2.Product + ',' + CTE1.Product), 
     CONVERT(VARCHAR(MAX), CTE2.Method + ',' + CTE1.Method), 
     CTE2.RowID + 1 
    FROM 
     CTE2 INNER JOIN CTE1 
      ON CTE2.Customer = CTE1.Customer 
      AND CTE2.RowID + 1 = CTE1.RowID 
) 

SELECT Customer, MAX(Product) AS Products, MAX(Method) AS Methods 
FROM CTE2 
GROUP BY Customer 

Выход:

Customer Products    Methods 
John  Computer,Mouse   Credit,Cash 
Todd  Computer    Credit 
Will  Computer,Mouse,Speaker Credit,Cash,Cash 
+2

Привет, @JamesZ разместил ссылку на [сравнение производительности] (http://sqlperformance.com/2014/08/t-sql-queries/sql-server-grouped-concatenation). Вы можете взглянуть на это. Ваш код работает, но он очень низко работает ** ... – Shnugo

1

Другим решением является метод CLR для группы конкатенации @aaron Бертран сделал сравнение производительности на этом here. Если вы можете развернуть CLR, загрузите скрипт с http://groupconcat.codeplex.com/, который является бесплатным. и все детали указаны в документации. Ваш запрос будет просто изменить в так

SELECT Customer,dbo.GROUP_CONCAT(product),dbo.GROUP_CONCAT(method) 
FROM Purchases 
GROUP BY Customer 

Этот запрос является коротким, легко запомнить и использовать метод XML также делает работу, но помня код немного сложно (по крайней мере для меня), и ползет в проблема, как XML-правофикация, которую можно решить уверенно, и некоторые подводные камни также описаны в его блоге.

Также с точки зрения представления с использованием.запрос занимает много времени. У меня были те же проблемы с производительностью. Надеюсь, вы можете найти этот вопрос, который я задал здесь в https://dba.stackexchange.com/questions/125771/multiple-column-concatenation. проверить версию 2, предоставленную kenneth fisher, вложенным методом конкатенации xml или непривилегированным/сводным методом, предложенным spaggettidba.

Смежные вопросы