2009-12-09 47 views
30

Tôi đang cố gắng thu thập một số dữ liệu cho một báo cáo và cần phải nối các giá trị hàng của một trong các bảng. Dưới đây là cấu trúc bảng cơ bản:Ghép nối các giá trị hàng T-SQL

Nhận xét

ReviewID 
ReviewDate 

phản biện

ReviewerID 
ReviewID 
UserID 

Người dùng

UserID 
FName 
LName 

Đây là mối quan hệ M: M. Mỗi Đánh giá có thể có nhiều Người đánh giá; mỗi Người dùng có thể được liên kết với nhiều Bài đánh giá.

Về cơ bản, tất cả những gì tôi muốn xem là Reviews.ReviewID, Reviews.ReviewDate và chuỗi nối của FName của tất cả người dùng được liên kết cho Đánh giá đó (được phân cách bằng dấu phẩy).

Thay vì:

ReviewID---ReviewDate---User 
1----------12/1/2009----Bob 
1----------12/1/2009----Joe 
1----------12/1/2009----Frank 
2----------12/9/2009----Sue 
2----------12/9/2009----Alice 

hiển thị này:

ReviewID---ReviewDate----Users 
1----------12/1/2009-----Bob, Joe, Frank 
2----------12/9/2009-----Sue, Alice 

Tôi đã tìm thấy this bài viết mô tả một số cách để làm điều này, nhưng hầu hết trong số này dường như chỉ đối phó với một bảng, không nhiều ; Thật không may, SQL-fu của tôi không đủ mạnh để thích ứng với những hoàn cảnh của tôi. Tôi đặc biệt quan tâm đến ví dụ trên trang web sử dụng FOR XML PATH() vì nó trông sạch sẽ và thẳng tiến nhất.

SELECT p1.CategoryId, 
(SELECT ProductName + ', ' 
    FROM Northwind.dbo.Products p2 
    WHERE p2.CategoryId = p1.CategoryId 
    ORDER BY ProductName FOR XML PATH('') 
) AS Products 
FROM Northwind.dbo.Products p1 
GROUP BY CategoryId; 

Có ai có thể cho tôi một tay với điều này không? Mọi sự trợ giúp sẽ rất được trân trọng!

+0

Tương tự như http://stackoverflow.com/questions/122942/how-to-return-multiple -giá trị-trong-một-cột-t-sql và http://stackoverflow.com/questions/451415/simulating-groupconcat-mysql-function-in-ms-sql-server-2005 – VolkerK

Trả lời

32

Có xem xét này

DECLARE @Reviews TABLE(
     ReviewID INT, 
     ReviewDate DATETIME 
) 

DECLARE @Reviewers TABLE(
     ReviewerID INT, 
     ReviewID INT, 
     UserID INT 
) 

DECLARE @Users TABLE(
     UserID INT, 
     FName VARCHAR(50), 
     LName VARCHAR(50) 
) 

INSERT INTO @Reviews SELECT 1, '12 Jan 2009' 
INSERT INTO @Reviews SELECT 2, '25 Jan 2009' 

INSERT INTO @Users SELECT 1, 'Bob', '' 
INSERT INTO @Users SELECT 2, 'Joe', '' 
INSERT INTO @Users SELECT 3, 'Frank', '' 
INSERT INTO @Users SELECT 4, 'Sue', '' 
INSERT INTO @Users SELECT 5, 'Alice', '' 

INSERT INTO @Reviewers SELECT 1, 1, 1 
INSERT INTO @Reviewers SELECT 2, 1, 2 
INSERT INTO @Reviewers SELECT 3, 1, 3 
INSERT INTO @Reviewers SELECT 4, 2, 4 
INSERT INTO @Reviewers SELECT 5, 2, 5 

SELECT *, 
     ( 
      SELECT u.FName + ',' 
      FROM @Users u INNER JOIN 
        @Reviewers rs ON u.UserID = rs.UserID 
      WHERE rs.ReviewID = r.ReviewID 
      FOR XML PATH('') 
     ) AS Products 
FROM @Reviews r 
+5

điều này không xử lý đúng XML đặc biệt các ký tự như '>' và '&'. Vì vậy, nếu dữ liệu chứa 'Frank & Bill' bạn sẽ nhận được' Frank & Bill' trong tập kết quả. Có một cách tiện lợi để xử lý việc này, xem: http://stackoverflow.com/questions/5031204/does-t-sql-have-an-aggregate-function-to-concatenate-strings/5031297#5031297 –

3

Một UDF sẽ là một cách ok để giải quyết này.

