Finden Sie den Index des letzten Auftretens einer Unterzeichenfolge mithilfe von T-SQL


127

Gibt es eine einfache Möglichkeit, den Index des letzten Auftretens einer Zeichenfolge mithilfe von SQL zu ermitteln? Ich verwende gerade SQL Server 2000. Ich brauche grundsätzlich die Funktionalität, die die .NET- System.String.LastIndexOfMethode bietet. Ein wenig googeln ergab dies - Funktion zum Abrufen des letzten Index -, aber das funktioniert nicht, wenn Sie einen Spaltenausdruck "Text" übergeben. Andere an anderer Stelle gefundene Lösungen funktionieren nur, solange der gesuchte Text 1 Zeichen lang ist.

Ich werde wahrscheinlich eine Funktion erfinden müssen. Wenn ich das tue, werde ich es hier posten, damit ihr es euch ansehen und vielleicht nutzen könnt.

Antworten:


33

Sie sind auf eine kleine Liste von Funktionen für den Textdatentyp beschränkt.

Alles, was ich vorschlagen kann, ist, mit zu beginnen PATINDEX, aber von DATALENGTH-1, DATALENGTH-2, DATALENGTH-3usw. rückwärts zu arbeiten, bis Sie ein Ergebnis erhalten oder bei Null enden (DATALENGTH-DATALENGTH)

Das ist wirklich etwas, mit dem man SQL Server 2000einfach nicht umgehen kann.

Für andere Antworten bearbeiten : REVERSE ist nicht in der Liste der Funktionen enthalten, die mit Textdaten in SQL Server 2000 verwendet werden können


1
Ja, es ist ziemlich umständlich. Dies scheint einfach zu sein, nur ist es nicht!
Raj

... aus diesem Grund hat SQL 2005 varchar (max), um normale Funktionen zuzulassen
gbn

1
Ah! "varchar (max)" ist also eine SQL 2005-Sache, was erklärt, warum es nicht funktioniert hat, als ich es unter SQL 2000 ausprobiert habe.
Raj

DATALENGTH liefert für mich nicht das richtige Ergebnis, obwohl LENGTH funktioniert.
Tequila

@Tequila und andere: Gibt DATALENGTHdie Anzahl der Bytes und nicht der Zeichen zurück. Gibt daher DATALENGTH2 x die Anzahl der Zeichen in einer Zeichenfolge für NVARCHARZeichenfolgen zurück. LENGibt jedoch die Anzahl der Zeichen abzüglich aller nachgestellten Leerzeichen zurück . Ich verwende es nie DATALENGTHfür die Berechnung der VARCHARNVARCHAR
Zeichenlänge

174

Einfacher Weg? Nein, aber ich habe das Gegenteil verwendet. Buchstäblich.

In früheren Routinen habe ich, um das letzte Vorkommen einer bestimmten Zeichenfolge zu ermitteln, die Funktion REVERSE () verwendet, gefolgt von CHARINDEX, gefolgt von REVERSE, um die ursprüngliche Reihenfolge wiederherzustellen. Zum Beispiel:

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

zeigt, wie die tatsächlichen Datenbankdateinamen aus ihren "physischen Namen" extrahiert werden, unabhängig davon, wie tief sie in Unterordnern verschachtelt sind. Dies sucht zwar nur nach einem Zeichen (dem Backslash), aber Sie können darauf für längere Suchzeichenfolgen aufbauen.

Der einzige Nachteil ist, ich weiß nicht, wie gut dies bei TEXT-Datentypen funktioniert. Ich bin jetzt seit ein paar Jahren in SQL 2005 und bin nicht mehr mit der Arbeit mit TEXT vertraut - aber ich erinnere mich, dass Sie LEFT und RIGHT verwenden können?

Philip


1
Entschuldigung - ich bin mir ziemlich sicher, dass ich es nie getan habe, als ich mit 2000 gearbeitet habe, und ich habe derzeit keinen Zugriff auf SQL 2000-Installationen.
Philip Kelley

