2016-06-07 2 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 

이것은 나에게 결과를 주지만 내 관심사는 이것의 속도이다.
언뜻보기에는 세 가지 다른 선택이 여기에 있습니다. 두 개는 각각 구매 항목의 행 수를 곱합니다. 결국 이것은 expenentially 느려질 것입니다.

그래서 더 나은 성능으로 이것을 수행 할 수있는 방법이 있습니까?
집계에 더 많은 열을 추가하고 싶습니다. 모든 열에 대해 STUFF() 블록을 사용해야합니까? 그것은 나를 위해 충분히 빨리 들리지 않습니다.

Siggestions?

+0

데이터를 비정규 화하여 성능이 잠재적 인 문제가 될 수 있습니다. XML 메서드는 데이터를 구분 된 목록으로 역 정규화하는 가장 좋은 방법입니다. –

+0

'for xml path'를 사용할 때 조심하십시오. 데이터에 예를 들어'&'가 있으면 놀라실 것입니다. Aaron Bertrand는 체크 아웃 할 수있는 여러 가지 방법 중 [비교] (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

@Shnugo 감사합니다 :) 매우 감사드립니다. – Devart

1

이 재귀 CTE를 (공통 테이블 식)에 대한 사용 사례 중 하나입니다. 당신은 더 여기 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

또 다른 해결책은 베르트랑이 here의 성능 비교를 수행하고있다 @aaron 그룹 연결에 대한 CLR 방법입니다. 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 에서 제기 한이 질문을 찾을 수 있기를 바랍니다. kenneth fisher가 중첩 된 xml 연결 메소드 또는 spaggettba가 제안한 피벗/피벗 메소드에서 제공하는 버전 2를 확인하십시오.

관련 문제