14

区切られた文字列の n 番目の要素を返す関数を作成する必要があります。

データ移行プロジェクトのために、SQL Server データベースに保存されている JSON 監査レコードを、SQL スクリプトを使用して構造化レポートに変換しています。目標は、コードなしでスクリプトで使用される SQL スクリプトと SQL 関数を提供することです。

(これは、新しい監査機能が ASP.NET/MVC アプリケーションに追加されている間に使用される短期的な修正です)

区切られた文字列から表への使用可能な例が不足することはありません。Common Table Expression の例を選択しましたhttp://www.sqlperformance.com/2012/07/t-sql-queries/split-strings

例: '1,222,2,67,888,1111' から 67 を返したい

4

11 に答える 11

36

これは 67 を解放する最も簡単な答えです (タイプセーフ!! ):

SELECT CAST('<x>' + REPLACE('1,222,2,67,888,1111',',','</x><x>') + '</x>' AS XML).value('/x[4]','int')

以下に、文字列、区切り文字、および位置の変数でこれを使用する方法の例を示します (XML で禁止されている文字を含むエッジケースの場合でも)。

簡単なもの

この質問は、文字列の分割方法に関するものではなく、n 番目の要素を取得する方法に関するものです。最も簡単で完全にインライン化可能な方法は、次の IMO です。

これは、スペースで区切られたパート 2 を取得する実際のワンライナーです。

DECLARE @input NVARCHAR(100)=N'part1 part2 part3';
SELECT CAST(N'<x>' + REPLACE(@input,N' ',N'</x><x>') + N'</x>' AS XML).value('/x[2]','nvarchar(max)')

sql:variable()変数はまたはで使用できますsql:column()

もちろん、区切り文字と位置に変数を使用できますsql:column(クエリの値から直接位置を取得するために使用します)。

DECLARE @dlmt NVARCHAR(10)=N' ';
DECLARE @pos INT = 2;
SELECT CAST(N'<x>' + REPLACE(@input,@dlmt,N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)')

XML 禁止文字を含む Edge-Case

文字列に禁止文字が含まれている可能性がある場合でも、この方法で行うことができます。最初に文字列で使用FOR XML PATHして、禁止されているすべての文字を適切なエスケープシーケンスに暗黙的に置き換えます。

さらに、区切り文字がセミコロンである場合は、非常に特殊なケースです。この場合、最初に区切り文字を「#DLMT#」に置き換え、最後にこれを XML タグに置き換えます。

SET @input=N'Some <, > and &;Other äöü@€;One more';
SET @dlmt=N';';
SELECT CAST(N'<x>' + REPLACE((SELECT REPLACE(@input,@dlmt,'#DLMT#') AS [*] FOR XML PATH('')),N'#DLMT#',N'</x><x>') + N'</x>' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)');

SQL Server 2016+ の更新

残念ながら、開発者は でパーツのインデックスを返すのを忘れていましたSTRING_SPLIT。しかし、SQL-Server 2016+ を使用するとJSON_VALUEOPENJSON.

JSON_VALUEインデックスの配列として位置を渡すことができます。

ドキュメントには明確にOPENJSON記載されています:

OPENJSON が JSON 配列を解析すると、関数は JSON テキスト内の要素のインデックスをキーとして返します。

のような文字列に1,2,3は、角かっこ以外は何も必要ありません: [1,2,3].
のような単語の文字列は、 であるthis is an example必要があります["this","is","an"," example"]
これらは非常に簡単な文字列操作です。試してみてください:

DECLARE @str VARCHAR(100)='Hello John Smith';
DECLARE @position INT = 2;

--We can build the json-path '$[1]' using CONCAT
SELECT JSON_VALUE('["' + REPLACE(@str,' ','","') + '"]',CONCAT('$[',@position-1,']'));

--位置セーフな文字列スプリッター (ゼロベース) については、これを参照してください。

SELECT  JsonArray.[key] AS [Position]
       ,JsonArray.[value] AS [Part]
FROM OPENJSON('["' + REPLACE(@str,' ','","') + '"]') JsonArray

この投稿では、さまざまなアプローチをテストし、それOPENJSONが非常に高速であることを発見しました。有名な「delimitedSplit8k()」メソッドよりもはるかに高速です...

UPDATE 2 - タイプセーフな値を取得する

doubled を使用するだけで、配列内で配列[[]]を使用できます。これにより、型付きWITH-clauseが可能になります。

DECLARE  @SomeDelimitedString VARCHAR(100)='part1|1|20190920';

DECLARE @JsonArray NVARCHAR(MAX)=CONCAT('[["',REPLACE(@SomeDelimitedString,'|','","'),'"]]');

SELECT @SomeDelimitedString          AS TheOriginal
      ,@JsonArray                    AS TransformedToJSON
      ,ValuesFromTheArray.*
FROM OPENJSON(@JsonArray)
WITH(TheFirstFragment VARCHAR(100) '$[0]'
    ,TheSecondFragment INT '$[1]'
    ,TheThirdFragment DATE '$[2]') ValuesFromTheArray
于 2016-07-08T19:53:59.347 に答える
5

これが私の最初の解決策です...これは、アーロン・バートランドの作業に基づいています http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings

戻り値の型をスカラー関数に変更しただけです。

例: SELECT dbo.GetSplitString_CTE('1,222,2,67,888,1111',',',4)

CREATE FUNCTION dbo.GetSplitString_CTE
(
   @List       VARCHAR(MAX),
   @Delimiter  VARCHAR(255),
   @ElementNumber int
)
RETURNS VARCHAR(4000)
AS
BEGIN

   DECLARE @result varchar(4000)    
   DECLARE @Items TABLE ( position int IDENTITY PRIMARY KEY,
                          Item VARCHAR(4000)
                         )  

   DECLARE @ll INT = LEN(@List) + 1, @ld INT = LEN(@Delimiter);  

   WITH a AS
   (
       SELECT
           [start] = 1,
           [end]   = COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                       @List, @ld), 0), @ll),
           [value] = SUBSTRING(@List, 1, 
                     COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                       @List, @ld), 0), @ll) - 1)
       UNION ALL
       SELECT
           [start] = CONVERT(INT, [end]) + @ld,
           [end]   = COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                       @List, [end] + @ld), 0), @ll),
           [value] = SUBSTRING(@List, [end] + @ld, 
                     COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                       @List, [end] + @ld), 0), @ll)-[end]-@ld)
       FROM a
       WHERE [end] < @ll
   )
   INSERT @Items SELECT [value]
   FROM a
   WHERE LEN([value]) > 0
   OPTION (MAXRECURSION 0);

   SELECT @result=Item
   FROM @Items
   WHERE position=@ElementNumber

   RETURN @result;
