web-dev-qa-db-ja.com

行値の連結T-SQL

レポートのデータをまとめようとしていますが、いずれかのテーブルの行の値を連結する必要があります。基本的なテーブル構造は次のとおりです。

レビュー

 ReviewID  
 ReviewDate  

レビュアー

 ReviewerID  
 ReviewID  
 UserID  

ユーザー

UserID  
FName  
LName  

これはM:Mの関係です。各レビューには多くのレビューアを含めることができます。各ユーザーは多くのレビューに関連付けることができます。

基本的に、確認したいのは、Reviews.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 これを行う方法を説明した記事を見つけましたが、これらのほとんどは複数ではなく1つのテーブルのみを扱っているようです。残念ながら、私の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;

誰も私にこれを手に入れることができますか?どんな助けも大歓迎です!

33
Sesame

これを見てください

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
33
Adriaan Stander

これを行うには、UDFを必要としないさらに簡単な方法があります。

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

同様の問題があり、15分間コードで遊んだ後、甘い解決策が見つかりました

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

結果をvalue1、value2、value3、value4として返します

楽しむ;)

10
Talha

SQL Server 2017には STRING_AGG があり、指定されたセパレーターを使用して複数の文字列を1つに集約します。

7
John

説明したように、ロールアップデータを処理した3つの方法があります。1。カーソルを使用、2。UDFを使用、または3.カスタム集計(.NET CLRで記述)を使用します。
カーソルとUDFはかなり遅いです。 (行ごとに約0.1秒)。 CLRカスタム集計は驚くほど高速です。 (行ごとに約0.001秒)

Microsoftは、SQL 2005のSDKの一部としてコードを出荷します(希望どおりに実行します)。インストールしている場合、このフォルダーでコードを見つけることができるはずです: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;

上に示したような結果セットを取得します

6
TimG
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,
5
pradeep

SQL Server 2017から、 STRING_AGG と呼ばれる新しいT-SQL関数があります。
これは、文字列式の値を連結し、それらの間にセパレータ値を配置する新しい集計関数です。
区切り文字は文字列の最後に追加されません。

例:

SELECT STRING_AGG ( ISNULL(FirstName,'N/A'), ',') AS csv 
FROM Person.Person; 

結果セット:

John,N/A,Mike,Peter,N/A,N/A,Alice,Bob
4
Wael Ali
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;
3
JeffO

[〜#〜] udf [〜#〜] は、これを解決するのに適切な方法です。

Int param(製品ID)を取り、文字列(製品に関連付けられた名前の連結)を返すT-SQL関数(UDF)を定義するだけです。メソッドの名前がGetProductNamesの場合、クエリは次のようになります。

SELECT p1.CategoryId, dbo.GetProductNames(p1.CategoryId)
FROM Northwind.dbo.Products p1
GROUP BY CategoryId
3
Paul Sasik

これを試して:

 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
Charles Bretana

データをダンプする一時テーブルを作成します。次に、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
2
Bradley

group_concatの機能(mysqlから)が必要なようです。これは別のテストデータセットについてここで対処されています。 1つの列に複数の値を返す方法(T-SQL)?

2
Peter Carrero
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
2
prathmanu
STRING_AGG ( expression, separator ) [ <order_clause> ]

<order_clause> ::=   
    WITHIN GROUP ( ORDER BY <order_by_expression_list> [ ASC | DESC ] )

SQLサーバーの文字列集計関数を探してStackoverflowに来ました。

関連する質問は閉じられており、この質問の複製としてマークされているため、ここで回答するか、まったく回答しないようにします。

詳細については、 https://docs.Microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql?view=sql-server-2017 を参照してください。

0
Ivan

アイテムの数が少ない場合、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
0
Igor Krupitsky