115

異なる行の文字列を1つの行に集約する方法を見つけています。いろいろな場所でやってみたいと思っているので、これを簡単にする機能があるといいですね。COALESCE私はとを使用して解決策を試しましFOR XMLたが、それらは私のためにそれをカットしません。

文字列の集計は次のようになります。

id | Name                    Result: id | Names
-- - ----                            -- - -----
1  | Matt                            1  | Matt, Rocks
1  | Rocks                           2  | Stylus
2  | Stylus

との代わりにCLR定義の集計関数を調べましたが、SQL AzureはCLR定義のものをサポートしていないようです。これは、使用できると多くの問題が解決されることがわかっているため、私にとっては苦痛です。私にとっての問題。COALESCEFOR XML

自分のものを集約するために使用できる、考えられる回避策、または同様に最適な方法(CLRほど最適ではないかもしれませんが、取得できるものを使用します)はありますか?

4

7 に答える 7

75

解決

最適の定義はさまざまですが、通常のTransactSQLを使用してさまざまな行の文字列を連結する方法を次に示します。これはAzureで正常に機能するはずです。

;WITH Partitioned AS
(
    SELECT 
        ID,
        Name,
        ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name) AS NameNumber,
        COUNT(*) OVER (PARTITION BY ID) AS NameCount
    FROM dbo.SourceTable
),
Concatenated AS
(
    SELECT 
        ID, 
        CAST(Name AS nvarchar) AS FullName, 
        Name, 
        NameNumber, 
        NameCount 
    FROM Partitioned 
    WHERE NameNumber = 1

    UNION ALL

    SELECT 
        P.ID, 
        CAST(C.FullName + ', ' + P.Name AS nvarchar), 
        P.Name, 
        P.NameNumber, 
        P.NameCount
    FROM Partitioned AS P
        INNER JOIN Concatenated AS C 
                ON P.ID = C.ID 
                AND P.NameNumber = C.NameNumber + 1
)
SELECT 
    ID,
    FullName
FROM Concatenated
WHERE NameNumber = NameCount

説明

このアプローチは、次の3つのステップに要約されます。

  1. 連結の必要に応じて、行に番号を付けOVERPARTITIONグループ化して順序付けします。結果はPartitionedCTEです。後で結果をフィルタリングするために、各パーティションの行数を保持します。

  2. 再帰CTE(Concatenated)を使用して、行番号(列)を反復処理し、列に値をNameNumber追加します。NameFullName

  3. が最も高い結果を除くすべての結果を除外しますNameNumber

このクエリを予測可能にするには、グループ化(たとえば、シナリオでは同じ行IDが連結される)と並べ替え(連結の前に文字列をアルファベット順に並べ替えるだけだと想定)の両方を定義する必要があることに注意してください。

次のデータを使用して、SQLServer2012でソリューションをすばやくテストしました。

INSERT dbo.SourceTable (ID, Name)
VALUES 
(1, 'Matt'),
(1, 'Rocks'),
(2, 'Stylus'),
(3, 'Foo'),
(3, 'Bar'),
(3, 'Baz')

クエリ結果:

ID          FullName
----------- ------------------------------
2           Stylus
3           Bar, Baz, Foo
1           Matt, Rocks
于 2012-12-03T10:50:07.460 に答える
59

以下のようなFORXMLPATHを使用するメソッドは本当に遅いですか?Itzik Ben-Ganは、この方法が彼のT-SQL Queryingの本で優れたパフォーマンスを発揮していると書いています(私の見解では、Ben-Gan氏は信頼できる情報源です)。

create table #t (id int, name varchar(20))

insert into #t
values (1, 'Matt'), (1, 'Rocks'), (2, 'Stylus')

select  id
        ,Names = stuff((select ', ' + name as [text()]
        from #t xt
        where xt.id = t.id
        for xml path('')), 1, 2, '')
from #t t
group by id
于 2012-12-08T22:59:32.140 に答える
50

STRING_AGG()SQL Server 2017、Azure SQL、およびPostgreSQLの場合: https ://www.postgresql.org/docs/current/static/functions-aggregate.html
https://docs.microsoft.com/en-us/sql/t- sql / Functions / string-agg-transact-sql

GROUP_CONCAT()MySQLの
http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_group-concat

(@Brianjordenと@milanioのAzureアップデートに感謝します)

サンプルコード:

select Id
, STRING_AGG(Name, ', ') Names 
from Demo
group by Id

SQLフィドル:http ://sqlfiddle.com/#!18/89251/1

于 2016-12-13T15:19:50.123 に答える
26

@sergeの答えは正しいですが、私は彼の方法の時間消費をxmlpathと比較し、xmlpathが非常に高速であることがわかりました。比較コードを書いて、自分で確認できます。これは@sergeの方法です:

DECLARE @startTime datetime2;
DECLARE @endTime datetime2;
DECLARE @counter INT;
SET @counter = 1;

set nocount on;

declare @YourTable table (ID int, Name nvarchar(50))

WHILE @counter < 1000
BEGIN
    insert into @YourTable VALUES (ROUND(@counter/10,0), CONVERT(NVARCHAR(50), @counter) + 'CC')
    SET @counter = @counter + 1;
END

