文字列を分割してアイテムxにアクセスするにはどうすればよいですか?


493

SQL Serverを使用して、アイテムxにアクセスできるように文字列を分割するにはどうすればよいですか?

文字列 "Hello John Smith"を取ります。文字列をスペースで分割し、「John」を返す必要があるインデックス1のアイテムにアクセスするにはどうすればよいですか?




4
ここでの最高の答えは-少なくとも私にとって-かなり古風で時代遅れです。手続き型検索、ループ、再帰、CLR、関数、多くのコード行...「アクティブ」な回答を読んで最新のアプローチを見つけるのは興味深いかもしれません。
Shnugo

私はより多くの最新のアプローチで新しい答えを追加しました:stackoverflow.com/a/49669994/632604
Gorgi Rankovski

試してみてくださいリストのn番目の要素を取得します - > portosql.wordpress.com/2019/05/27/enesimo-elemento-lista
ホセ・ディズ

回答:


191

区切られた文字列を解析するSQLユーザー定義関数のソリューションが役立つかもしれません(コードプロジェクトから)。

この単純なロジックを使用できます。

Declare @products varchar(200) = '1|20|3|343|44|6|8765'
Declare @individual varchar(20) = null

WHILE LEN(@products) > 0
BEGIN
    IF PATINDEX('%|%', @products) > 0
    BEGIN
        SET @individual = SUBSTRING(@products,
                                    0,
                                    PATINDEX('%|%', @products))
        SELECT @individual

        SET @products = SUBSTRING(@products,
                                  LEN(@individual + '|') + 1,
                                  LEN(@products))
    END
    ELSE
    BEGIN
        SET @individual = @products
        SET @products = NULL
        SELECT @individual
    END
END

1
なぜSET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( RTRIM( LTRIM( @p_SourceText)))、そう ではないの SET @p_SourceText = RTRIM( LTRIM( @p_SourceText)) SET @w_Length = DATALENGTH( @p_SourceText)ですか?
Beth

12
@GateKillerこのソリューションはUnicodeをサポートせず、ハードコードされたnumeric(18,3)を使用するため、実行可能な「再利用可能な」関数にはなりません。
Filip De Vos、2011年

4
これは機能しますが、大量のメモリを割り当て、CPUを浪費します。
jjxtra

2
SQL Server 2016以降STRING_SPLIT、文字列を分割し、SELECTステートメントまたは他の場所で使用できる1列のテーブル結果を返す組み込み関数があります。
qJake 2017

私が働いている人たちが2016年にいないのは残念ですが、彼らが彼らの靴からリードを得ることができるように私はそれを心に留めておきます。暫定的には素晴らしいソリューションです。関数として実装し、引数として区切り文字を追加しました。
ブランドングリフィン

355

SQL Serverに分割関数が組み込まれているとは思わないので、UDF以外に、PARSENAME関数をハイジャックすることが唯一の答えです。

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 2) 

PARSENAMEは文字列を取り、ピリオド文字で分割します。2番目の引数として数値を受け取り、その数値は文字列のどのセグメントを返すかを指定します(後ろから前へと機能します)。

SELECT PARSENAME(REPLACE('Hello John Smith', ' ', '.'), 3)  --return Hello

明らかな問題は、文字列にすでにピリオドが含まれている場合です。私はまだUDFを使用するのが最善の方法だと思います...他の提案はありますか?


102
Saulに感謝します...このソリューションは実際の開発にとって本当に悪いソリューションであることを指摘しておきます。PARSENAMEは4つの部分しか予期しないため、4つを超える部分を含む文字列を使用すると、NULLが返されます。UDFソリューションは明らかに優れています。
ネイサンベッドフォード、

33
これは素晴らしいハックであり、実際の言語で非常に単純なものにこのようなものが必要であることも私を泣かせます。
Factor Mystic

36
インデックスを「正しい」方法で、つまり1から機能させるために、REVERSEでハイジャックをハイジャックしました:REVERSE(PARSENAME(REPLACE(REVERSE( 'Hello John Smith')、 ''、 '。') 、1))-Helloを返します
NothingsImpossible

3
@FactorMystic First Normal Formでは、1つのフィールドに複数の値を入力しないでください。これは文字通りRDBMSの最初のルールです。SPLIT()それが悪いデータベース設計を奨励し、データベースがこの形式で保存されたデータを使用するように最適化されることがないため、機能が付属していません。RDBMSは、開発者が処理しないように設計された愚かなことを行うのを助ける義務はありません。正解は常に「40年前に言ったようにデータベースを正規化する」です。SQLもRDBMSも、設計の悪さのせいではありません。
ベーコンビット2014

8
@BaconBitsは理論的には同意しますが、実際にはこのようなツールは、あなたの前に来た誰かが作成した質の悪いデザインを正規化するときに役立ちます。
Tim Abell

110

最初に、関数を作成します(CTEを使用すると、共通テーブル式では一時テーブルが不要になります)。

 create function dbo.SplitString 
    (
        @str nvarchar(4000), 
        @separator char(1)
    )
    returns table
    AS
    return (
        with tokens(p, a, b) AS (
            select 
                1, 
                1, 
                charindex(@separator, @str)
            union all
            select
                p + 1, 
                b + 1, 
                charindex(@separator, @str, b + 1)
            from tokens
            where b > 0
        )
        select
            p-1 zeroBasedOccurance,
            substring(
                @str, 
                a, 
                case when b > 0 then b-a ELSE 4000 end) 
            AS s
        from tokens
      )
    GO