Brillant! Ich hätte nie gedacht, dieses Problem auf diese Weise anzugreifen!
Jared

4
Schön! Ich habe für meine eigenen Bedürfnisse geändert: email.Substring (0, email.lastIndexOf ('@')) == SELECT LEFT (E-Mail, LEN (E-Mail) -CHARINDEX ('@', REVERSE (E-Mail)))
Fredrik Johansson

1
Cleveres Zeug wie dieses ist der Grund, warum Programmieren so viel Spaß macht!
Chris

Warum nicht einfach rechts statt links auf dem Original anstelle einer zusätzlichen Rückseite verwenden
Phil

108

Der einfachste Weg ist ....

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

3
+1 Weil KEIN Feuerfehler wie 'Ungültiger Längenparameter an die LEFT- oder SUBSTRING-Funktion übergeben', wenn keine Übereinstimmung gefunden wurde
Xilmiki

12
Wenn Ihr [expr]Symbol länger als 1 ist, müssen Sie es auch umkehren!
Andrius Naruševičius

60

Wenn Sie Sqlserver 2005 oder höher verwenden, wirkt sich die REVERSEmehrfache Verwendung der Funktion nachteilig auf die Leistung aus. Der folgende Code ist effizienter.

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
Im Nachhinein mag es offensichtlich erscheinen, aber wenn Sie nach einer Zeichenfolge anstelle eines einzelnen Zeichens suchen, müssen Sie Folgendes tun: 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

Alte, aber immer noch gültige Frage, also hier ist, was ich basierend auf den Informationen erstellt habe, die von anderen hier bereitgestellt wurden.

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

7

Das hat bei mir sehr gut funktioniert.

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)))  

hat besser für mich gearbeitet


4

Hmm, ich weiß, dass dies ein alter Thread ist, aber eine Tally-Tabelle könnte dies in SQL2000 (oder einer anderen Datenbank) tun:

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

Eine Tally-Tabelle ist nur eine Tabelle mit inkrementierenden Zahlen.

Das substring(@str, _Tally.n, 1) = @delimerhält die Position jedes Trennzeichens, dann erhalten Sie nur die maximale Position in diesem Satz.