END
GO
于 2013-10-18T12:18:14.520 に答える
3

@a - 値 (fe 'a/bb/ccc/dddd/ee/ff/....')

@p - 希望する位置 (1,2,3...)

@d - 区切り記号 ( '/' )

trim(substring(replace(@a,@d,replicate(' ',len(@a))),(@p-1)*len(@a)+1,len(@a)))

唯一の問題は、必要な部分に末尾または先頭の空白がある場合、それらがトリミングされることです。

https://exceljet.net/formula/split-text-with-delimiterの記事に完全に基づく

于 2018-05-29T19:01:50.743 に答える
1

この選択を UFN に入れることができます。必要に応じて、区切り文字を指定するためにカスタマイズすることもできます。その場合、ufn には 2 つの入力があります。使用する N 番目の数値と区切り文字。

    DECLARE @tlist varchar(max)='10,20,30,40,50,60,70,80,90,100'
    DECLARE @i INT=1, @nth INT=3
    While len(@tlist) <> 0
    BEGIN
            IF @i=@nth
            BEGIN
              select Case when charindex(',',@tlist) <> 0 Then LEFT(@tlist,charindex(',',@tlist)-1)
                          Else @tlist
                    END
            END

              Select @tlist = Case when charindex(',',@tlist) <> 0 Then substring(@tlist,charindex(',',@tlist)+1,len(@tlist))
                          Else ''
                          END

            SELECT @i=@i+1
    END
于 2013-10-18T13:54:51.170 に答える
0

私の評判が低いため、ゲイリーのソリューションについてコメントすることはできません

Gary が別のリンクを参照していたことは知っています。

この変数が必要な理由を理解するのに苦労しました

@ld INT = LEN(@Delimiter)

また、charindex が区切り文字 @ld の長さの位置から開始する必要がある理由もわかりません

単一の区切り文字を使用して多くの例をテストしましたが、それらは機能します。ほとんどの場合、区切り文字は 1 文字です。ただし、開発者は区切り記号の長さとして ld を含めたため、コードは複数の文字を持つ区切り記号に対して機能する必要があります。

この場合、次の場合は失敗します

11,,,22,,,33,,,44,,,55,,,

このリンクのコードから複製しました。http://codebetter.com/raymondlewallen/2005/10/26/quick-t-sql-to-parse-a-delimited-string/

複数の文字を持つ区切り文字を含むさまざまなシナリオをテストしました

alter FUNCTION [dbo].[split1]
(
    @string1 VARCHAR(8000) -- List of delimited items
    , @Delimiter VARCHAR(40) = ',' -- delimiter that separates items
    , @ElementNumber int
)
RETURNS varchar(8000)
AS
BEGIN
    declare @position int
    declare @piece varchar(8000)=''
    declare @returnVal varchar(8000)=''
    declare @Pattern varchar(50) = '%' + @Delimiter + '%'
    declare @counter int =0
    declare @ld int = len(@Delimiter)
    declare @ls1 int = len (@string1)
    declare @foundit int = 0

    if patindex(@Pattern , @string1) = 0
        return  ''

    if right(rtrim(@string1),1) <> @Delimiter
        set @string1 = @string1  + @Delimiter

    set @position =  patindex(@Pattern , @string1) + @ld  -1  
    while @position > 0
    begin
        set @counter = @counter +1 
        set @ls1  = len (@string1)
        if (@ls1 >= @ld)
            set @piece = left(@string1, @position - @ld)
        else
            break
        if (@counter = @ElementNumber)
        begin
            set @foundit = 1
                break
        end
        if len(@string1) > 0
        begin
            set @string1 = stuff(@string1, 1, @position, '')
            set @position =  patindex(@Pattern , @string1) + @ld  -1  
        end
        else
        set @position = -1
    end 


    if @foundit =1
        set @returnVal = @piece
    else 
        set @returnVal =  ''
    return @returnVal
于 2015-10-03T01:49:49.350 に答える