次に、このようにテーブルとして使用します(または既存のストアドプロシージャに収まるように変更します)。

select s 
from dbo.SplitString('Hello John Smith', ' ')
where zeroBasedOccurance=1

更新

以前のバージョンでは、4000文字より長い入力文字列は失敗しました。このバージョンは制限を処理します:

create function dbo.SplitString 
(
    @str nvarchar(max), 
    @separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
    select 
        cast(1 as bigint), 
        cast(1 as bigint), 
        charindex(@separator, @str)
    union all
    select
        p + 1, 
        b + 1, 
        charindex(@separator, @str, b + 1)
    from tokens
    where b > 0
)
select
    p-1 ItemIndex,
    substring(
        @str, 
        a, 
        case when b > 0 then b-a ELSE LEN(@str) end) 
    AS s
from tokens
);

GO

使い方は同じです。


14
エレガントですが、再帰の深さの制限のため、100要素でのみ機能します。
12

4
@Pking、いいえ、デフォルトは100(無限ループを防ぐため)です。MAXRECURSIONヒントを使用して、再帰レベルの数を定義します(0to 327670「制限なし」-サーバーをクラッシュさせる可能性があります)。ところで、PARSENAMEそれは普遍的なので:-) よりはるかに良い答えです。+1
ミシャウポワガ2013年

maxrecursionこのソリューションに追加することは、この質問とその回答に注意してください。Table-Valued-Function内でCTE maxrecursionオプションを設定する方法
のMichałPowaga

具体的には、Crisfoleの回答を参照してください。彼の方法は多少遅くなりますが、他のほとんどのオプションよりも簡単です。
AHiggins、2015

マイナーポイントですが、列名を変更したため、使用法は同じでsはなく、定義されていません
Tim Abell

62

ここでのほとんどのソリューションは、whileループまたは再帰CTEを使用します。スペース以外の区切り文字を使用できる場合は、セットベースのアプローチの方が優れています。

CREATE FUNCTION [dbo].[SplitString]
    (
        @List NVARCHAR(MAX),
        @Delim VARCHAR(255)
    )
    RETURNS TABLE
    AS
        RETURN ( SELECT [Value], idx = RANK() OVER (ORDER BY n) FROM 
          ( 
            SELECT n = Number, 
              [Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number],
              CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number])))
            FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
              FROM sys.all_objects) AS x
              WHERE Number <= LEN(@List)
              AND SUBSTRING(@Delim + @List, [Number], LEN(@Delim)) = @Delim
          ) AS y
        );

使用例:

SELECT Value FROM dbo.SplitString('foo,bar,blat,foo,splunge',',')
  WHERE idx = 3;

結果:

----
blat

idx必要なものを引数として関数に追加することもできますが、読者への演習として残しておきます。

SQL Server 2016で追加されたネイティブ関数だけでこれを行うことはできません。これは、出力が元のリストの順序でレンダリングされる保証がないためです。言い換えれば、あなたが結果を渡す場合、結果はその順序になる可能性がありますが、それは可能性があります。私は組み込み機能を改善するためにコミュニティの助けをここに求めました:STRING_SPLIT3,6,11,3,6

十分な定性的なフィードバックがあれば、実際に次のような機能強化を検討するかもしれません。

アプリケーション層からの文字列を分割する場合の分割関数の詳細、whileループと再帰CTEがスケーリングしない理由(およびその証明)、およびより良い代替案:

ただし、SQL Server 2016以降では、以下を確認しSTRING_SPLIT()STRING_AGG()ください。


1
ベストアンサー、IMHO。他のいくつかの回答では、100のSQL再帰制限の問題がありますが、この場合はありません。非常に高速で非常にシンプルな実装。+2ボタンはどこにありますか?
T-moty

5
私は使用してそのままこの機能を試してみました:select * from DBO.SplitString('Hello John smith', ' ');と生成される出力は次の通りであった: ハローello LLO LO OジョンOHN HN Nスミスmith第i番目の時間
wwmbes

2
@AaronBertrand GateKillerによって投稿された元の問題にはスペース区切り文字が含まれます。
wwmbes

1
@ user1255933対処済み。
アーロンバートランド

1
@Michaelはい、そうです。また、ALTER SCHEMA権限を持っていない場合は選択するテーブルがなく、SELECT権限がない場合はそこから選択できません。いつでも誰かに関数の作成を依頼できます。 。または、それを作成できる場所に作成します(一時的な場合でも、tempdbなどで)。そして2016+では、とにかく自分で作成する必要がある関数ではなく、STRING_SPLIT()を使用する必要があります。
アーロンバートランド

38

Numberテーブルを利用して文字列の解析を行うことができます。

物理数値テーブルを作成します。

    create table dbo.Numbers (N int primary key);
    insert into dbo.Numbers
        select top 1000 row_number() over(order by number) from master..spt_values
    go

1000000行のテストテーブルを作成する

    create table #yak (i int identity(1,1) primary key, array varchar(50))

    insert into #yak(array)
        select 'a,b,c' from dbo.Numbers n cross join dbo.Numbers nn
    go

関数を作成する

    create function [dbo].[ufn_ParseArray]
        (   @Input      nvarchar(4000), 
            @Delimiter  char(1) = ',',
            @BaseIdent  int
        )
    returns table as
    return  
        (   select  row_number() over (order by n asc) + (@BaseIdent - 1) [i],
                    substring(@Input, n, charindex(@Delimiter, @Input + @Delimiter, n) - n) s
            from    dbo.Numbers
            where   n <= convert(int, len(@Input)) and
                    substring(@Delimiter + @Input, n, 1) = @Delimiter
        )
    go