Tally Tische sind fantastisch. Wenn Sie sie noch nicht verwendet haben, gibt es einen guten Artikel über SQL Server Central (Free reg, oder verwenden Sie einfach Bug Me Not ( http://www.bugmenot.com/view/sqlservercentral.com )).

* EDIT: Entfernt n <= LEN(TEXT_FIELD), da Sie LEN () nicht für den TEXT-Typ verwenden können. Solange das substring(...) = @delimbleibt, ist das Ergebnis immer noch korrekt.


Nett. Ich denke, dies ist tatsächlich die gleiche Lösung wie die von gbn akzeptierte Antwort. Sie verwenden nur eine Tabelle, um die Ganzzahlen 1, 2, 3 usw. zu speichern, die von DATALENGTH subtrahiert werden, und lesen vom ersten Zeichen vorwärts statt vom letzten Zeichen zurück.
Michael Petito

2

Kehren Sie sowohl Ihren String als auch Ihren Teilstring um und suchen Sie dann nach dem ersten Vorkommen.


Guter Punkt. Ich habe jetzt kein Jahr 2000 und kann mich nicht erinnern, ob ich es damals geschafft habe.
AK

2

Einige der anderen Antworten geben eine tatsächliche Zeichenfolge zurück, während ich den tatsächlichen Index int besser kennen musste. Und die Antworten, die das tun, scheinen die Dinge zu komplizieren. Ich habe einige der anderen Antworten als Inspiration verwendet und Folgendes getan ...

Zuerst habe ich eine Funktion erstellt:

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

Dann können Sie in Ihrer Abfrage einfach Folgendes tun:

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

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

Das obige sollte 23 zurückgeben (der letzte Index von ':')

Hoffe das hat es jemandem etwas leichter gemacht!


2

Mir ist klar, dass dies eine mehrere Jahre alte Frage ist, aber ...

Ein Access 2010können Sie dazu verwenden InStrRev(). Hoffe das hilft.


2

Diese Antwort verwendet MS SQL Server 2008 (ich habe keinen Zugriff auf MS SQL Server 2000), aber die Art und Weise, wie ich es laut OP sehe, sind drei Situationen, die berücksichtigt werden müssen. Nach dem, was ich versucht habe, deckt hier keine Antwort alle drei ab:

  1. Gibt den letzten Index eines Suchzeichens in einer bestimmten Zeichenfolge zurück.
  2. Gibt den letzten Index einer Suchunterzeichenfolge (mehr als nur ein einzelnes Zeichen) in einer bestimmten Zeichenfolge zurück.
  3. Wenn sich das Suchzeichen oder die Unterzeichenfolge nicht in der angegebenen Zeichenfolge befindet, wird zurückgegeben 0

Die Funktion, die ich mir ausgedacht habe, benötigt zwei Parameter:

@String NVARCHAR(MAX) : Die zu durchsuchende Zeichenfolge

@FindString NVARCHAR(MAX) : Entweder ein einzelnes Zeichen oder eine Unterzeichenfolge, um den letzten Index von in zu erhalten @String

Es wird ein Wert zurückgegeben INT, der entweder der positive Index von @FindStringin ist @Stringoder dies 0bedeutet@FindString die nicht in ist@String

Hier ist eine Erklärung, was die Funktion tut:

  1. Initialisiert, @ReturnValum anzuzeigen , 0dass dies @FindStringnicht der Fall ist@String
  2. Überprüft den Index des @FindStringIn @StringmitCHARINDEX()
  3. Wenn der Index von @FindStringin @Stringist 0, @ReturnValbleibt als0
  4. Wenn der Index des @FindStringin @Stringist > 0, @FindStringin ist , @Stringso dass es den letzten Index berechnet @FindStringin @Stringunter Verwendung vonREVERSE()
  5. Gibt @ReturnValentweder eine positive Zahl zurück, die der letzte Index von @FindStringin ist, @Stringoder gibt an 0, dass @FindStringnicht in ist@String

Hier ist das Skript zum Erstellen von Funktionen (fertig zum Kopieren und Einfügen):

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

Hier ist ein kleines bisschen, das die Funktion bequem testet:

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)

Ich habe dies nur unter MS SQL Server 2008 ausgeführt, da ich keinen Zugriff auf eine andere Version habe, aber von dem, was ich mir angesehen habe, sollte dies zumindest für 2008+ gut sein.

Genießen.


1

Ich weiß, dass es ineffizient sein wird, aber haben Sie darüber nachgedacht, das textFeld zu varcharbesetzen, damit Sie die Lösung verwenden können, die von der von Ihnen gefundenen Website bereitgestellt wird? Ich weiß, dass diese Lösung Probleme verursachen würde, da Sie den Datensatz möglicherweise abschneiden könnten, wenn die Länge im textFeld die Länge Ihres Datensatzes überschreitetvarchar (ganz zu schweigen davon, dass dies nicht sehr leistungsfähig wäre).

Da sich Ihre Daten in einem textFeld befinden (und Sie SQL Server 2000 verwenden), sind Ihre Optionen begrenzt.


Ja, das Umwandeln in "varchar" ist keine Option, da die verarbeiteten Daten häufig das Maximum überschreiten, das in einem "varchar" gespeichert werden kann. Vielen Dank für Ihre Antwort!
Raj

1

Wenn Sie den Index des letzten Leerzeichens in einer Wortfolge abrufen möchten, können Sie diesen Ausdruck RIGHT (name, (CHARINDEX ('', REVERSE (name), 0)) verwenden, um das letzte Wort in der Zeichenfolge zurückzugeben ist hilfreich, wenn Sie den Nachnamen eines vollständigen Namens analysieren möchten, der Initialen für den Vor- und / oder Zweitnamen enthält.


1

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

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

Nicht getestet, es kann sein, dass es wegen des Nullindex um eins abweicht, funktioniert aber in SUBSTRINGFunktion, wenn Sie von @indexOfZeichen bis zum Ende Ihrer Zeichenfolge abhacken

SUBSTRING([MyField], 0, @LastIndexOf)


1

Dieser Code funktioniert auch dann, wenn der Teilstring mehr als 1 Zeichen enthält.

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

Ich musste die n-te letzte Position eines Backslashs in einem Ordnerpfad finden. Hier ist meine Lösung.

/*
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

Hier sind meine Testfälle, die bestehen

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

So erhalten Sie das Teil vor dem letzten Auftreten des Trennzeichens (funktioniert nur NVARCHARaufgrund der DATALENGTHVerwendung):

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

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

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

0

Diese Antwort entspricht den Anforderungen des OP. Insbesondere erlaubt es der Nadel, mehr als ein einzelnes Zeichen zu sein, und es erzeugt keinen Fehler, wenn die Nadel nicht im Heuhaufen gefunden wird. Es schien mir, dass die meisten (alle?) Der anderen Antworten diese Randfälle nicht behandelten. Darüber hinaus habe ich das Argument "Startposition" hinzugefügt, das von der nativen MS SQL Server CharIndex-Funktion bereitgestellt wird. Ich habe versucht, die Spezifikation für CharIndex genau zu spiegeln, außer von rechts nach links statt von links nach rechts zu verarbeiten. zB gebe ich null zurück, wenn entweder Nadel oder Heuhaufen null ist, und ich gebe Null zurück, wenn Nadel nicht im Heuhaufen gefunden wird. Eine Sache, die ich nicht umgehen konnte, ist, dass mit der eingebauten Funktion der dritte Parameter optional ist. Bei benutzerdefinierten SQL Server-Funktionen müssen alle Parameter im Aufruf angegeben werden, es sei denn, die Funktion wird mit "EXEC" aufgerufen. . Während der dritte Parameter in der Parameterliste enthalten sein muss, können Sie das Schlüsselwort "default" als Platzhalter dafür angeben, ohne ihm einen Wert zu geben (siehe Beispiele unten). Da es einfacher ist, den dritten Parameter aus dieser Funktion zu entfernen, wenn dies nicht gewünscht ist, als ihn bei Bedarf hinzuzufügen, habe ich ihn hier als Ausgangspunkt aufgenommen.

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

Ich bin auf diesen Thread gestoßen, als ich nach einer Lösung für mein ähnliches Problem gesucht habe, das genau die gleichen Anforderungen hatte, aber für eine andere Art von Datenbank war, der auch das fehlte REVERSE Funktion .

In meinem Fall war dies für eine OpenEdge (Progress) -Datenbank, die eine etwas andere Syntax hat. Dies stellte INSTRmir die Funktion zur Verfügung, die die meisten von Oracle typisierten Datenbanken bieten .

Also habe ich mir folgenden Code ausgedacht:

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

Für meine spezielle Situation (als OpenEdge (Progress) -Datenbank) führte dies jedoch nicht zum gewünschten Verhalten, da das Ersetzen des Zeichens durch ein leeres Zeichen dieselbe Länge wie die ursprüngliche Zeichenfolge ergab. Das macht für mich nicht viel Sinn, aber ich konnte das Problem mit dem folgenden Code umgehen:

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

Jetzt verstehe ich, dass dieser Code das Problem für T-SQL nicht lösen wird, da es keine Alternative zu der INSTRFunktion gibt, die das bietetOccurence Eigenschaft .

Um genau zu sein, füge ich den Code hinzu, der zum Erstellen dieser Skalarfunktion erforderlich ist, damit er auf die gleiche Weise wie in den obigen Beispielen verwendet werden kann.

  -- 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

Um das Offensichtliche zu vermeiden, REVERSEmüssen Sie diese Skalarfunktion nicht erstellen , wenn die Funktion verfügbar ist, und Sie können einfach das gewünschte Ergebnis wie folgt erhalten:

SELECT
  LEN(foo.filepath) - CHARINDEX('/', REVERSE(foo.filepath))+1 AS LastIndexOfSlash 
FROM foo
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.