SET @startTime = GETDATE()

;WITH Partitioned AS
(
    SELECT 
        ID,
        Name,
        ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name) AS NameNumber,
        COUNT(*) OVER (PARTITION BY ID) AS NameCount
    FROM @YourTable
),
Concatenated AS
(
    SELECT ID, CAST(Name AS nvarchar) AS FullName, Name, NameNumber, NameCount FROM Partitioned WHERE NameNumber = 1

    UNION ALL

    SELECT 
        P.ID, CAST(C.FullName + ', ' + P.Name AS nvarchar), P.Name, P.NameNumber, P.NameCount
    FROM Partitioned AS P
        INNER JOIN Concatenated AS C ON P.ID = C.ID AND P.NameNumber = C.NameNumber + 1
)
SELECT 
    ID,
    FullName
FROM Concatenated
WHERE NameNumber = NameCount

SET @endTime = GETDATE();

SELECT DATEDIFF(millisecond,@startTime, @endTime)
--Take about 54 milliseconds

そしてこれはxmlpathの方法です:

DECLARE @startTime datetime2;
DECLARE @endTime datetime2;
DECLARE @counter INT;
SET @counter = 1;

set nocount on;

declare @YourTable table (RowID int, HeaderValue int, ChildValue varchar(5))

WHILE @counter < 1000
BEGIN
    insert into @YourTable VALUES (@counter, ROUND(@counter/10,0), CONVERT(NVARCHAR(50), @counter) + 'CC')
    SET @counter = @counter + 1;
END

SET @startTime = GETDATE();

set nocount off
SELECT
    t1.HeaderValue
        ,STUFF(
                   (SELECT
                        ', ' + t2.ChildValue
                        FROM @YourTable t2
                        WHERE t1.HeaderValue=t2.HeaderValue
                        ORDER BY t2.ChildValue
                        FOR XML PATH(''), TYPE
                   ).value('.','varchar(max)')
                   ,1,2, ''
              ) AS ChildValues
    FROM @YourTable t1
    GROUP BY t1.HeaderValue

SET @endTime = GETDATE();

SELECT DATEDIFF(millisecond,@startTime, @endTime)
--Take about 4 milliseconds
于 2014-02-19T09:14:48.240 に答える
19

更新:Ms SQL Server 2017以降、AzureSQLデータベース

使用できます:STRING_AGG

OPの要求の使用法は非常に簡単です。

SELECT id, STRING_AGG(name, ', ') AS names
FROM some_table
GROUP BY id

続きを読む

さて、私の古い無回答は正しく削除されました(下にそのまま残されています)が、将来誰かがここに着陸した場合、良いニュースがあります。また、Azure SQLデータベースでもSTRING_AGG()を実装しています。これにより、この投稿で最初に要求された正確な機能と、ネイティブおよび組み込みのサポートが提供されます。@hrobkyは、これを当時のSQLServer2016の機能として以前に言及しました。

---古い投稿:@hrobkyに直接返信するには評判が十分ではありませんが、STRING_AGGは見栄えがしますが、現在SQL Server2016vNextでのみ使用できます。うまくいけば、AzureSQLDatababseにもすぐに続くでしょう。

于 2016-12-19T14:10:14.767 に答える
4

+ =を使用して、文字列を連結できます。次に例を示します。

declare @test nvarchar(max)
set @test = ''
select @test += name from names

@testを選択すると、すべての名前が連結されて表示されます

于 2018-03-08T15:20:22.670 に答える
2

Sergeの答えは非常に有望であることがわかりましたが、記述どおりのパフォーマンスの問題も発生しました。ただし、一時テーブルを使用し、ダブルCTEテーブルを含まないように再構築した場合、1000の結合レコードのパフォーマンスは1分40秒から1秒未満になりました。これは、古いバージョンのSQLServerでFORXMLを使用せずにこれを行う必要がある人向けです。

DECLARE @STRUCTURED_VALUES TABLE (
     ID                 INT
    ,VALUE              VARCHAR(MAX) NULL
    ,VALUENUMBER        BIGINT
    ,VALUECOUNT         INT
);

INSERT INTO @STRUCTURED_VALUES
SELECT   ID
        ,VALUE
        ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY VALUE) AS VALUENUMBER
        ,COUNT(*) OVER (PARTITION BY ID)    AS VALUECOUNT
FROM    RAW_VALUES_TABLE;

WITH CTE AS (
    SELECT   SV.ID
            ,SV.VALUE
            ,SV.VALUENUMBER
            ,SV.VALUECOUNT
    FROM    @STRUCTURED_VALUES SV
    WHERE   VALUENUMBER = 1

    UNION ALL

    SELECT   SV.ID
            ,CTE.VALUE + ' ' + SV.VALUE AS VALUE
            ,SV.VALUENUMBER
            ,SV.VALUECOUNT
    FROM    @STRUCTURED_VALUES SV
    JOIN    CTE 
        ON  SV.ID = CTE.ID
        AND SV.VALUENUMBER = CTE.VALUENUMBER + 1

)
SELECT   ID
        ,VALUE
FROM    CTE
WHERE   VALUENUMBER = VALUECOUNT
ORDER BY ID
;
于 2018-05-10T05:44:59.943 に答える