私はこのようなSQLテーブルを持っています:
| SomeID | OtherID | Data
+----------------+-------------+-------------------
| abcdef-..... | cdef123-... | 18,20,22
| abcdef-..... | 4554a24-... | 17,19
| 987654-..... | 12324a2-... | 13,19,20
このように、個々の行を返すSELECT OtherID, SplitData WHERE SomeID = 'abcdef-.......'
のようなクエリを実行できるクエリがあります。
| OtherID | SplitData
+-------------+-------------------
| cdef123-... | 18
| cdef123-... | 20
| cdef123-... | 22
| 4554a24-... | 17
| 4554a24-... | 19
基本的に、コンマのデータを個々の行に分割しますか?
私はcomma-separated
文字列をリレーショナルデータベースに保存するのは愚かに思えますが、コンシューマアプリケーションの通常の使用例では本当に便利です。
ページングが必要なのでアプリケーションで分割したくないので、アプリ全体をリファクタリングする前にオプションを検討したいと思いました。
SQL Server 2008
です(R2以外)。
あなたはSQL Serverから素晴らしい再帰関数を使うことができます:
サンプル表:
CREATE TABLE Testdata
(
SomeID INT,
OtherID INT,
String VARCHAR(MAX)
)
INSERT Testdata SELECT 1, 9, '18,20,22'
INSERT Testdata SELECT 2, 8, '17,19'
INSERT Testdata SELECT 3, 7, '13,19,20'
INSERT Testdata SELECT 4, 6, ''
INSERT Testdata SELECT 9, 11, '1,2,3,4'
クエリ
;WITH tmp(SomeID, OtherID, DataItem, String) AS
(
SELECT
SomeID,
OtherID,
LEFT(String, CHARINDEX(',', String + ',') - 1),
STUFF(String, 1, CHARINDEX(',', String + ','), '')
FROM Testdata
UNION all
SELECT
SomeID,
OtherID,
LEFT(String, CHARINDEX(',', String + ',') - 1),
STUFF(String, 1, CHARINDEX(',', String + ','), '')
FROM tmp
WHERE
String > ''
)
SELECT
SomeID,
OtherID,
DataItem
FROM tmp
ORDER BY SomeID
-- OPTION (maxrecursion 0)
-- normally recursion is limited to 100. If you know you have very long
-- strings, uncomment the option
出力
SomeID | OtherID | DataItem
--------+---------+----------
1 | 9 | 18
1 | 9 | 20
1 | 9 | 22
2 | 8 | 17
2 | 8 | 19
3 | 7 | 13
3 | 7 | 19
3 | 7 | 20
4 | 6 |
9 | 11 | 1
9 | 11 | 2
9 | 11 | 3
9 | 11 | 4
最後に、SQL Server 2016で待機は終了です。彼らは分割文字列関数 STRING_SPLIT
を導入しました:
select OtherID, cs.Value --SplitData
from yourtable
cross apply STRING_SPLIT (Data, ',') cs
XML、Tallyテーブル、whileループなどの文字列を分割する他の方法はすべて、このSTRING_SPLIT
関数によって吹き飛ばされました。
これはパフォーマンスの比較に関する優れた記事です:パフォーマンスの驚きと仮定:STRING_SPLIT。
旧バージョンでは、tally tableを使用しています(これが1つの分割文字列関数です(最善の方法))。
CREATE FUNCTION [dbo].[DelimitedSplit8K]
(@pString VARCHAR(8000), @pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
-- enough to cover NVARCHAR(4000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(@pString, l.N1, l.L1)
FROM cteLen l
;
これをチェックして
SELECT A.OtherID,
Split.a.value('.', 'VARCHAR(100)') AS Data
FROM
(
SELECT OtherID,
CAST ('<M>' + REPLACE(Data, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM Table1
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a);
select t.OtherID,x.Kod
from testData t
cross apply (select Code from dbo.Split(t.Data,',') ) x
2016年2月現在 - TALLY Table Exampleを参照してください - 2014年2月以降、私のTVFを下回っている可能性が非常に高いです。
上記の例で私の好みのためにあまりにも多くの繰り返しコード。そして私はCTEとXMLのパフォーマンスが嫌いです。また、オーダー固有のコンシューマーがORDER BY
節を指定できるように、明示的なId
を指定することもできます。
CREATE FUNCTION dbo.Split
(
@Line nvarchar(MAX),
@SplitOn nvarchar(5) = ','
)
RETURNS @RtnValue table
(
Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED,
Data nvarchar(100) NOT NULL
)
AS
BEGIN
IF @Line IS NULL RETURN
DECLARE @split_on_len INT = LEN(@SplitOn)
DECLARE @start_at INT = 1
DECLARE @end_at INT
DECLARE @data_len INT
WHILE 1=1
BEGIN
SET @end_at = CHARINDEX(@SplitOn,@Line,@start_at)
SET @data_len = CASE @end_at WHEN 0 THEN LEN(@Line) ELSE @end_at-@start_at END
INSERT INTO @RtnValue (data) VALUES( SUBSTRING(@Line,@start_at,@data_len) );
IF @end_at = 0 BREAK;
SET @start_at = @end_at + @split_on_len
END
RETURN
END
それが2016年のバージョンで解決されたのを見るのはうれしいですが、それに載っていないそれらすべてのために、ここに上記のメソッドの2つの一般化されたそして単純化されたバージョンがあります。
XMLメソッドはもっと短いですが、もちろんxmlトリックを可能にするために文字列が必要です( '悪い'文字はありません)。
XML方式:
create function dbo.splitString(@input Varchar(max), @Splitter VarChar(99)) returns table as
Return
SELECT Split.a.value('.', 'VARCHAR(max)') AS Data FROM
( SELECT CAST ('<M>' + REPLACE(@input, @Splitter, '</M><M>') + '</M>' AS XML) AS Data
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a);
再帰的な方法:
create function dbo.splitString(@input Varchar(max), @Splitter Varchar(99)) returns table as
Return
with tmp (DataItem, ix) as
( select @input , CHARINDEX('',@Input) --Recu. start, ignored val to get the types right
union all
select Substring(@input, ix+1,ix2-ix-1), ix2
from (Select *, CHARINDEX(@Splitter,@Input+@Splitter,ix+1) ix2 from tmp) x where ix2<>0
) select DataItem from tmp where ix<>0
動作中の機能
Create table TEST_X (A int, CSV Varchar(100));
Insert into test_x select 1, 'A,B';
Insert into test_x select 2, 'C,D';
Select A,data from TEST_X x cross apply dbo.splitString(x.CSV,',') Y;
Drop table TEST_X
XML-METHOD 2:Unicode対応????(Max Hodgesによる追加提供)create function dbo.splitString(@input nVarchar(max), @Splitter nVarchar(99)) returns table as Return SELECT Split.a.value('.', 'NVARCHAR(max)') AS Data FROM ( SELECT CAST ('<M>' + REPLACE(@input, @Splitter, '</M><M>') + '</M>' AS XML) AS Data ) AS A CROSS APPLY Data.nodes ('/M') AS Split(a);
DECLARE @id_list VARCHAR(MAX) = '1234,23,56,576,1231,567,122,87876,57553,1216'
DECLARE @table TABLE ( id VARCHAR(50) )
DECLARE @x INT = 0
DECLARE @firstcomma INT = 0
DECLARE @nextcomma INT = 0
SET @x = LEN(@id_list) - LEN(REPLACE(@id_list, ',', '')) + 1 -- number of ids in id_list
WHILE @x > 0
BEGIN
SET @nextcomma = CASE WHEN CHARINDEX(',', @id_list, @firstcomma + 1) = 0
THEN LEN(@id_list) + 1
ELSE CHARINDEX(',', @id_list, @firstcomma + 1)
END
INSERT INTO @table
VALUES ( SUBSTRING(@id_list, @firstcomma + 1, (@nextcomma - @firstcomma) - 1) )
SET @firstcomma = CHARINDEX(',', @id_list, @firstcomma + 1)
SET @x = @x - 1
END
SELECT *
FROM @table
非常に遅いですが、これを試してみてください。
SELECT ColumnID, Column1, value --Do not change 'value' name. Leave it as it is.
FROM tbl_Sample
CROSS APPLY STRING_SPLIT(Tags, ','); --'Tags' is the name of column containing comma separated values
だから我々はこれを持っていた:tbl_Sample:
ColumnID| Column1 | Tags
--------|-----------|-------------
1 | ABC | 10,11,12
2 | PQR | 20,21,22
このクエリを実行した後:
ColumnID| Column1 | value
--------|-----------|-----------
1 | ABC | 10
1 | ABC | 11
1 | ABC | 12
2 | PQR | 20
2 | PQR | 21
2 | PQR | 22
ありがとうございます。
下記のTSQLを参照してください。 STRING_SPLIT関数は、互換性レベル130以上でのみ利用可能です。
TSQL:
DECLARE @stringValue NVARCHAR(400) = 'red,blue,green,yellow,black'
DECLARE @separator CHAR = ','
SELECT [value] As Colour
FROM STRING_SPLIT(@stringValue, @separator);
結果:
レッドブルーグリーンイエローブラック
関数
CREATE FUNCTION dbo.SplitToRows (@column varchar(100), @separator varchar(10))
RETURNS @rtnTable TABLE
(
ID int identity(1,1),
ColumnA varchar(max)
)
AS
BEGIN
DECLARE @position int = 0
DECLARE @endAt int = 0
DECLARE @tempString varchar(100)
set @column = ltrim(rtrim(@column))
WHILE @position<=len(@column)
BEGIN
set @endAt = CHARINDEX(@separator,@column,@position)
if(@endAt=0)
begin
Insert into @rtnTable(ColumnA) Select substring(@column,@position,len(@column)-@position)
break;
end
set @tempString = substring(ltrim(rtrim(@column)),@position,@endAt-@position)
Insert into @rtnTable(ColumnA) select @tempString
set @position=@endAt+1;
END
return
END
使用事例
select * from dbo.SplitToRows('T14; p226.0001; eee; 3554;', ';')
または複数の結果セットを持つ単なる選択
DECLARE @column varchar(max)= '1234; 4748;abcde; 324432'
DECLARE @separator varchar(10) = ';'
DECLARE @position int = 0
DECLARE @endAt int = 0
DECLARE @tempString varchar(100)
set @column = ltrim(rtrim(@column))
WHILE @position<=len(@column)
BEGIN
set @endAt = CHARINDEX(@separator,@column,@position)
if(@endAt=0)
begin
Select substring(@column,@position,len(@column)-@position)
break;
end
set @tempString = substring(ltrim(rtrim(@column)),@position,@endAt-@position)
select @tempString
set @position=@endAt+1;
END
このアプローチを使うとき、あなたの値のどれも違法なXMLであるものを含んでいないことを確実にしなければなりません - user1151923
私はいつもXMLメソッドを使います。必ずVALID XMLを使用してください。有効なXMLとTextの間で変換するための2つの関数があります。 (私は通常それらを必要としないので私はキャリッジリターンを取り除く傾向があります。
CREATE FUNCTION dbo.udf_ConvertTextToXML (@Text varchar(MAX))
RETURNS varchar(MAX)
AS
BEGIN
SET @Text = REPLACE(@Text,CHAR(10),'')
SET @Text = REPLACE(@Text,CHAR(13),'')
SET @Text = REPLACE(@Text,'<','<')
SET @Text = REPLACE(@Text,'&','&')
SET @Text = REPLACE(@Text,'>','>')
SET @Text = REPLACE(@Text,'''',''')
SET @Text = REPLACE(@Text,'"','"')
RETURN @Text
END
CREATE FUNCTION dbo.udf_ConvertTextFromXML (@Text VARCHAR(MAX))
RETURNS VARCHAR(max)
AS
BEGIN
SET @Text = REPLACE(@Text,'<','<')
SET @Text = REPLACE(@Text,'&','&')
SET @Text = REPLACE(@Text,'>','>')
SET @Text = REPLACE(@Text,''','''')
SET @Text = REPLACE(@Text,'"','"')
RETURN @Text
END
;WITH tmp(SomeID, OtherID, DataItem, Data) as (
SELECT SomeID, OtherID, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
FROM Testdata
WHERE Data > ''
)
SELECT SomeID, OtherID, Data
FROM tmp
ORDER BY SomeID
上記のクエリにごくわずかな変更を加えるだけで...
下記はSQL Server 2008で動作します
select *, ROW_NUMBER() OVER(order by items) as row#
from
( select 134 myColumn1, 34 myColumn2, 'd,c,k,e,f,g,h,a' comaSeperatedColumn) myTable
cross apply
SPLIT (rtrim(comaSeperatedColumn), ',') splitedTable -- gives 'items' column
Originテーブルの列と分割テーブルの "items"を含むすべてのデカルト積を取得します。
次の関数を使ってデータを抽出することができます
CREATE FUNCTION [dbo].[SplitString]
(
@RowData NVARCHAR(MAX),
@Delimeter NVARCHAR(MAX)
)
RETURNS @RtnValue TABLE
(
ID INT IDENTITY(1,1),
Data NVARCHAR(MAX)
)
AS
BEGIN
DECLARE @Iterator INT
SET @Iterator = 1
DECLARE @FoundIndex INT
SET @FoundIndex = CHARINDEX(@Delimeter,@RowData)
WHILE (@FoundIndex>0)
BEGIN
INSERT INTO @RtnValue (data)
SELECT
Data = LTRIM(RTRIM(SUBSTRING(@RowData, 1, @FoundIndex - 1)))
SET @RowData = SUBSTRING(@RowData,
@FoundIndex + DATALENGTH(@Delimeter) / 2,
LEN(@RowData))
SET @Iterator = @Iterator + 1
SET @FoundIndex = CHARINDEX(@Delimeter, @RowData)
END
INSERT INTO @RtnValue (Data)
SELECT Data = LTRIM(RTRIM(@RowData))
RETURN
END