2010-06-30 21 views
5

Nói rằng tôi có bảng sau:Có thể ghép các giá trị cột vào một chuỗi bằng CTE không?

id|myId|Name 
------------- 
1 | 3 |Bob 
2 | 3 |Chet 
3 | 3 |Dave 
4 | 4 |Jim 
5 | 4 |Jose 
------------- 

Có thể sử dụng một CTE đệ quy để tạo đầu ra sau đây:

3 | Bob, Chet, Date 
4 | Jim, Jose 

tôi đã chơi đùa với nó một chút nhưng có không có thể làm cho nó hoạt động. Tôi sẽ làm tốt hơn bằng cách sử dụng một kỹ thuật khác?

+0

bản sao có thể có của [SQL Server: Tôi có thể Comma phân định nhiều hàng vào một cột không?] (Http://stackoverflow.com/questions/2046037/sql-server-can-i-comma-delimit-multiple-rows- thành một cột) –

+0

Bản sao bạn đề xuất không bao gồm sử dụng CTE để thực hiện việc này. –

Trả lời

6

Tôi không đề xuất điều này, nhưng tôi đã cố gắng giải quyết.

Bảng:

CREATE TABLE [dbo].[names](
    [id] [int] NULL, 
    [myId] [int] NULL, 
    [name] [char](25) NULL 
) ON [PRIMARY] 

dữ liệu:

INSERT INTO names values (1,3,'Bob') 
INSERT INTO names values 2,3,'Chet') 
INSERT INTO names values 3,3,'Dave') 
INSERT INTO names values 4,4,'Jim') 
INSERT INTO names values 5,4,'Jose') 
INSERT INTO names values 6,5,'Nick') 

Query:

WITH CTE (id, myId, Name, NameCount) 
    AS (SELECT id, 
       myId, 
       Cast(Name AS VARCHAR(225)) Name, 
       1       NameCount 
     FROM (SELECT Row_number() OVER (PARTITION BY myId ORDER BY myId) AS id, 
         myId, 
         Name 
       FROM names) e 
     WHERE id = 1 
     UNION ALL 
     SELECT e1.id, 
       e1.myId, 
       Cast(Rtrim(CTE.Name) + ',' + e1.Name AS VARCHAR(225)) AS Name, 
       CTE.NameCount + 1          NameCount 
     FROM CTE 
       INNER JOIN (SELECT Row_number() OVER (PARTITION BY myId ORDER BY myId) AS id, 
            myId, 
            Name 
          FROM names) e1 
        ON e1.id = CTE.id + 1 
        AND e1.myId = CTE.myId) 
SELECT myID, 
     Name 
FROM (SELECT myID, 
       Name, 
       (Row_number() OVER (PARTITION BY myId ORDER BY namecount DESC)) AS id 
     FROM CTE) AS p 
WHERE id = 1 

Theo yêu cầu, đây là phương pháp XML:

SELECT myId, 
     STUFF((SELECT ',' + rtrim(convert(char(50),Name)) 
     FROM namestable b 
     WHERE a.myId = b.myId 
     FOR XML PATH('')),1,1,'') Names 
FROM namestable a 
GROUP BY myId 
+0

Trong khi điều này đạt được kết quả cuối cùng, nó không giải quyết được câu hỏi của tôi về việc thực hiện nó với một CTE. –

+0

Đã chỉnh sửa để trả lời câu hỏi thực tế lần này! – Kenneth

+0

Dấu kiểm và +1! Điều đó trông HIDEOUS, cảm ơn cho figuring nó ra. Tôi sẽ gắn bó với phương pháp STUFF ... Bất kỳ cơ hội nào bạn có thể đăng rằng ngoài phương pháp này? –

2

Một CTE chỉ là một bảng dẫn xuất được tôn vinh với một số tính năng bổ sung (như đệ quy). Câu hỏi là, bạn có thể sử dụng đệ quy để làm điều này? Có lẽ, nhưng nó sử dụng một tuốc nơ vít để đập trong một móng tay. Phần tốt đẹp về việc thực hiện đường dẫn XML (xem trong câu trả lời đầu tiên) là nó sẽ kết hợp nhóm cột MyId với chuỗi nối.

