2009-12-09 24 views
30

我正在尝试将某些报表的数据放在一起,并需要连接其中一个表的行值。这是基本的表结构:连接行值T-SQL

评论

ReviewID 
ReviewDate 

评审

ReviewerID 
ReviewID 
UserID 

用户

UserID 
FName 
LName 

这是一个M:M的关系。每个评论可以有许多评论者;每个用户可以与许多评论相关联。

基本上,我想看到的仅仅是Review.ReviewID,Reviews.ReviewDate和该评论的所有关联用户(逗号分隔)的FName的连接字符串。

相反的:

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 

显示此:

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

我发现this文章描述了一些方法可以做到这一点,但大多数的这些似乎只与一个表,而不是多重处理;不幸的是,我的SQL-fu不够强大,无法适应我的情况。我特别感兴趣的是使用FOR XML PATH()的那个网站上的例子,因为它看起来是最干净和最直接的。

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; 

有谁能帮我一个忙吗?任何帮助将不胜感激!

+0

类似http://stackoverflow.com/questions/122942/how-to-return-multiple -values-in-one-column-t-sql和http://stackoverflow.com/questions/451415/simulating-groupconcat-mysql-function-in-ms-sql-server-2005 – VolkerK 2009-12-09 16:32:01

回答

32

看一看这个

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

这不能正确处理XML特殊像'>'和'&'这样的字符。所以如果数据中包含“弗兰克和比尔”,你会在结果集中获得“弗兰克&比尔”。有一个漂亮的方式来处理此问题,请参阅:http://stackoverflow.com/questions/5031204/does-t-sql-have-an-aggregate-function-to-concatenate-strings/5031297#5031297 – 2011-02-17 16:20:48

3

一个UDF将是解决这个的确定方式。

只需定义一个接受int参数(产品ID)并返回一个字符串(与产品关联的名称串联)的T-SQL函数(UDF)。如果您的方法名称是GetProductNames,那么您的查询可能看起来像这样:

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

以及如何那个UDF看起来像?我不认为这是必要的,真的。 – 2009-12-09 16:43:33

+2

@marc:true。 UDF仅仅是解决这个问题的一种方法。我认为这是一个很好的解决方案,以呈现给SQL n00b。 – 2009-12-09 16:58:03

+0

UDF的问题是它需要使用动态SQL或存在每个查询类型.. SQL服务器支持自定义CLR AGGREGATE函数..以使用额外的CLR程序集为代价.. – 2012-07-11 20:31:12

6

我已经处理了滚动数据的3种方式,如您所描述的,1.使用游标,2.使用UDF或3.使用a自定义聚合(用.NET CLR编写)。
光标和UDF非常缓慢。 (每行约0.1秒)。 CLR自定义聚合速度惊人地快。 (大约每秒0.001秒)

作为SQL 2005 SDK的一部分,Microsoft提供了代码(用于完成您想要的功能)。如果已安装它,则应该能够在此文件夹中找到代码: C:\ Program Files \ Microsoft SQL Server \ 90 \ Samples \ Engine \ Programmability \ CLR \ StringUtilities。 您可能还想在MSDN中阅读这篇文章。它谈论安装自定义聚合,并在启用: http://msdn.microsoft.com/en-us/library/ms161551(SQL.90).aspx

一旦你编译和安装自定义聚合,你应该能够查询是这样的:

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; 

,并得到一个结果集像你这样的表现(上图)

+1

+1 ..不幸的是它需要messing与CLR的东西。 (如果UDF可以成为自定义AGGREGATE函数的目标,那将会很不错: - /) – 2012-07-11 20:32:11

3

尝试这种情况:

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

创建一个临时表来转储数据。然后使用FOR XML PATH方法。外部查询需要修剪掉列表中的最后一个逗号。

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

原来还有一个更简单的方法来做到这一点,它不需要一个UDF:

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

这是我见过的最干净的方法,它不需要UDF。我的情况非常快。谢谢! – AaronShockley 2013-11-27 23:55:36

+0

我一直在寻找这片纯金的片段。谢谢。 – 2017-08-10 20:14:18

10

有类似的问题,并与代码打15分钟后发现了一个甜蜜的解决方案

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

返回结果为值1,值2,值3,值4

享受)

+0

干得好 - 非常整洁! @芝麻,我会建议改变接受的答案! – 2015-06-25 12:09:11

+2

这不受Microsoft的支持,可能会产生意想不到的结果。请参阅https://www.simple-talk.com/sql/t-sql-programming/concatenating-row-values-in-transact-sql/或我的博客http://marc.durdin.net/2015/07/在sql-server-or-undefined-behavior-by-design /中进行串联 - 字符串的更多讨论。 – 2015-07-15 00:27:37

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 

结果

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年现在有STRING_AGG其聚集多个字符串为一个使用一个给定的分离器。

0

当项目的数量小,这个可以用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