T-SQLを使用してサブ文字列が最後に出現するインデックスを検索する


127

SQLを使用して文字列が最後に出現するインデックスを見つける簡単な方法はありますか?現在SQL Server 2000を使用しています。基本的に、.NET System.String.LastIndexOfメソッドが提供する機能が必要です。少しグーグルでこれが明らかになりました- 「最後のインデックスを取得する関数」 -しかし、「テキスト」列式を渡した場合は機能しません。他の場所で見つかった他の解決策は、検索しているテキストが1文字である場合にのみ機能します。

たぶん関数を作り直さなければならないでしょう。私がそうするなら、皆さんがそれを見て多分利用できるように、私はここにそれを投稿します。

回答:


33

テキストデータタイプの関数のリストは限られています。

私が提案することができますすべてはで始まるですPATINDEXから、しかし仕事の後方DATALENGTH-1, DATALENGTH-2, DATALENGTH-3あなたが結果を得るか、ゼロ(DATALENGTH-DATALENGTH)で終わるまで、など

これは本当にSQL Server 2000処理できないものです。

他の回答の編集:REVERSEは、SQL Server 2000のテキストデータで使用できる関数のリストにありません


1
ええ、それはかなり厄介です。これは簡単なはずですが、簡単ではありません!
Raj

...これが、SQL 2005が通常の関数を許可するvarchar(max)を備えている理由です
gbn

1
ああ!「varchar型(max)は」私はSQL 2000でそれをしようとしたとき、それが機能しなかった理由を説明するSQL 2005のもの、であるので
ラジ

DATALENGTHは機能しますが、正しい結果を生成できません。
テキーラ2016

@Tequilaなど: DATALENGTH文字数ではなくバイト数を返します。したがって、DATALENGTH文字列の場合、文字列内の文字数の2倍を返しNVARCHARます。 LENただし、末尾の空白を除いた文字数を返します。私は決して使用しないDATALENGTH末尾の空白をすることは重要でない限り、文字の長さの計算のために、私は私のデータ型が一致していることを確実に知る、彼らがいるかどうかVARCHARNVARCHAR
rbsdca

174

簡単な方法ですか?いいえ、しかし私はその逆を使用しました。文字通り。

以前のルーチンでは、特定の文字列が最後に出現する場所を見つけるために、REVERSE()関数を使用し、その後にCHARINDEXを使用し、その後REVERSEを使用して元の順序に戻しました。例えば:

SELECT
   mf.name
  ,mf.physical_name
  ,reverse(left(reverse(physical_name), charindex('\', reverse(physical_name)) -1))
 from sys.master_files mf

は、サブフォルダで深くネストされていても、「物理名」から実際のデータベースファイル名を抽出する方法を示しています。これは1文字(バックスラッシュ)のみを検索しますが、これに基づいてより長い検索文字列を作成できます。

唯一の欠点は、これがTEXTデータ型でどのように機能するかわかりません。私はSQL 2005を使用して数年になりますが、TEXTの操作に精通していません。しかし、LEFTとRIGHTを使用できることを思い出したようです。

フィリップ


1
申し訳ありません-私が2000で作業していたときは戻ったことがないと確信しています。現在、SQL 2000のインストールにアクセスできません。
フィリップケリー

鮮やかさ!この問題をこの方法で攻撃することは考えられなかったでしょう!
Jared

4
良いですね!自分のニーズに合わせて変更しました:email.Substring(0、email.lastIndexOf( '@'))== SELECT LEFT(email、LEN(email)-CHARINDEX( '@'、REVERSE(email)))
Fredrik Johansson

1
このような賢いものがプログラミングがとても楽しい理由です!
Chris

追加のリバースの代わりに、オリジナルでは左ではなく右を使用しないのはなぜ
Phil

108

最も簡単な方法は...

REVERSE(SUBSTRING(REVERSE([field]),0,CHARINDEX('[expr]',REVERSE([field]))))

3
+1一致が見つからない場合、「LEFT関数またはSUBSTRING関数に無効な長さのパラメーターが渡されました」などのエラーが発生しないため
Xilmiki

12
あなた[expr]が1シンボルより長い場合、あなたもそれを逆にする必要があります!
AndriusNaruševičius15年

60

Sqlserver 2005以降を使用している場合、REVERSE関数を何度も使用するとパフォーマンスが低下します。以下のコードの方が効率的です。

DECLARE @FilePath VARCHAR(50) = 'My\Super\Long\String\With\Long\Words'
DECLARE @FindChar VARCHAR(1) = '\'

-- Shows text before last slash
SELECT LEFT(@FilePath, LEN(@FilePath) - CHARINDEX(@FindChar,REVERSE(@FilePath))) AS Before
-- Shows text after last slash
SELECT RIGHT(@FilePath, CHARINDEX(@FindChar,REVERSE(@FilePath))-1) AS After
-- Shows the position of the last slash
SELECT LEN(@FilePath) - CHARINDEX(@FindChar,REVERSE(@FilePath)) AS LastOccuredAt

1
後から見ると明白に思えるかもしれませんが、単一の文字ではなく文字列を検索する場合は、次のようにする必要があります。LEN(@FilePath)-CHARINDEX(REVERSE(@FindString)、REVERSE(@FilePath))
pkExec

14
DECLARE @FilePath VARCHAR(50) = 'My\Super\Long\String\With\Long\Words'
DECLARE @FindChar VARCHAR(1) = '\'

SELECT LEN(@FilePath) - CHARINDEX(@FindChar,REVERSE(@FilePath)) AS LastOccuredAt

8

古いがまだ有効な質問なので、ここに他の人から提供された情報に基づいて私が作成したものがあります。

create function fnLastIndexOf(@text varChar(max),@char varchar(1))
returns int
as
begin
return len(@text) - charindex(@char, reverse(@text)) -1
end

7

これは私にとって非常にうまくいきました。

REVERSE(SUBSTRING(REVERSE([field]), CHARINDEX(REVERSE('[expr]'), REVERSE([field])) + DATALENGTH('[expr]'), DATALENGTH([field])))

6
REVERSE(SUBSTRING(REVERSE(ap_description),CHARINDEX('.',REVERSE(ap_description)),len(ap_description)))  

私にとってはうまくいった


4

うーん、私はこれが古いスレッドであることを知っていますが、タリーテーブルはSQL2000(または他のデータベース)でこれを実行できます。

DECLARE @str CHAR(21),
        @delim CHAR(1)
 SELECT @str = 'Your-delimited-string',
        @delim = '-'

SELECT
    MAX(n) As 'position'
FROM
    dbo._Tally
WHERE
    substring(@str, _Tally.n, 1) = @delim

タリーテーブルは、インクリメントする数値のテーブルです。

substring(@str, _Tally.n, 1) = @delimその後、あなただけの、そのセット内の最大位置を取得し、各区切りの位置を取得します。

タリーテーブルは素晴らしいです。以前にそれらを使用したことがない場合は、SQL Server Central(無料の登録、またはBug Me Not(http://www.bugmenot.com/view/sqlservercentral.com)を使用してください。)を)。

*編集:n <= LEN(TEXT_FIELD)TEXTタイプでLEN()を使用できないため、削除されました。substring(...) = @delim結果がまだ正しいのに残っている限り。


いいね。これは実質的に同じ解決策だと思いますが、GBNが受け入れた回答と同じです。DATALENGTHから差し引かれた整数1、2、3などを格納するためにテーブルを使用していて、最後の文字の後ろではなく最初の文字から順方向に読み取っています。
Michael Petito

2

文字列と部分文字列の両方を逆にし、最初の出現を検索します。


いい視点ね。今は2000を持っていません。できたときにできたかどうか思い出せません。
AK

2

他の回答の一部は実際の文字列を返しますが、実際のインデックスintを知る必要がありました。そして、それを行う答えは、物事を複雑にしすぎるようです。他の回答のいくつかをインスピレーションとして使用して、私は次のことをしました...

まず、関数を作成しました。

CREATE FUNCTION [dbo].[LastIndexOf] (@stringToFind varchar(max), @stringToSearch varchar(max))
RETURNS INT
AS
BEGIN
    RETURN (LEN(@stringToSearch) - CHARINDEX(@stringToFind,REVERSE(@stringToSearch))) + 1
END
GO

次に、クエリでこれを行うだけです:

declare @stringToSearch varchar(max) = 'SomeText: SomeMoreText: SomeLastText'

select dbo.LastIndexOf(':', @stringToSearch)

上記は23(「:」の最後のインデックス)を返します。

これが誰かにとって少し簡単になったことを願っています!


2

これは数年前の質問だと思いますが...

上ではAccess 2010、あなたが使用することができInStrRev()、これを行うために。お役に立てれば。


2

この回答ではMS SQL Server 2008を使用しています(MS SQL Server 2000にアクセスできません)が、OPによると、3つの状況を考慮する必要があります。私が試したことから、ここでは答えはありませんが、3つすべてをカバーしています:

  1. 指定された文字列内の検索文字の最後のインデックスを返します。
  2. 指定された文字列内の検索サブ文字列(単一の文字だけではない)の最後のインデックスを返します。
  3. 検索文字または部分文字列が指定された文字列にない場合は 0

私が思いついた機能は2つのパラメータを取ります:

@String NVARCHAR(MAX) :検索する文字列

@FindString NVARCHAR(MAX) :の最後のインデックスを取得する単一の文字または部分文字列 @String

それは返すINTのいずれかの正の指標であること@FindStringにある@String0の意味@FindStringではありませんが@String

ここでは、関数の機能について説明します。

  1. 初期化@ReturnVal0てい@FindStringないことを示します@String
  2. を使用して@FindStringインのインデックスをチェックします@StringCHARINDEX()
  3. インデックス場合@FindStringでは@Stringある0@ReturnValとして残されています0
  4. @FindStringin のインデックス@String> 0@FindStringis inの場合、次を使用してinの@String最後のインデックスを計算します@FindString@StringREVERSE()
  5. 戻り値@ReturnValのいずれかの最後のインデックスである正の数である @FindString@String0それが示す@FindStringではありません@String

次に、関数作成スクリプトを示します(コピーして貼り付けてください)。

CREATE FUNCTION [dbo].[fn_LastIndexOf] 
(@String NVARCHAR(MAX)
, @FindString NVARCHAR(MAX))
RETURNS INT
AS 
BEGIN
    DECLARE @ReturnVal INT = 0
    IF CHARINDEX(@FindString,@String) > 0
        SET @ReturnVal = (SELECT LEN(@String) - 
        (CHARINDEX(REVERSE(@FindString),REVERSE(@String)) + 
        LEN(@FindString)) + 2)  
    RETURN @ReturnVal
END

以下は、関数を簡単にテストするための少しのコードです。

DECLARE @TestString NVARCHAR(MAX) = 'My_sub2_Super_sub_Long_sub1_String_sub_With_sub_Long_sub_Words_sub2_'
, @TestFindString NVARCHAR(MAX) = 'sub'

SELECT dbo.fn_LastIndexOf(@TestString,@TestFindString)

私は他のバージョンにアクセスできないため、これをMS SQL Server 2008でのみ実行しましたが、私が調べたところ、少なくとも2008+には良いはずです。

楽しい。


1

私はそれが非効率的であることを知っていますが、あなたが見つけたウェブサイトによって提供されるソリューションを使用できるようにtextフィールドをキャストすることを検討しvarcharましたか?textフィールドの長さがあなたの長さをオーバーフローした場合、レコードが切り捨てられる可能性があるため、この解決策が問題を引き起こすことを知っていますvarchar(言うまでもありませんが、あまりパフォーマンスが良くありません)。

データはtextフィールド内にあり(SQL Server 2000を使用しているため)、オプションは限られています。


はい、処理されているデータは頻繁に「varchar」に保持できる最大値を超えるため、「varchar」へのキャストはオプションではありません。あなたの答えをありがとう!
Raj

1

単語の文字列の最後のスペースのインデックスを取得する場合は、この式RIGHT(name、(CHARINDEX( ''、REVERSE(name)、0))を使用して、文字列の最後の単語を返すことができます。ファーストネームやミドルネームのイニシャルを含むフルネームの姓を解析する場合に役立ちます。


1

@indexOf = <whatever characters you are searching for in your string>

@LastIndexOf = LEN([MyField]) - CHARINDEX(@indexOf, REVERSE([MyField]))

テストされていません。インデックスがゼロのため、1ずつずれている可能性がありますが、文字SUBSTRINGから@indexOf文字列の最後まで切り捨てると機能します

SUBSTRING([MyField], 0, @LastIndexOf)


1

このコードは、部分文字列に複数の文字が含まれている場合でも機能します。

DECLARE @FilePath VARCHAR(100) = 'My_sub_Super_sub_Long_sub_String_sub_With_sub_Long_sub_Words'
DECLARE @FindSubstring VARCHAR(5) = '_sub_'

-- Shows text before last substing
SELECT LEFT(@FilePath, LEN(@FilePath) - CHARINDEX(REVERSE(@FindSubstring), REVERSE(@FilePath)) - LEN(@FindSubstring) + 1) AS Before
-- Shows text after last substing
SELECT RIGHT(@FilePath, CHARINDEX(REVERSE(@FindSubstring), REVERSE(@FilePath)) -1) AS After
-- Shows the position of the last substing
SELECT LEN(@FilePath) - CHARINDEX(REVERSE(@FindSubstring), REVERSE(@FilePath)) AS LastOccuredAt

0

フォルダパス内のバックスラッシュの最後からn番目の位置を見つける必要がありました。これが私の解決策です。

/*
http://stackoverflow.com/questions/1024978/find-index-of-last-occurrence-of-a-sub-string-using-t-sql/30904809#30904809
DROP FUNCTION dbo.GetLastIndexOf
*/
CREATE FUNCTION dbo.GetLastIndexOf
(
  @expressionToFind         VARCHAR(MAX)
  ,@expressionToSearch      VARCHAR(8000)
  ,@Occurrence              INT =  1        -- Find the nth last 
)
RETURNS INT
AS
BEGIN

    SELECT  @expressionToSearch = REVERSE(@expressionToSearch)

    DECLARE @LastIndexOf        INT = 0
            ,@IndexOfPartial    INT = -1
            ,@OriginalLength    INT = LEN(@expressionToSearch)
            ,@Iteration         INT = 0

    WHILE (1 = 1)   -- Poor man's do-while
    BEGIN
        SELECT @IndexOfPartial  = CHARINDEX(@expressionToFind, @expressionToSearch)

        IF (@IndexOfPartial = 0) 
        BEGIN
            IF (@Iteration = 0) -- Need to compensate for dropping out early
            BEGIN
                SELECT @LastIndexOf = @OriginalLength  + 1
            END
            BREAK;
        END

        IF (@Occurrence > 0)
        BEGIN
            SELECT @expressionToSearch = SUBSTRING(@expressionToSearch, @IndexOfPartial + 1, LEN(@expressionToSearch) - @IndexOfPartial - 1)
        END

        SELECT  @LastIndexOf = @LastIndexOf + @IndexOfPartial
                ,@Occurrence = @Occurrence - 1
                ,@Iteration = @Iteration + 1

        IF (@Occurrence = 0) BREAK;
    END

    SELECT @LastIndexOf = @OriginalLength - @LastIndexOf + 1 -- Invert due to reverse
    RETURN @LastIndexOf 
END
GO

GRANT EXECUTE ON GetLastIndexOf TO public
GO

これが合格した私のテストケースです

SELECT dbo.GetLastIndexOf('f','123456789\123456789\', 1) as indexOf -- expect 0 (no instances)
SELECT dbo.GetLastIndexOf('\','123456789\123456789\', 1) as indexOf -- expect 20
SELECT dbo.GetLastIndexOf('\','123456789\123456789\', 2) as indexOf -- expect 10
SELECT dbo.GetLastIndexOf('\','1234\6789\123456789\', 3) as indexOf -- expect 5

0

区切り文字が最後に出現する前の部分を取得するには(使用法のNVARCHARためにのみ機能しDATALENGTHます):

DECLARE @Fullstring NVARCHAR(30) = '12.345.67890.ABC';

DECLARE @Delimiter CHAR(1) = '.';

SELECT SUBSTRING(@Fullstring, 1, DATALENGTH(@Fullstring)/2 - CHARINDEX(@Delimiter, REVERSE(@Fullstring)));

0

この回答はOPの要件を満たしています。具体的には、針を1文字以上にすることができ、haystackに針が見つからない場合でもエラーは生成されません。他の回答のほとんど(すべて?)はこれらのエッジケースを処理しなかったように思えました。さらに、ネイティブMS SQLサーバーのCharIndex関数によって提供される「開始位置」引数を追加しました。左から右ではなく右から左に処理することを除いて、CharIndexの仕様を正確に反映しようとしました。たとえば、needleまたはhaystackのいずれかがnullの場合はnullを返し、needleがhaystackで見つからない場合は0を返します。私が回避できなかったことの1つは、組み込み関数では3番目のパラメーターがオプションであることです。SQL Serverのユーザー定義関数では、関数が「EXEC」を使用して呼び出されない限り、すべてのパラメーターを呼び出しで提供する必要があります。。3番目のパラメーターはパラメーターリストに含める必要がありますが、値を指定しなくても、プレースホルダーとしてキーワード「default」を指定できます(以下の例を参照)。この関数から3番目のパラメーターを削除する方が望ましくない場合は、必要な場合に追加するよりも簡単なので、ここに開始点として含めました。

create function dbo.lastCharIndex(
 @needle as varchar(max),
 @haystack as varchar(max),
 @offset as bigint=1
) returns bigint as begin
 declare @position as bigint
 if @needle is null or @haystack is null return null
 set @position=charindex(reverse(@needle),reverse(@haystack),@offset)
 if @position=0 return 0
 return (len(@haystack)-(@position+len(@needle)-1))+1
end
go

select dbo.lastCharIndex('xyz','SQL SERVER 2000 USES ANSI SQL',default) -- returns 0
select dbo.lastCharIndex('SQL','SQL SERVER 2000 USES ANSI SQL',default) -- returns 27
select dbo.lastCharIndex('SQL','SQL SERVER 2000 USES ANSI SQL',1) -- returns 27
select dbo.lastCharIndex('SQL','SQL SERVER 2000 USES ANSI SQL',11) -- returns 1

0

まったく同じ要件があり、REVERSE機能も欠けている別の種類のデータベースに対するものである私の類​​似の問題の解決策を探しているときに、このスレッドに出くわしました。

私の場合、これはOpenEdge(Progress)データベース用で、構文が少し異なります。これにより、ほとんどのOracle型データベースが提供するINSTR機能を利用できるようになりました。

だから私は次のコードを思いついた:

SELECT 
  INSTR(foo.filepath, '/',1, LENGTH(foo.filepath) - LENGTH( REPLACE( foo.filepath, '/',  ''))) AS IndexOfLastSlash 
FROM foo

ただし、私の特定の状況(OpenEdge(Progress)データベースであるため)では、文字を空の文字に置き換えると元の文字列と同じ長さになるため、これは望ましい動作になりませんでした。これは私にはあまり意味がありませんが、以下のコードで問題を回避することができました:

SELECT 
  INSTR(foo.filepath, '/',1, LENGTH( REPLACE( foo.filepath, '/',  'XX')) - LENGTH(foo.filepath))  AS IndexOfLastSlash 
FROM foo

プロパティを提供する関数に代わるものがないため、このコードはT-SQLの問題を解決しないことを理解しました。INSTROccurence

念のため、このスカラー関数を作成するために必要なコードを追加して、上記の例と同じように使用できるようにします。

  -- Drop the function if it already exists
  IF OBJECT_ID('INSTR', 'FN') IS NOT NULL
    DROP FUNCTION INSTR
  GO

  -- User-defined function to implement Oracle INSTR in SQL Server
  CREATE FUNCTION INSTR (@str VARCHAR(8000), @substr VARCHAR(255), @start INT, @occurrence INT)
  RETURNS INT
  AS
  BEGIN
    DECLARE @found INT = @occurrence,
            @pos INT = @start;

    WHILE 1=1 
    BEGIN
        -- Find the next occurrence
        SET @pos = CHARINDEX(@substr, @str, @pos);

        -- Nothing found
        IF @pos IS NULL OR @pos = 0
            RETURN @pos;

        -- The required occurrence found
        IF @found = 1
            BREAK;

        -- Prepare to find another one occurrence
        SET @found = @found - 1;
        SET @pos = @pos + 1;
    END

    RETURN @pos;
  END
  GO

明らかなことを避けるために、REVERSE関数が使用可能な場合は、このスカラー関数を作成する必要はなく、次のように必要な結果を取得できます。

SELECT
  LEN(foo.filepath) - CHARINDEX('/', REVERSE(foo.filepath))+1 AS LastIndexOfSlash 
FROM foo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.