Bạn sẽ nối một danh sách các chuỗi bằng CTE bằng cách nào? Tôi không nghĩ đó là mục đích của nó.

0

CTE chỉ là một mối quan hệ được tạo tạm thời (các bảng và khung nhìn là cả hai quan hệ) chỉ tồn tại cho "cuộc sống" của truy vấn hiện tại.

Tôi đã chơi với tên CTE và tên trường. Tôi thực sự không thích sử dụng lại các tên trường như id ở nhiều nơi; Tôi có xu hướng nghĩ rằng những người khó hiểu. Và vì chỉ sử dụng cho tên.id là ORDER BY trong câu lệnh ROW_NUMBER() đầu tiên, tôi không sử dụng lại nó trong tương lai.

WITH namesNumbered as (
    select myId, Name, 
     ROW_NUMBER() OVER (
      PARTITION BY myId 
      ORDER BY id 
     ) as nameNum 
    FROM names 
) 
, namesJoined(myId, Name, nameCount) as (
    SELECT myId, 
     Cast(Name AS VARCHAR(225)), 
     1 
    FROM namesNumbered nn1 
    WHERE nameNum = 1 
    UNION ALL 
    SELECT nn2.myId, 
     Cast(
      Rtrim(nc.Name) + ',' + nn2.Name 
      AS VARCHAR(225) 
     ), 
     nn.nameNum 
    FROM namesJoined nj 
    INNER JOIN namesNumbered nn2 ON nn2.myId = nj.myId 
     and nn2.nameNum = nj.nameCount + 1 
) 
SELECT myId, Name 
FROM (
    SELECT myID, Name, 
     ROW_NUMBER() OVER (
      PARTITION BY myId 
      ORDER BY nameCount DESC 
     ) AS finalSort 
    FROM namesJoined 
) AS tmp 
WHERE finalSort = 1 

Các CTE đầu tiên, namesNumbered, trả về hai lĩnh vực chúng ta quan tâm và một giá trị sắp xếp; chúng tôi không thể chỉ sử dụng tên.id cho điều này vì chúng tôi cần, cho mỗi giá trị myId, để có giá trị 1, 2, .... tên.id sẽ có 1, 2 ... cho myId = 1 nhưng nó sẽ có giá trị bắt đầu cao hơn cho các giá trị myId tiếp theo.

CTE thứ hai, tên Đã đặt trước, phải có tên trường được chỉ định trong chữ ký CTE vì nó sẽ được đệ quy. Trường hợp cơ sở (một phần trước UNION ALL) cung cấp cho chúng tôi hồ sơ nơi nameNum = 1. Chúng tôi phải CAST() Tên trường vì nó sẽ phát triển với các lần truy cập tiếp theo; chúng ta cần đảm bảo rằng chúng ta CAST() nó đủ lớn để xử lý bất kỳ đầu ra nào; chúng tôi luôn có thể TRIM() sau này, nếu cần. Chúng tôi không phải chỉ định bí danh cho các trường vì chữ ký CTE cung cấp các bí danh đó.Trường hợp đệ quy (sau UNION ALL) tham gia CTE hiện tại với CTE trước, đảm bảo rằng các lần truy cập tiếp theo sử dụng các giá trị tênNum cao hơn bao giờ hết. Chúng tôi cần phải TRIM() các lần lặp trước của Tên, sau đó thêm dấu phẩy và Tên mới. Kết quả sẽ được, rõ ràng, CAST() ed đến một trường lớn hơn.

Truy vấn chính thức lấy chỉ các lĩnh vực chúng tôi quan tâm (myid, Tên) và, trong subquery, châm chọc tái sắp xếp các hồ sơ để các namesJoined.nameCount giá trị cao nhất sẽ nhận được 1 như giá trị finalSort. Sau đó, chúng tôi yêu cầu mệnh đề WHERE chỉ cung cấp cho chúng tôi một bản ghi này (cho mỗi giá trị myId).

Có, tôi đã đặt bí danh truy vấn con là tmp, tức là bạn có thể nhận được thông tin chung. Hầu hết các công cụ SQL yêu cầu bạn cung cấp cho một truy vấn phụ một bí danh, ngay cả khi đó là mối quan hệ duy nhất có thể nhìn thấy tại thời điểm đó.

Các vấn đề liên quan