Chỉ cần xác định hàm T-SQL (UDF) lấy tham số int (ID sản phẩm) và trả về một chuỗi (nối các tên được kết hợp với sản phẩm.) Nếu tên của phương thức là GetProductNames thì truy vấn của bạn có thể trông như thế này :

SELECT p1.CategoryId, dbo.GetProductNames(p1.CategoryId) 
FROM Northwind.dbo.Products p1 
GROUP BY CategoryId 
+0

và cách thức UDF trông như thế nào? Tôi không nghĩ rằng điều này là cần thiết, thực sự. –

+2

@marc: đúng. một UDF chỉ đơn thuần là một trong hàng trăm cách để giải quyết vấn đề này. tôi nghĩ rằng nó là một giải pháp tốt để trình bày cho một SQL n00b. –

+0

Vấn đề với UDF là cần phải sử dụng SQL động hoặc tồn tại cho mỗi loại truy vấn .. Hỗ trợ máy chủ SQL tùy chỉnh các chức năng AGRREGATE CLR .. với chi phí sử dụng một hội đồng CLR bổ sung .. –

6

Có 3 cách tôi đã xử lý dữ liệu cuộn lên, như bạn đã mô tả, 1.sử dụng con trỏ, 2.sử dụng UDF hoặc 3. sử dụng Tổng hợp tùy chỉnh (được viết bằng .NET CLR).
Con trỏ và UDF khá chậm. (khoảng 0,1 giây mỗi hàng). Tổng hợp tùy chỉnh CLR nhanh đáng ngạc nhiên. (khoảng 0,001 giây mỗi hàng)

Microsoft gửi mã (để thực hiện chính xác những gì bạn muốn) như một phần của SDK cho SQL 2005. Nếu bạn đã cài đặt, bạn sẽ có thể tìm thấy mã trong thư mục này: C: \ Program Files \ Microsoft SQL Server \ 90 \ mẫu \ Engine \ Programmability \ CLR \ StringUtilities. Bạn cũng có thể muốn bài viết này trong MSDN.Nó nói về việc cài đặt tổng hợp tùy chỉnh và tạo điều kiện cho nó: http://msdn.microsoft.com/en-us/library/ms161551(SQL.90).aspx

Khi bạn biên dịch và cài đặt tổng hợp tùy chỉnh, bạn sẽ có thể truy vấn như thế này:

SELECT Reviews.ReviewID, ReviewDate, dbo.StringUtilities.Concat(FName) AS [User] 
FROM Reviews INNER JOIN Reviewers ON Reviews.ReviewID = Reviewers.ReviewID 
    INNER JOIN Users ON Reviews.UserID = Users.UserID 
GROUP BY ReviewID, ReviewDate; 

và nhận được một kết quả thiết lập như bạn thấy (ở trên)

+1

+1 .. không may nó đòi hỏi rối xung quanh với các công cụ CLR. (Sẽ thật tuyệt nếu một UDF có thể là đích của một hàm AGGREGATE tùy chỉnh: - /) –

3

Hãy thử điều này:

Declare @Revs Table 
(RevId int Priimary Key Not Null, 
    RevDt DateTime Null, 
    users varChar(1000) default '') 

Insert @Revs (RevId, RevDt) 
Select Distinct ReviewId, ReviewDate 
From Reviews 
Declare @UId Integer 
Set @Uid = 0 
While Exists (Select * From Users 
       Where UserID > @Uid) 
Begin 
    Update @Revs Set 
     users = users + u.fName + ', ' 
    From @Revs R 
     Join Reviewers uR On ur.ReviewId = R.RId 
     Join users u On u.UserId = uR.UserId 
    Where uR.UserId = @UId 
    Select @Uid = Min(UserId) 
    From users 
    Where UserId > @UId 
    End 
    Select * From @Revs 
3
Select R.ReviewID, ReviewDate 
, (Select FName + ', ' 
    from Users 
    where UserID = R.UserID 
    order by FName FOR XML PATH(') 
) as [Users] 
from Reviews 
inner join Reviewers AS R 
    On Reviews.ReviewID = R.ReviewID 
Group By R.ReviewID, ReviewDate; 
2

Tạo bảng tạm thời để kết xuất dữ liệu của bạn. Sau đó, sử dụng phương thức FOR XML PATH. Truy vấn bên ngoài là cần thiết để cắt dấu phẩy cuối cùng khỏi danh sách.

CREATE TABLE #ReviewInfo (
ReviewId INT, 
ReviewDate DATETIME, 
Reviewer VARCHAR(1000)) 

INSERT INTO #ReviewInfo (ReviewId, ReviewDate, Reviewer) 
SELECT r.ReviewId, r.ReviewDate, u.FName 
FROM Reviews r 
JOIN Reviewers rs ON r.ReviewId = rs.ReviewId 
JOIN Users u ON u.UserId = rs.UserId 