使用状況(私のラップトップでは40代で3milの行を出力します)

    select * 
    from #yak 
    cross apply dbo.ufn_ParseArray(array, ',', 1)

掃除

    drop table dbo.Numbers;
    drop function  [dbo].[ufn_ParseArray]

ここでのパフォーマンスは驚くべきものではありませんが、100万行を超える関数を呼び出すことは最良のアイデアではありません。多くの行にまたがる文字列分割を実行する場合、関数を回避します。


2
最良の解決策はIMOですが、その他には何らかの制限があります。これは高速で、多くの要素を持つ長い文字列を解析できます。
Pking

なぜnを降順で注文するのですか?3つの項目があり、1から番号を付け始めた場合、最初の項目は3番、最後の項目は1番になります。これらdescを削除すると、より直感的な結果が得られませんか?
手斧-2014年

1
同意し、ASC方向の方が直感的です。私は、descを使用するparsename()規則に従っていました
Nathan Skerl 2014年

3
これがどのように機能するかについてのいくつかの説明は素晴らしいでしょう
Tim Abell

解析する最大3つのフィールドの1億行のテストでは、ufn_ParseArrayは25分後に完了しませんでしたが、@ NothingsImpossible REVERSE(PARSENAME(REPLACE(REVERSE('Hello John Smith'), ' ', '.'), 1)) から1.5 分で完了しました。@hello_earth 4つ以上のフィールドを持つより長い文字列で、ソリューションをどのように比較しますか?
wwmbes

32

この質問は、文字列分割アプローチではなく、n番目の要素を取得する方法に関するものです。

ここでのすべての回答は、再帰、CTEs、複数CHARINDEXREVERSEおよびPATINDEX発明関数、CLRメソッドの呼び出し、数値テーブルなどを使用して、ある種の文字列分割を行っていCROSS APPLYます。ほとんどの回答は、コードの多くの行をカバーしています。

しかし、実際にn番目の要素を取得するためのアプローチしか必要ない場合は、これを実際のワンライナーとして、UDFを使用せずに、サブ選択を使用することなく行うことができます...そして、追加の利点として、タイプセーフ

スペースで区切られたパート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: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)')

文字列に禁止文字(特にの1つ&><)が含まれている可能性がある場合でも、この方法でそれを行うことができます。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_VALUE、およびがありOPENJSONます。

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-タイプセーフな値を取得する

我々は使用することができ、配列内の配列を単純に倍増を使用することによって[[]]。これにより、型付きのWITH句を使用できます。

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

Re:文字列に禁止文字が含まれている可能性がある場合...部分文字列をそのようにラップするだけ<x><![CDATA[x<&>x]]></x>です。
Salman A

@SalmanA、ええ、- CDATAセクションでもこれに対処できます...しかし、キャスト後は削除されます(text()暗黙的にエスケープに変更されます)。私はフードの下で魔法が好きではないので、私は(SELECT 'Text with <&>' AS [*] FOR XML PATH(''))-アプローチを好むでしょう。これは私にはきれいに見え、とにかく起こります...(CDATAとXMLについてのいくつかの詳細)。
Shnugo

22

これがそれを行うUDFです。区切られた値のテーブルが返され、すべてのシナリオを試したことはありませんが、例は正常に動作します。


CREATE FUNCTION SplitString 
(
    -- Add the parameters for the function here
    @myString varchar(500),
    @deliminator varchar(10)
)
RETURNS 
@ReturnTable TABLE 
(
    -- Add the column definitions for the TABLE variable here
    [id] [int] IDENTITY(1,1) NOT NULL,
    [part] [varchar](50) NULL
)
AS
BEGIN
        Declare @iSpaces int
        Declare @part varchar(50)

        --initialize spaces
        Select @iSpaces = charindex(@deliminator,@myString,0)
        While @iSpaces > 0

        Begin
            Select @part = substring(@myString,0,charindex(@deliminator,@myString,0))

            Insert Into @ReturnTable(part)
            Select @part

    Select @myString = substring(@mystring,charindex(@deliminator,@myString,0)+ len(@deliminator),len(@myString) - charindex(' ',@myString,0))


            Select @iSpaces = charindex(@deliminator,@myString,0)
        end

        If len(@myString) > 0
            Insert Into @ReturnTable
            Select @myString

    RETURN 
END
GO

次のように呼び出します。


Select * From SplitString('Hello John Smith',' ')

編集:以下のようにlen> 1のデリミタを処理するために更新されたソリューション:


select * From SplitString('Hello**John**Smith','**')

select * for dbo.ethos_SplitString_fn( 'guy、wicks、was here'、 '、')id部分では機能しませんでした----------- ------------ -------------------------------------- 1ガイ2ウィック
ガイ

