次の3行の名前を保持するデータベーステーブルについて考えてみます。
Peter
Paul
Mary
これを単一の文字列に変換する簡単な方法はありPeter, Paul, Mary
ますか?
次の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 (''), TYPE
).value('text()[1]','nvarchar(max)') [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 (''), TYPE
).value('text()[1]','nvarchar(max)'), 2, 1000) [Students]
FROM dbo.Students ST2
この回答は予期しない結果を返す可能性があります一貫した結果を得るには、他の回答で詳述されているFORXMLPATHメソッドの1つを使用してください。
使用COALESCE
:
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
いくつかの説明(この回答は比較的定期的な見解を得るように思われるため):
1)@Names
空の文字列値で初期化する必要はありません。
2)最後に余分なセパレーターを取り除く必要はありません。
@Names
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」を適切なものに置き換えます])。
XML
data()
SQL Serverのコマンドでまだ表示されていない 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
SELECT Stuff(
(SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'')
FOR JSON 構文を使用できます
すなわち
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 文字が含まれていても機能します
データに含まれている場合はエスケープされるため、これ'"},{"_":"'
は安全です'"},{"_":"',
"},{\"_\":\"
', '
任意の文字列セパレーターに置き換えることができます
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
PostgreSQL配列は素晴らしいです。例:
いくつかのテストデータを作成します。
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以降、「名前のない馬」による削除された回答からの引用はさらに簡単です。
select string_agg(name, ',')
from names;
Oracle 11g Release 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
再帰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
私は自宅でSQLServerにアクセスできないので、ここで構文を推測しますが、多かれ少なかれ次のようになります。
DECLARE @names VARCHAR(500)
SELECT @names = @names + ' ' + Name
FROM Names
SQL Server vNext では、これは STRING_AGG 関数で組み込まれます。詳細については、STRING_AGG (Transact-SQL)を参照してください。
余分なコンマがない、すぐに使えるソリューション:
select substring(
(select ', '+Name AS 'data()' from Names for xml path(''))
,3, 255) as "MyList"
空のリストは NULL 値になります。通常、リストをテーブルの列またはプログラム変数に挿入します。必要に応じて最大長の 255 を調整します。
(Diwakar と Jens Frandsen は良い回答を提供しましたが、改善が必要です。)
XML を使用すると、コンマで区切られた行を取得するのに役立ちました。余分なカンマについては、SQL Server の置換機能を使用できます。カンマを追加する代わりに、AS 'data()' を使用すると、行がスペースで連結されます。これは、後で以下に示す構文のようにカンマに置き換えることができます。
REPLACE(
(select FName AS 'data()' from NameList for xml path(''))
, ' ', ', ')
DECLARE @Names VARCHAR(8000)
SELECT @name = ''
SELECT @Names = @Names + ',' + Names FROM People
SELECT SUBSTRING(2, @Names, 7998)
これにより、コンマが先頭に配置されます。
ただし、他の列が必要な場合、または子テーブルを CSV にする場合は、これをスカラー ユーザー定義フィールド (UDF) でラップする必要があります。
XML パスを SELECT 句の相関サブクエリとして使用することもできます (ただし、Google は自宅で仕事をしていないため、仕事に戻るまで待つ必要があります :-)
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
null 値を回避するには、CONCAT() を使用できます。
DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name)
FROM Names
select @names
私はダナの答えの優雅さが本当に好きで、それを完成させたかっただけです。
DECLARE @names VARCHAR(MAX)
SET @names = ''
SELECT @names = @names + ', ' + Name FROM Names
-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)
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() 関数もオプションです。安定しているように見えますが、Oracle はアプリケーション SQL に使用しないことを明示的に推奨しているため、自己責任で使用してください。
それ以外は、独自の関数を作成する必要があります。上記の Oracle ドキュメントには、その方法に関するガイドがあります。
この回答が機能するには、サーバー上で何らかの権限が必要です。
アセンブリはあなたにとって良い選択肢です。作り方を解説しているサイトがたくさんあります。私が非常によく説明されていると思うのはこれです。
必要に応じて、既にアセンブリを作成しています。ここから DLL ファイルをダウンロードできます。
ダウンロードしたら、SQL Server で次のスクリプトを実行する必要があります。
EXEC sp_configure 'show advanced options', 1
RECONFIGURE;
EXEC sp_configure 'clr strict security', 1;
RECONFIGURE;
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
SQL Server 2017以降、 STRING_AGG関数を使用できるようになりました。
null を処理する場合は、where 句を追加するか、最初の句の周りに別の COALESCE を追加して実行できます。
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(COALESCE(@Names + ', ', '') + Name, @Names) FROM People
私は通常、次のような 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
Chris Shafferの答えに加えて:
次のように、データが繰り返される可能性がある場合
Tom
Ali
John
Ali
Tom
Mike
持つ代わりにTom,Ali,John,Ali,Tom,Mike
DISTINCT を使用して重複を回避し、次を取得できますTom,Ali,John,Mike
。
DECLARE @Names VARCHAR(8000)
SELECT DISTINCT @Names = COALESCE(@Names + ',', '') + Name
FROM People
WHERE Name IS NOT NULL
SELECT @Names
これも重宝しそう
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
オラクルでは、ですwm_concat
。この機能は10g 以降のリリースで利用できると思います。
CREATE TABLE dbo.Students
(
StudentId INT
, Name VARCHAR(50)
, CONSTRAINT PK_Students PRIMARY KEY (StudentId)
);
CREATE TABLE dbo.Subjects
(
SubjectId INT
, Name VARCHAR(50)
, CONSTRAINT PK_Subjects PRIMARY KEY (SubjectId)
);
CREATE TABLE dbo.Schedules
(
StudentId INT
, SubjectId INT
, CONSTRAINT PK__Schedule PRIMARY KEY (StudentId, SubjectId)
, CONSTRAINT FK_Schedule_Students FOREIGN KEY (StudentId) REFERENCES dbo.Students (StudentId)
, CONSTRAINT FK_Schedule_Subjects FOREIGN KEY (SubjectId) REFERENCES dbo.Subjects (SubjectId)
);
INSERT dbo.Students (StudentId, Name) VALUES
(1, 'Mary')
, (2, 'John')
, (3, 'Sam')
, (4, 'Alaina')
, (5, 'Edward')
;
INSERT dbo.Subjects (SubjectId, Name) VALUES
(1, 'Physics')
, (2, 'Geography')
, (3, 'French')
, (4, 'Gymnastics')
;
INSERT dbo.Schedules (StudentId, SubjectId) VALUES
(1, 1) --Mary, Physics
, (2, 1) --John, Physics
, (3, 1) --Sam, Physics
, (4, 2) --Alaina, Geography
, (5, 2) --Edward, Geography
;
SELECT
sub.SubjectId
, sub.Name AS [SubjectName]
, ISNULL( x.Students, '') AS Students
FROM
dbo.Subjects sub
OUTER APPLY
(
SELECT
CASE ROW_NUMBER() OVER (ORDER BY stu.Name) WHEN 1 THEN '' ELSE ', ' END
+ stu.Name
FROM
dbo.Students stu
INNER JOIN dbo.Schedules sch
ON stu.StudentId = sch.StudentId
WHERE
sch.SubjectId = sub.SubjectId
ORDER BY
stu.Name
FOR XML PATH('')
) x (Students)
;
私のリストには10項目未満しかなかったので、パフォーマンスの分析を行ったわけではありませんが、30個の奇妙な回答を調べた後、驚いたことに、単一のグループリストにCOALESCEを使用するのと同様に、すでに与えられた同様の回答にひねりがありました。 '変数を設定する必要さえありません(とにかくデフォルトはNULLです)。ソースデータテーブルのすべてのエントリが空白ではないことを前提としています:
DECLARE @MyList VARCHAR(1000), @Delimiter CHAR(2) = ', '
SELECT @MyList = CASE WHEN @MyList > '' THEN @MyList + @Delimiter ELSE '' END + FieldToConcatenate FROM MyData
COALESCE は内部的に同じ考えを使用していると確信しています。マイクロソフトが私にこれを変更しないことを願いましょう。
SQL Serverでこれを行う1つの方法は、テーブルのコンテンツをXML(XML rawの場合)として返し、結果を文字列に変換してから、タグを "、"に置き換えることです。
SELECT PageContent = Stuff(
( SELECT PageContent
FROM dbo.InfoGuide
WHERE CategoryId = @CategoryId
AND SubCategoryId = @SubCategoryId
for xml path(''), type
).value('.[1]','nvarchar(max)'),
1, 1, '')
FROM dbo.InfoGuide info
手遅れですが、すでに多くの解決策があります。MySQLの簡単なソリューションは次のとおりです。
SELECT t1.id,
GROUP_CONCAT(t1.id) ids
FROM table t1 JOIN table t2 ON (t1.id = t2.id)
GROUP BY t1.id
Oracle にはいくつかの方法があります。
create table name
(first_name varchar2(30));
insert into name values ('Peter');
insert into name values ('Paul');
insert into name values ('Mary');
解決策は 1:
select substr(max(sys_connect_by_path (first_name, ',')),2) from (select rownum r, first_name from name ) n start with r=1 connect by prior r+1=r
o/p=> Peter,Paul,Mary
解決策は2です:
select rtrim(xmlagg (xmlelement (e, first_name || ',')).extract ('//text()'), ',') first_name from name
o/p=> Peter,Paul,Mary
これを使って:
ISNULL(SUBSTRING(REPLACE((select ',' FName as 'data()' from NameList for xml path('')), ' ,',', '), 2, 300), '') 'MyList'
「300」は、表示されると思われるアイテムの最大数を考慮して、任意の幅にすることができます。
「TABLE」タイプを使用すると、非常に簡単です。テーブルが呼び出されStudents
、 columnがあるとしますname
。
declare @rowsCount INT
declare @i INT = 1
declare @names varchar(max) = ''
DECLARE @MyTable TABLE
(
Id int identity,
Name varchar(500)
)
insert into @MyTable select name from Students
set @rowsCount = (select COUNT(Id) from @MyTable)
while @i < @rowsCount
begin
set @names = @names + ', ' + (select name from @MyTable where Id = @i)
set @i = @i + 1
end
select @names
この例は、 SQL Server 2008 R2でテストされています。