SELECT ReviewId, ReviewDate, LEFT(Users, LEN(Users)-1) 
FROM (
SELECT ReviewId, ReviewDate, 
(
    SELECT Reviewer + ', ' 
    FROM #ReviewInfo ri2 
    WHERE ri2.ReviewId = ri1.ReviewId 
    ORDER BY Reviewer 
    FOR XML PATH('') 
) AS Users 
FROM #ReviewInfo ri1 
GROUP BY ReviewId, ReviewDate 
) a 

DROP TABLE #ReviewInfo 
20

Hóa ra có một cách dễ dàng hơn để làm điều này mà không đòi hỏi một UDF:

select replace(replace(replace((cast((
     select distinct columnName as X 
     from tableName 
     for xml path('')) as varchar(max))), 
    '</X><X>', ', '),'<X>', ''),'</X>','') 
+1

Đây là cách sạch nhất mà tôi đã thấy để làm điều này mà không đòi hỏi một UDF. Và nó rất nhanh trong trường hợp của tôi. Cảm ơn! – AaronShockley

+0

Tôi đã tìm kiếm khắp nơi cho đoạn vàng nguyên chất này. Cảm ơn. –

10

Đã vấn đề tương tự và tìm ra giải pháp ngọt ngào sau khi chơi với mã cho 15 phút

declare @result varchar(1000) 
select @result = COALESCE(@result+','+A.col1, A.col1) 
       FROM ( select col1 
         from [table] 
       ) A 
select @result 

trả về kết quả như value1, value2, value3, VALUE4

Thưởng thức;)

+0

Thực hiện tốt - rất gọn gàng! @Sameame, tôi khuyên bạn nên thay đổi câu trả lời được chấp nhận cho điều này! –

+2

Điều này không được hỗ trợ bởi Microsoft và có thể cho kết quả không mong muốn.Xem https://www.simple-talk.com/sql/t-sql-programming/concatenating-row-values-in-transact-sql/ hoặc blog của tôi http://marc.durdin.net/2015/07/ concatenating-strings-in-sql-server-hoặc-undefined-behavior-by-design/để thảo luận thêm. –

5
select p1.Availability ,COUNT(*), 
(select name+',' from AdventureWorks2008.Production.Location p2 where 
p1.Availability=p2.Availability for XML path(''),type).value('.','varchar(max)') 
as Name from AdventureWorks2008.Production.Location p1 group by Availability 

quả

Availability COUNT  Name 
--------------------------------------------------------------------------------- 
0.00 7 Tool Crib,Sheet Metal Racks,Paint Shop,Paint Storage,Metal 
        Storage,Miscellaneous Storage,Finished Goods Storage, 
80.00 1 Specialized Paint, 
96.00 1 Frame Forming, 
108.00 1 Frame Welding, 
120.00 4 Debur and Polish,Paint,Subassembly,Final Assembly, 
2
select 
     p1.Availability, 
     COUNT(*), 
     (
      select name+',' 
      from AdventureWorks2008.Production.Location p2 
      where p1.Availability=p2.Availability 
      for XML path(''),type 
    ).value('.','varchar(max)') as Name 
from AdventureWorks2008.Production.Location p1 
group by Availability 
4

SQLServer 2017 bây giờ đã STRING_AGG rằng tập hợp nhiều chuỗi thành một bằng cách sử dụng dấu phân cách nhất định.

0

Khi số lượng các mặt hàng nhỏ này có thể được thực hiện bằng ROW_NUMBER() OVER PARTITION BY:

declare @t table (col1 int, col2 varchar) 
insert into @t VALUES (1,'A') 
insert into @t VALUES (1,'B') 
insert into @t VALUES (1,'C') 
insert into @t VALUES (1,'D') 
insert into @t VALUES (1,'E') 
insert into @t VALUES (2,'X') 
insert into @t VALUES (3,'Y') 

select col1, 
    MAX(CASE seq WHEN 1 THEN  col2 ELSE '' END) + 
    MAX(CASE seq WHEN 2 THEN ', ' + col2 ELSE '' END) + 
    MAX(CASE seq WHEN 3 THEN ', ' + col2 ELSE '' END) + 
    MAX(CASE seq WHEN 4 THEN ', ' + col2 ELSE '' END) + 
    MAX(CASE seq WHEN 5 THEN ',...' ELSE '' END) 
    as col2 
from (
    select col1, col2, ROW_NUMBER() OVER (PARTITION BY col1 ORDER BY col2) seq 
    from @t 
    group by col1, col2 
) x 
group by col1 
Các vấn đề liên quan