2
lenので引数が末尾のスペースをしている場合、それは正しい番号を返さないだろうと)(気をつけて、例えばLEN(「 - 」)。= 2
ロリー・

機能しない:select * from dbo.SplitString( 'foo、foo test ,,,, foo'、 '、')
cbp

1
cbpを修正します。@ myString = substring(@ mystring、@ iSpaces + len(@deliminator)、len(@myString)-charindex(@ deliminator、@ myString、0))を選択します
Alxwest

16

ここに私は簡単な解決方法を投稿します

CREATE FUNCTION [dbo].[split](
          @delimited NVARCHAR(MAX),
          @delimiter NVARCHAR(100)
        ) RETURNS @t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
        AS
        BEGIN
          DECLARE @xml XML
          SET @xml = N'<t>' + REPLACE(@delimited,@delimiter,'</t><t>') + '</t>'

          INSERT INTO @t(val)
          SELECT  r.value('.','varchar(MAX)') as item
          FROM  @xml.nodes('/t') as records(r)
          RETURN
        END


このような関数を実行します

  select * from dbo.split('Hello John Smith',' ')

私はこのソリューションが好きでした。結果内の指定された列に基づいてスカラー値を返すように拡張されました。
アラン・

これを使用して分割される文字列に「&」が付いて火傷しました
KeithL

10

私の意見では、皆さんはそれをあまりにも複雑にしています。CLR UDFを作成し、それで完了です。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;

public partial class UserDefinedFunctions {
  [SqlFunction]
  public static SqlString SearchString(string Search) {
    List<string> SearchWords = new List<string>();
    foreach (string s in Search.Split(new char[] { ' ' })) {
      if (!s.ToLower().Equals("or") && !s.ToLower().Equals("and")) {
        SearchWords.Add(s);
      }
    }

    return new SqlString(string.Join(" OR ", SearchWords.ToArray()));
  }
};

20
Visual Studioが必要で、サーバーでCLRを有効にし、プロジェクトを作成してコンパイルし、最後にアセンブリをデータベースに追加して使用する必要があるため、これは複雑すぎると思います。しかし、それでも興味深い答えです。
GuillermoGutiérrez2012

3
@ guillegr123、それは複雑である必要はありません。SQLCLR関数とプロシージャのライブラリであるSQL#をダウンロードしてインストールするだけです(無料で!)。SQLsharp.comから入手できます。はい、私は著者ですが、String_Splitは無料版に含まれています。
ソロモンルツキー2013

10

stringvalues()ステートメントについてはどうですか?

DECLARE @str varchar(max)
SET @str = 'Hello John Smith'

DECLARE @separator varchar(max)
SET @separator = ' '

DECLARE @Splited TABLE(id int IDENTITY(1,1), item varchar(max))

SET @str = REPLACE(@str, @separator, '''),(''')
SET @str = 'SELECT * FROM (VALUES(''' + @str + ''')) AS V(A)' 

INSERT INTO @Splited
EXEC(@str)

SELECT * FROM @Splited

結果セットが達成されました。

id  item
1   Hello
2   John
3   Smith

1
私はあなたの答えを使用しましたが、機能しませんでしたが、変更し、これはunion allで機能し
エンジェル

9

私はフレデリックの答えを使用していますが、これはSQL Server 2005では機能しませんでした

私はそれを変更し、私はと使用selectunion allています

DECLARE @str varchar(max)
SET @str = 'Hello John Smith how are you'

DECLARE @separator varchar(max)
SET @separator = ' '

DECLARE @Splited table(id int IDENTITY(1,1), item varchar(max))

SET @str = REPLACE(@str, @separator, ''' UNION ALL SELECT ''')
SET @str = ' SELECT  ''' + @str + '''  ' 

INSERT INTO @Splited
EXEC(@str)

SELECT * FROM @Splited

そして結果セットは:

id  item
1   Hello
2   John
3   Smith
4   how
5   are
6   you

これは私が今までSQLで見たことが本当に素晴らしいです、それは私の仕事のために働きました、そして私は感謝します、ありがとう!
Abdurrahman I.

これは見た目がすっきりとして理解しやすいので、私はこれを見て本当に興奮しましたが、残念ながら、これが原因でUDF内に配置することはできませんEXECEXECストアドプロシージャを暗黙的に呼び出します。UDFでストアドプロシージャを使用することはできません。
Kristen Hammack

これは完璧に動作します!! 私はここから関数(SplitStrings_Moden)の使用を検討していました:これを行うsqlperformance.com/2012/07/t-sql-queries/split-strings#commentsこれはデータを分割して返すのに1分半かかりました4つの口座番号のみを使用する場合の行。私は、アカウント番号のデータをテーブルに左結合してバージョンをテストしましたが、2〜3秒かかりました。大きな違いと完璧に動作します!できればこの20票をあげます!
MattE

8

このパターンは正常に機能し、一般化できます

Convert(xml,'<n>'+Replace(FIELD,'.','</n><n>')+'</n>').value('(/n[INDEX])','TYPE')
                          ^^^^^                                   ^^^^^     ^^^^

FIELDINDEX、およびTYPEに注意してください。

のような識別子を持ついくつかのテーブルをしましょう

sys.message.1234.warning.A45
sys.message.1235.error.O98
....

次に、あなたは書くことができます

SELECT Source         = q.value('(/n[1])', 'varchar(10)'),
       RecordType     = q.value('(/n[2])', 'varchar(20)'),
       RecordNumber   = q.value('(/n[3])', 'int'),
       Status         = q.value('(/n[4])', 'varchar(5)')
FROM   (
         SELECT   q = Convert(xml,'<n>'+Replace(fieldName,'.','</n><n>')+'</n>')
         FROM     some_TABLE
       ) Q

すべてのパーツを分割してキャストします。


これは、特定のタイプにキャストできる唯一のソリューションであり、適度に効率的です(CLRは依然として最も効率的ですが、このアプローチは8 GB、10トークン、10 Mの行テーブルを約9分で処理します(aws m3サーバー、4k iopsプロビジョニングされたドライブ)
Andrew Hill

7

データベースの互換性レベルが130以上の場合、STRING_SPLIT関数とOFFSET FETCH句を使用して、インデックスで特定のアイテムを取得できます。

インデックスN(ゼロベース)のアイテムを取得するには、次のコードを使用できます

SELECT value
FROM STRING_SPLIT('Hello John Smith',' ')
ORDER BY (SELECT NULL)
OFFSET N ROWS
FETCH NEXT 1 ROWS ONLY

データベースの互換性レベルを確認するには、次のコードを実行します。

SELECT compatibility_level  
FROM sys.databases WHERE name = 'YourDBName';

トリックは、最初の項目をスキップし、2番目の項目を返すオフセット1行にあります。インデックスが0ベースであり、@ Xがフェッチするアイテムインデックスを保持する変数である場合、OFFSET @X ROWS
Gorgi Rankovski

わかりました、これは以前は使用していませんでした...わかりました... xml値をタイプセーフにフェッチでき、サブクエリを必要としないので、-splitベースのアプローチを使用しますが、これはいいもの。私の側からの+1
Shnugo

3
ここでの問題は、STRING_SPLITが返される結果の順序を保証しないことです。あなたの項目だから1は、あるいは私の項目1であってもなくてもよい
user1443098

@ GorgiRankovski、v2016以降のSTRING_SPLIT要求を使用します。この場合、OPENJSONまたはを使用する方がはるかに適切JSON_VALUEです。あなたは私の答え
Shnugo

6

私はネット上の解決策を探していましたが、以下がうまくいきました。 参照

そして、あなたはこのような関数を呼び出します:

SELECT * FROM dbo.split('ram shyam hari gopal',' ')

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE FUNCTION [dbo].[Split](@String VARCHAR(8000), @Delimiter CHAR(1))       
RETURNS @temptable TABLE (items VARCHAR(8000))       
AS       
BEGIN       
    DECLARE @idx INT       
    DECLARE @slice VARCHAR(8000)        
    SELECT @idx = 1       
    IF len(@String)<1 OR @String IS NULL  RETURN       
    WHILE @idx!= 0       
    BEGIN       
        SET @idx = charindex(@Delimiter,@String)       
        IF @idx!=0       
            SET @slice = LEFT(@String,@idx - 1)       
        ELSE       
            SET @slice = @String       
        IF(len(@slice)>0)  
            INSERT INTO @temptable(Items) VALUES(@slice)       
        SET @String = RIGHT(@String,len(@String) - @idx)       
        IF len(@String) = 0 break       
    END   
    RETURN       
END

この機能を使用してN番目のアイテムに簡単にアクセスすることはできません。
ビョルンLindqvist

6

さらにもう1つは、デリミタ関数によって文字列のn番目の部分を取得します。

create function GetStringPartByDelimeter (
    @value as nvarchar(max),
    @delimeter as nvarchar(max),
    @position as int
) returns NVARCHAR(MAX) 
AS BEGIN
    declare @startPos as int
    declare @endPos as int
    set @endPos = -1
    while (@position > 0 and @endPos != 0) begin
        set @startPos = @endPos + 1
        set @endPos = charindex(@delimeter, @value, @startPos)

        if(@position = 1) begin
            if(@endPos = 0)
                set @endPos = len(@value) + 1

            return substring(@value, @startPos, @endPos - @startPos)
        end

        set @position = @position - 1
    end

    return null
end

そして使用法:

select dbo.GetStringPartByDelimeter ('a;b;c;d;e', ';', 3)

これは次を返します:

c

この解決策は、選択する必要がある解析済みのテーブルを取得するのではなく、単一の部分文字列を返すオプションとして気に入っています。テーブル結果を使用することには用途がありますが、私が必要な場合にはこれが完全に機能しました。
James H

5

これを試して:

CREATE function [SplitWordList]
(
 @list varchar(8000)
)
returns @t table 
(
 Word varchar(50) not null,
 Position int identity(1,1) not null
)
as begin
  declare 
    @pos int,
    @lpos int,
    @item varchar(100),
    @ignore varchar(100),
    @dl int,
    @a1 int,
    @a2 int,
    @z1 int,
    @z2 int,
    @n1 int,
    @n2 int,
    @c varchar(1),
    @a smallint
  select 
    @a1 = ascii('a'),
    @a2 = ascii('A'),
    @z1 = ascii('z'),
    @z2 = ascii('Z'),
    @n1 = ascii('0'),
    @n2 = ascii('9')
  set @ignore = '''"'
  set @pos = 1
  set @dl = datalength(@list)
  set @lpos = 1
  set @item = ''
  while (@pos <= @dl) begin
    set @c = substring(@list, @pos, 1)
    if (@ignore not like '%' + @c + '%') begin
      set @a = ascii(@c)
      if ((@a >= @a1) and (@a <= @z1))  
        or ((@a >= @a2) and (@a <= @z2))
        or ((@a >= @n1) and (@a <= @n2))
      begin
        set @item = @item + @c
      end else if (@item > '') begin
        insert into @t values (@item)
        set @item = ''
      end
    end 
    set @pos = @pos + 1
  end
  if (@item > '') begin
    insert into @t values (@item)
  end
  return
end

次のようにテストします。

select * from SplitWordList('Hello John Smith')

私はそれを経験しました、そしてそれは私が望むものと完全に似ています!選択した特殊文字を無視するようにカスタマイズすることもできます!
Vikas

5

次の例では、再帰CTEを使用しています

アップデート 18.09.2013

CREATE FUNCTION dbo.SplitStrings_CTE(@List nvarchar(max), @Delimiter nvarchar(1))
RETURNS @returns TABLE (val nvarchar(max), [level] int, PRIMARY KEY CLUSTERED([level]))
AS
BEGIN
;WITH cte AS
 (
  SELECT SUBSTRING(@List, 0, CHARINDEX(@Delimiter,  @List + @Delimiter)) AS val,
         CAST(STUFF(@List + @Delimiter, 1, CHARINDEX(@Delimiter, @List + @Delimiter), '') AS nvarchar(max)) AS stval, 
         1 AS [level]
  UNION ALL
  SELECT SUBSTRING(stval, 0, CHARINDEX(@Delimiter, stval)),
         CAST(STUFF(stval, 1, CHARINDEX(@Delimiter, stval), '') AS nvarchar(max)),
         [level] + 1
  FROM cte
  WHERE stval != ''
  )
  INSERT @returns
  SELECT REPLACE(val, ' ','' ) AS val, [level]
  FROM cte
  WHERE val > ''
  RETURN
END

SQLFiddleのデモ


2


    Alter Function dbo.fn_Split
    (
    @Expression nvarchar(max),
    @Delimiter  nvarchar(20) = ',',
    @Qualifier  char(1) = Null
    )
    RETURNS @Results TABLE (id int IDENTITY(1,1), value nvarchar(max))
    AS
    BEGIN
       /* USAGE
            Select * From dbo.fn_Split('apple pear grape banana orange honeydew cantalope 3 2 1 4', ' ', Null)
            Select * From dbo.fn_Split('1,abc,"Doe, John",4', ',', '"')
            Select * From dbo.fn_Split('Hello 0,"&""&&&&', ',', '"')
       */

       -- Declare Variables
       DECLARE
          @X     xml,
          @Temp  nvarchar(max),
          @Temp2 nvarchar(max),
          @Start int,
          @End   int

       -- HTML Encode @Expression
       Select @Expression = (Select @Expression For XML Path(''))

       -- Find all occurences of @Delimiter within @Qualifier and replace with |||***|||
       While PATINDEX('%' + @Qualifier + '%', @Expression) > 0 AND Len(IsNull(@Qualifier, '')) > 0
       BEGIN
          Select
             -- Starting character position of @Qualifier
             @Start = PATINDEX('%' + @Qualifier + '%', @Expression),
             -- @Expression starting at the @Start position
             @Temp = SubString(@Expression, @Start + 1, LEN(@Expression)-@Start+1),
             -- Next position of @Qualifier within @Expression
             @End = PATINDEX('%' + @Qualifier + '%', @Temp) - 1,
             -- The part of Expression found between the @Qualifiers
             @Temp2 = Case When @End < 0 Then @Temp Else Left(@Temp, @End) End,
             -- New @Expression
             @Expression = REPLACE(@Expression,
                                   @Qualifier + @Temp2 + Case When @End < 0 Then '' Else @Qualifier End,
                                   Replace(@Temp2, @Delimiter, '|||***|||')
                           )
       END

       -- Replace all occurences of @Delimiter within @Expression with '</fn_Split><fn_Split>'
       -- And convert it to XML so we can select from it
       SET
          @X = Cast('<fn_Split>' +
                    Replace(@Expression, @Delimiter, '</fn_Split><fn_Split>') +
                    '</fn_Split>' as xml)

       -- Insert into our returnable table replacing '|||***|||' back to @Delimiter
       INSERT @Results
       SELECT
          "Value" = LTRIM(RTrim(Replace(C.value('.', 'nvarchar(max)'), '|||***|||', @Delimiter)))
       FROM
          @X.nodes('fn_Split') as X(C)

       -- Return our temp table
       RETURN
    END

2

関数を必要とせずに、SQLで文字列を分割できます。

DECLARE @bla varchar(MAX)
SET @bla = 'BED40DFC-F468-46DD-8017-00EF2FA3E4A4,64B59FC5-3F4D-4B0E-9A48-01F3D4F220B0,A611A108-97CA-42F3-A2E1-057165339719,E72D95EA-578F-45FC-88E5-075F66FD726C'

-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT 
    x.XmlCol.value('.', 'varchar(36)') AS val 
FROM 
(
    SELECT 
    CAST('<e>' + REPLACE(@bla, ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b 
CROSS APPLY b.RawXml.nodes('e') x(XmlCol);

任意の文字列(xml特殊文字を含む)をサポートする必要がある場合

DECLARE @bla NVARCHAR(MAX)
SET @bla = '<html>unsafe & safe Utf8CharsDon''tGetEncoded ÄöÜ - "Conex"<html>,Barnes & Noble,abc,def,ghi'

-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT 
    x.XmlCol.value('.', 'nvarchar(MAX)') AS val 
FROM 
(
    SELECT 
    CAST('<e>' + REPLACE((SELECT @bla FOR XML PATH('')), ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b 
CROSS APPLY b.RawXml.nodes('e') x(XmlCol); 

1

私はそれが古い質問であることを知っていますが、誰かが私の解決策から利益を得ることができると思います。

select 
SUBSTRING(column_name,1,CHARINDEX(' ',column_name,1)-1)
,SUBSTRING(SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name))
    ,1
    ,CHARINDEX(' ',SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name)),1)-1)
,SUBSTRING(SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name))
    ,CHARINDEX(' ',SUBSTRING(column_name,CHARINDEX(' ',column_name,1)+1,LEN(column_name)),1)+1
    ,LEN(column_name))
from table_name

SQL FIDDLE

利点:

  • 3つのサブストリングデリミネーターすべてを「」で区切ります。
  • whileループはパフォーマンスを低下させるため、使用しないでください。
  • 結果のすべてのサブストリングが1つの行に表示されるため、ピボットする必要はありません。

制限:

  • 合計数を知っている必要があります。スペース(部分文字列)。

:ソリューションでは、最大Nまでの部分文字列を指定できます。

制限を克服するために、次のrefを使用できます。

しかし、再び上記の解決策はテーブルで使用できません(実際には使用できませんでした)。

もう一度私はこの解決策が誰かを助けることを望みます。

更新: 50000を超えるレコードの場合、パフォーマンスが低下するため、使用することはお勧めできません。LOOPS


1

TVFwith recursive を使用した純粋なセットベースのソリューションCTE。できますしJOINAPPLYこの関数を任意のデータセットに適用できます。

create function [dbo].[SplitStringToResultSet] (@value varchar(max), @separator char(1))
returns table
as return
with r as (
    select value, cast(null as varchar(max)) [x], -1 [no] from (select rtrim(cast(@value as varchar(max))) [value]) as j
    union all
    select right(value, len(value)-case charindex(@separator, value) when 0 then len(value) else charindex(@separator, value) end) [value]
    , left(r.[value], case charindex(@separator, r.value) when 0 then len(r.value) else abs(charindex(@separator, r.[value])-1) end ) [x]
    , [no] + 1 [no]
    from r where value > '')

select ltrim(x) [value], [no] [index] from r where x is not null;
go

使用法:

select *
from [dbo].[SplitStringToResultSet]('Hello John Smith', ' ')
where [index] = 1;

結果:

value   index
-------------
John    1

1

他のほとんどすべての答えは、CPUサイクルを浪費し、不必要なメモリ割り当てを実行する分割される文字列を置き換えることです。

ここでは、文字列分割を行うより良い方法について説明します。http//www.digitalruby.com/split-string-sql-server/

これがコードです:

SET NOCOUNT ON

-- You will want to change nvarchar(MAX) to nvarchar(50), varchar(50) or whatever matches exactly with the string column you will be searching against
DECLARE @SplitStringTable TABLE (Value nvarchar(MAX) NOT NULL)
DECLARE @StringToSplit nvarchar(MAX) = 'your|string|to|split|here'
DECLARE @SplitEndPos int
DECLARE @SplitValue nvarchar(MAX)
DECLARE @SplitDelim nvarchar(1) = '|'
DECLARE @SplitStartPos int = 1

SET @SplitEndPos = CHARINDEX(@SplitDelim, @StringToSplit, @SplitStartPos)

WHILE @SplitEndPos > 0
BEGIN
    SET @SplitValue = SUBSTRING(@StringToSplit, @SplitStartPos, (@SplitEndPos - @SplitStartPos))
    INSERT @SplitStringTable (Value) VALUES (@SplitValue)
    SET @SplitStartPos = @SplitEndPos + 1
    SET @SplitEndPos = CHARINDEX(@SplitDelim, @StringToSplit, @SplitStartPos)
END

SET @SplitValue = SUBSTRING(@StringToSplit, @SplitStartPos, 2147483647)
INSERT @SplitStringTable (Value) VALUES(@SplitValue)

SET NOCOUNT OFF

-- You can select or join with the values in @SplitStringTable at this point.

0

サーバーの痛みを伴う再帰CTEソリューション、テスト

MS SQL Server 2008スキーマのセットアップ

create table Course( Courses varchar(100) );
insert into Course values ('Hello John Smith');

クエリ1

with cte as
   ( select 
        left( Courses, charindex( ' ' , Courses) ) as a_l,
        cast( substring( Courses, 
                         charindex( ' ' , Courses) + 1 , 
                         len(Courses ) ) + ' ' 
              as varchar(100) )  as a_r,
        Courses as a,
        0 as n
     from Course t
    union all
      select 
        left(a_r, charindex( ' ' , a_r) ) as a_l,
        substring( a_r, charindex( ' ' , a_r) + 1 , len(a_R ) ) as a_r,
        cte.a,
        cte.n + 1 as n
    from Course t inner join cte 
         on t.Courses = cte.a and len( a_r ) > 0

   )
select a_l, n from cte
--where N = 1

結果

|    A_L | N |
|--------|---|
| Hello  | 0 |
|  John  | 1 |
| Smith  | 2 |

0

josejuanによるxmlベースの回答に似ていますが、xmlパスを一度だけ処理すると、ピボットが適度に効率的になることがわかりました。

select ID,
    [3] as PathProvidingID,
    [4] as PathProvider,
    [5] as ComponentProvidingID,
    [6] as ComponentProviding,
    [7] as InputRecievingID,
    [8] as InputRecieving,
    [9] as RowsPassed,
    [10] as InputRecieving2
    from
    (
    select id,message,d.* from sysssislog cross apply       ( 
          SELECT Item = y.i.value('(./text())[1]', 'varchar(200)'),
              row_number() over(order by y.i) as rn
          FROM 
          ( 
             SELECT x = CONVERT(XML, '<i>' + REPLACE(Message, ':', '</i><i>') + '</i>').query('.')
          ) AS a CROSS APPLY x.nodes('i') AS y(i)
       ) d
       WHERE event
       = 
       'OnPipelineRowsSent'
    ) as tokens 
    pivot 
    ( max(item) for [rn] in ([3],[4],[5],[6],[7],[8],[9],[10]) 
    ) as data

8:30に走った

select id,
tokens.value('(/n[3])', 'varchar(100)')as PathProvidingID,
tokens.value('(/n[4])', 'varchar(100)') as PathProvider,
tokens.value('(/n[5])', 'varchar(100)') as ComponentProvidingID,
tokens.value('(/n[6])', 'varchar(100)') as ComponentProviding,
tokens.value('(/n[7])', 'varchar(100)') as InputRecievingID,
tokens.value('(/n[8])', 'varchar(100)') as InputRecieving,
tokens.value('(/n[9])', 'varchar(100)') as RowsPassed
 from
(
    select id, Convert(xml,'<n>'+Replace(message,'.','</n><n>')+'</n>') tokens
         from sysssislog 
       WHERE event
       = 
       'OnPipelineRowsSent'
    ) as data

9:20に走った


0
CREATE FUNCTION [dbo].[fnSplitString] 
( 
    @string NVARCHAR(MAX), 
    @delimiter CHAR(1) 
) 
RETURNS @output TABLE(splitdata NVARCHAR(MAX) 
) 
BEGIN 
    DECLARE @start INT, @end INT 
    SELECT @start = 1, @end = CHARINDEX(@delimiter, @string) 
    WHILE @start < LEN(@string) + 1 BEGIN 
        IF @end = 0  
            SET @end = LEN(@string) + 1

        INSERT INTO @output (splitdata)  
        VALUES(SUBSTRING(@string, @start, @end - @start)) 
        SET @start = @end + 1 
        SET @end = CHARINDEX(@delimiter, @string, @start)

    END 
    RETURN 
END

そしてそれを使う

select *from dbo.fnSplitString('Querying SQL Server','')

0

分離されたテキストの一部だけを取得したい場合は、これを使用できます

fromSplitStringSep( 'Word1 wordr2 word3'、 '')から選択*

CREATE function [dbo].[SplitStringSep] 
(
    @str nvarchar(4000), 
    @separator char(1)
)
returns table
AS
return (
    with tokens(p, a, b) AS (
        select 
        1, 
        1, 
        charindex(@separator, @str)
        union all
        select
            p + 1, 
            b + 1, 
            charindex(@separator, @str, b + 1)
        from tokens
        where b > 0
        )
        select
            p-1 zeroBasedOccurance,
            substring(
                @str, 
                a, 
                case when b > 0 then b-a ELSE 4000 end) 
            AS s
        from tokens
  )

0

私はこれを開発しました、

declare @x nvarchar(Max) = 'ali.veli.deli.';
declare @item nvarchar(Max);
declare @splitter char='.';

while CHARINDEX(@splitter,@x) != 0
begin
    set @item = LEFT(@x,CHARINDEX(@splitter,@x))
    set @x    = RIGHT(@x,len(@x)-len(@item) )
     select @item as item, @x as x;
end

あなたが注意すべき唯一の注意はドット「。」です。@xの終わりは常にそこにあるはずです。


0

@NothingsImpossibleソリューションに基づいて構築するか、最も投票数の多い回答(承認済みの回答のすぐ下)にコメントする場合、次の迅速で汚いソリューションが自分のニーズを満たしていることがわかりました。SQLドメイン内にのみ存在するという利点があります。

文字列「first; second; third; fourth; fifth」を指定すると、たとえば、3番目のトークンを取得したいとします。これは、文字列が持つトークンの数がわかっている場合にのみ機能します。この場合は5です。したがって、私の方法は、最後の2つのトークンを削除し(内部クエリ)、最初の2つのトークンを削除します(外部クエリ)

私はこれが醜く、私があった特定の条件をカバーしていることを知っていますが、誰かがそれが役に立つと思った場合に備えて投稿しています。乾杯

select 
    REVERSE(
        SUBSTRING(
            reverse_substring, 
            0, 
            CHARINDEX(';', reverse_substring)
        )
    ) 
from 
(
    select 
        msg,
        SUBSTRING(
            REVERSE(msg), 
            CHARINDEX(
                ';', 
                REVERSE(msg), 
                CHARINDEX(
                    ';',
                    REVERSE(msg)
                )+1
            )+1,
            1000
        ) reverse_substring
    from 
    (
        select 'first;second;third;fourth;fifth' msg
    ) a
) b

これは、文字列がトークンをいくつ持つかがわかっている場合にのみ機能します
重大

0
declare @strng varchar(max)='hello john smith'
select (
    substring(
        @strng,
        charindex(' ', @strng) + 1,
        (
          (charindex(' ', @strng, charindex(' ', @strng) + 1))
          - charindex(' ',@strng)
        )
    ))

0

以降では、SQL Serverの2016年、私たちのstring_split

DECLARE @string varchar(100) = 'Richard, Mike, Mark'

SELECT value FROM string_split(@string, ',')

これは適切ですが、n番目の結果を取得するという問題には対応していません。
Johnie Karr、2017

STRING_SPLIT同じ注文の返品を保証するものではありません。しかし、OPENJSONそうです(私の回答を参照(更新セクション)
Shnugo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.