SQL Serverで複数の行のテキストを単一のテキスト文字列に連結する方法は?
-
10-07-2019 - |
質問
3行の名前を持つデータベーステーブルを検討します。
Peter
Paul
Mary
これを Peter、Paul、Mary
の単一の文字列に変換する簡単な方法はありますか?
解決
SQL Server 2017またはAzureを使用している場合は、 Mathieu Rendaの回答をご覧ください。
1対多の関係で2つのテーブルを結合しようとしたときに、同様の問題が発生しました。 SQL 2005では、 XML PATH
メソッドが行の連結を非常に簡単に処理できることがわかりました。
STUDENTS
SubjectID StudentName
---------- -------------
1 Mary
1 John
1 Sam
2 Alaina
2 Edward
期待していた結果:
SubjectID StudentName
---------- -------------
1 Mary, John, Sam
2 Alaina, Edward
次の T-SQL
を使用しました:
SELECT Main.SubjectID,
LEFT(Main.Students,Len(Main.Students)-1) As "Students"
FROM
(
SELECT DISTINCT ST2.SubjectID,
(
SELECT ST1.StudentName + ',' AS [text()]
FROM dbo.Students ST1
WHERE ST1.SubjectID = ST2.SubjectID
ORDER BY ST1.SubjectID
FOR XML PATH ('')
) [Students]
FROM dbo.Students ST2
) [Main]
最初にコンマを連結し、 substring
を使用して最初のコンマをスキップできる場合、サブクエリを実行する必要がないため、同じことをよりコンパクトに行うことができます:
SELECT DISTINCT ST2.SubjectID,
SUBSTRING(
(
SELECT ','+ST1.StudentName AS [text()]
FROM dbo.Students ST1
WHERE ST1.SubjectID = ST2.SubjectID
ORDER BY ST1.SubjectID
FOR XML PATH ('')
), 2, 1000) [Students]
FROM dbo.Students ST2
他のヒント
この回答は、予期しない結果を返す場合があります一貫した結果を得るには、他の回答で詳しく説明されているFOR XML PATHメソッドのいずれかを使用してください。
COALESCE
を使用:
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
少し説明をしてください(この回答は比較的規則的な見方をしているようです):
- 合体は、実際には2つのことを達成する有用なチートです:
1)空の文字列値で @Names
を初期化する必要はありません。
2)最後に余分なセパレータを取り除く必要はありません。
- 上記のソリューションは、行に NULL Name値がある場合、誤った結果を返します( NULL がある場合、 NULL は
@Names
NULL はその行の後、次の行は空の文字列として最初からやり直します。次の2つの解決策のいずれかで簡単に修正できます。
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
WHERE Name IS NOT NULL
または:
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') +
ISNULL(Name, 'N/A')
FROM People
必要な動作に応じて(最初のオプションは NULL を除外するだけで、2番目のオプションはそれらをマーカーメッセージと共にリストに保持します['N / A'を適切なものに置き換えますあなた])。
MS SQL Serverの XML
data()
コマンドでまだ表示されていないメソッドの1つは次のとおりです。
FNameという1つの列を持つNameListというテーブルを想定します
SELECT FName + ', ' AS 'data()'
FROM NameList
FOR XML PATH('')
戻り値:
"Peter, Paul, Mary, "
余分なコンマのみを処理する必要があります。
編集: @NReilinghのコメントから採用されているように、次の方法を使用して末尾のコンマを削除できます。同じテーブル名と列名を想定:
STUFF(REPLACE((SELECT '#!' + LTRIM(RTRIM(FName)) AS 'data()' FROM NameList
FOR XML PATH('')),' #!',', '), 1, 2, '') as Brands
SQL Server 2017+およびSQL Azure:STRING_AGG
次のバージョンのSQL Serverから始めて、変数やXMLの魔術に頼ることなく、最終的に行を連結できます。
グループ化なし
SELECT STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department;
グループ化:
SELECT GroupName, STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department
GROUP BY GroupName;
グループ化とサブソート付き
SELECT GroupName, STRING_AGG(Name, ', ') WITHIN GROUP (ORDER BY Name ASC) AS Departments
FROM HumanResources.Department
GROUP BY GroupName;
SQL Server 2005
SELECT Stuff(
(SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'')
SQL Server 2016の場合
i.e。
SELECT per.ID,
Emails = JSON_VALUE(
REPLACE(
(SELECT _ = em.Email FROM Email em WHERE em.Person = per.ID FOR JSON PATH)
,'"},{"_":"',', '),'$[0]._'
)
FROM Person per
そして結果は次のようになります
Id Emails
1 abc@gmail.com
2 NULL
3 def@gmail.com, xyz@gmail.com
これには、データに無効なXML文字が含まれていても機能します
the '"}、{" _":"'
は、データに '"}、{" _":"'が含まれている場合、
は"}、{\" _ \":\"
'、'
は任意の文字列区切り文字で置き換えることができます
およびSQL Server 2017では、Azure SQLデータベース
MySQLには、 GROUP_CONCAT()。複数の行の値を連結できます。例:
SELECT 1 AS a, GROUP_CONCAT(name ORDER BY name ASC SEPARATOR ', ') AS people
FROM users
WHERE id IN (1,2,3)
GROUP BY a
COALESCE を使用-こちらから詳細をご覧ください
例:
102
103
104
次に、SQLサーバーで以下のコードを記述します
Declare @Numbers AS Nvarchar(MAX) -- It must not be MAX if you have few numbers
SELECT @Numbers = COALESCE(@Numbers + ',', '') + Number
FROM TableName where Number IS NOT NULL
SELECT @Numbers
出力は次のようになります:
102,103,104
Postgres配列は素晴らしいです。例:
テストデータを作成します:
postgres=# \c test
You are now connected to database "test" as user "hgimenez".
test=# create table names (name text);
CREATE TABLE
test=# insert into names (name) values ('Peter'), ('Paul'), ('Mary');
INSERT 0 3
test=# select * from names;
name
-------
Peter
Paul
Mary
(3 rows)
それらを配列に集約します:
test=# select array_agg(name) from names;
array_agg
-------------------
{Peter,Paul,Mary}
(1 row)
配列をコンマ区切りの文字列に変換します:
test=# select array_to_string(array_agg(name), ', ') from names;
array_to_string
-------------------
Peter, Paul, Mary
(1 row)
完了
PostgreSQL 9.0以降では、さらに簡単です。
Oracle 11gリリース2は、LISTAGG関数をサポートしています。ドキュメントこちら。
COLUMN employees FORMAT A50
SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
FROM emp
GROUP BY deptno;
DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 ADAMS,FORD,JONES,SCOTT,SMITH
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD
3 rows selected.
警告
結果の文字列が4000文字を超える可能性がある場合は、この関数の実装に注意してください。例外をスローします。その場合は、例外を処理するか、結合文字列が4000文字を超えないようにする独自の関数をロールする必要があります。
SQL Server 2005以降では、以下のクエリを使用して行を連結します。
DECLARE @t table
(
Id int,
Name varchar(10)
)
INSERT INTO @t
SELECT 1,'a' UNION ALL
SELECT 1,'b' UNION ALL
SELECT 2,'c' UNION ALL
SELECT 2,'d'
SELECT ID,
stuff(
(
SELECT ','+ [Name] FROM @t WHERE Id = t.Id FOR XML PATH('')
),1,1,'')
FROM (SELECT DISTINCT ID FROM @t ) t
自宅ではSQL Serverにアクセスできないため、ここの構文は推測しますが、多かれ少なかれです:
DECLARE @names VARCHAR(500)
SELECT @names = @names + ' ' + Name
FROM Names
再帰的なCTEソリューションが提案されましたが、コードは提供されていません。以下のコードは、再帰CTEの例です。結果は質問と一致しますが、データは指定された説明と非常に一致しないことに注意してください。テーブル内のすべての行ではなく、行のグループ。テーブル内のすべての行に一致するように変更することは、読者の課題として残されています。
;with basetable as
( SELECT id, CAST(name as varchar(max))name,
ROW_NUMBER() OVER(Partition By id order by seq) rw,
COUNT(*) OVER (Partition By id) recs
FROM (VALUES (1, 'Johnny', 1), (1,'M', 2),
(2,'Bill', 1), (2, 'S.', 4), (2, 'Preston', 5), (2, 'Esq.', 6),
(3, 'Ted', 1), (3,'Theodore', 2), (3,'Logan', 3),
(4, 'Peter', 1), (4,'Paul', 2), (4,'Mary', 3)
)g(id, name, seq)
),
rCTE as (
SELECT recs, id, name, rw from basetable where rw=1
UNION ALL
SELECT b.recs, r.ID, r.name +', '+ b.name name, r.rw+1
FROM basetable b
inner join rCTE r
on b.id = r.id and b.rw = r.rw+1
)
SELECT name FROM rCTE
WHERE recs = rw and ID=4
最終結果を保持する変数を作成し、選択する必要があります。
最も簡単な解決策
DECLARE @char VARCHAR(MAX);
SELECT @char = COALESCE(@char + ', ' + [column], [column])
FROM [table];
PRINT @char;
PostgreSQL 9.0以降、これは非常に簡単です:
select string_agg(name, ',')
from names;
9.0より前のバージョンでは、hgmnzが示すように array_agg()
を使用できます
SQL Server vNextでは、これはSTRING_AGG関数を使用して組み込まれます。詳細については、以下を参照してください。 https://msdn.microsoft.com/en-us/library/mt790580.aspx
XMLを使用すると、行をコンマで区切ることができました。追加のコンマには、SQL  Serverのreplace関数を使用できます。コンマを追加する代わりに、AS 'data()'を使用すると、行がスペースで連結されます。これは、後で記述されている構文のようにコンマで置き換えることができます。
REPLACE(
(select FName AS 'data()' from NameList for xml path(''))
, ' ', ', ')
追加のコンマを使用しない、すぐに使用できるソリューション:
select substring(
(select ', '+Name AS 'data()' from Names for xml path(''))
,3, 255) as "MyList"
空のリストはNULL値になります。 通常は、テーブルの列またはプログラム変数にリストを挿入します。必要に応じて最大長を255に調整します。
(DiwakarとJens Frandsenは良い答えを提供しましたが、改善が必要です。)
SELECT STUFF((SELECT ', ' + name FROM [table] FOR XML PATH('')), 1, 2, '')
サンプルは次のとおりです。
DECLARE @t TABLE (name VARCHAR(10))
INSERT INTO @t VALUES ('Peter'), ('Paul'), ('Mary')
SELECT STUFF((SELECT ', ' + name FROM @t FOR XML PATH('')), 1, 2, '')
--Peter, Paul, Mary
DECLARE @Names VARCHAR(8000)
SELECT @name = ''
SELECT @Names = @Names + ',' + Names FROM People
SELECT SUBSTRING(2, @Names, 7998)
これにより、浮遊コンマが先頭に配置されます。
ただし、他の列が必要な場合、または子テーブルをCSVにするには、これをスカラーユーザー定義フィールド(UDF)でラップする必要があります。
XMLパスをSELECT句の相関サブクエリとして使用することもできます(ただし、Googleは自宅で仕事をしていないため、仕事に戻るまで待つ必要があります:-)
他の回答では、回答を読む人は、車両や学生などの特定のドメインテーブルを知っている必要があります。ソリューションをテストするには、テーブルを作成してデータを入力する必要があります。
以下は、SQL Server" Information_Schema.Columns"を使用する例です。表。このソリューションを使用すると、テーブルを作成したりデータを追加したりする必要がありません。この例では、データベース内のすべてのテーブルの列名のコンマ区切りリストを作成します。
SELECT
Table_Name
,STUFF((
SELECT ',' + Column_Name
FROM INFORMATION_SCHEMA.Columns Columns
WHERE Tables.Table_Name = Columns.Table_Name
ORDER BY Column_Name
FOR XML PATH ('')), 1, 1, ''
)Columns
FROM INFORMATION_SCHEMA.Columns Tables
GROUP BY TABLE_NAME
Oracle DBについては、次の質問を参照してください:ストアドプロシージャを作成せずにOracleで複数の行を1つに連結するにはどうすればよいですか?
最良の答えは、Oracle 11gリリース2以降で利用可能な組み込みLISTAGG()関数を使用した@Emmanuelによるものと思われます。
SELECT question_id,
LISTAGG(element_id, ',') WITHIN GROUP (ORDER BY element_id)
FROM YOUR_TABLE;
GROUP BY question_id
@ user762952が指摘し、Oracleのドキュメントによると、 http://www.oracle-base.com/articles/misc/string-aggregation-techniques.php では、WM_CONCAT()関数もオプションです。安定しているように見えますが、アプリケーションSQLには使用しないことを明示的に推奨しているため、自己責任で使用してください。
それ以外は、独自の関数を作成する必要があります。上記のOracleドキュメントには、その方法に関するガイドがあります。
Danaの回答の優雅さが本当に好きでした。完成させたかっただけです。
DECLARE @names VARCHAR(MAX)
SET @names = ''
SELECT @names = @names + ', ' + Name FROM Names
-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)
null値を回避するには、CONCAT()を使用できます
DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name)
FROM Names
select @names
この回答を行うには、サーバーでの何らかの権限が必要です。
アセンブリは最適なオプションです。作成方法を説明するサイトがたくさんあります。非常によく説明されていると思うのは、この one
必要に応じて、既にアセンブリを作成しており、DLL こちら。
ダウンロードしたら、SQL Serverで次のスクリプトを実行する必要があります。
CREATE Assembly concat_assembly
AUTHORIZATION dbo
FROM '<PATH TO Concat.dll IN SERVER>'
WITH PERMISSION_SET = SAFE;
GO
CREATE AGGREGATE dbo.concat (
@Value NVARCHAR(MAX)
, @Delimiter NVARCHAR(4000)
) RETURNS NVARCHAR(MAX)
EXTERNAL Name concat_assembly.[Concat.Concat];
GO
sp_configure 'clr enabled', 1;
RECONFIGURE
アセンブリへのパスにサーバーからアクセスできる可能性があることに注意してください。すべてのステップを正常に完了したため、次のような関数を使用できます。
SELECT dbo.Concat(field1, ',')
FROM Table1
願っています!!!
通常、このようなselectを使用して、SQL Serverで文字列を連結します。
with lines as
(
select
row_number() over(order by id) id, -- id is a line id
line -- line of text.
from
source -- line source
),
result_lines as
(
select
id,
cast(line as nvarchar(max)) line
from
lines
where
id = 1
union all
select
l.id,
cast(r.line + N', ' + l.line as nvarchar(max))
from
lines l
inner join
result_lines r
on
l.id = r.id + 1
)
select top 1
line
from
result_lines
order by
id desc
nullを処理する場合は、where句を追加するか、最初の句の周りに別のCOALESCEを追加することにより、nullを処理できます。
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(COALESCE(@Names + ', ', '') + Name, @Names) FROM People
MySQLの完全な例:
多数のデータを持つことができるユーザーがおり、リストにすべてのユーザーデータを表示できる出力が必要です:
結果:
___________________________
| id | rowList |
|-------------------------|
| 0 | 6, 9 |
| 1 | 1,2,3,4,5,7,8,1 |
|_________________________|
テーブルのセットアップ:
CREATE TABLE `Data` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1;
INSERT INTO `Data` (`id`, `user_id`) VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(5, 1),
(6, 0),
(7, 1),
(8, 1),
(9, 0),
(10, 1);
CREATE TABLE `User` (
`id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `User` (`id`) VALUES
(0),
(1);
クエリ:
SELECT User.id, GROUP_CONCAT(Data.id ORDER BY Data.id) AS rowList FROM User LEFT JOIN Data ON User.id = Data.user_id GROUP BY User.id
Oracleでは、 wm_concat
です。この機能は、 10gリリース以降で利用できると思います。
これも便利です
create table #test (id int,name varchar(10))
--use separate inserts on older versions of SQL Server
insert into #test values (1,'Peter'), (1,'Paul'), (1,'Mary'), (2,'Alex'), (3,'Jack')
DECLARE @t VARCHAR(255)
SELECT @t = ISNULL(@t + ',' + name, name) FROM #test WHERE id = 1
select @t
drop table #test
返品
Peter,Paul,Mary
この方法は、NPATH関数を使用するTeradata Asterデータベースにのみ適用されます。
繰り返しますが、学生テーブルがあります
SubjectID StudentName
---------- -------------
1 Mary
1 John
1 Sam
2 Alaina
2 Edward
NPATHを使用すると、単一のSELECTになります。
SELECT * FROM npath(
ON Students
PARTITION BY SubjectID
ORDER BY StudentName
MODE(nonoverlapping)
PATTERN('A*')
SYMBOLS(
'true' as A
)
RESULT(
FIRST(SubjectID of A) as SubjectID,
ACCUMULATE(StudentName of A) as StudentName
)
);
結果:
SubjectID StudentName
---------- -------------
1 [John, Mary, Sam]
2 [Alaina, Edward]