Trova l'indice dell'ultima occorrenza di una sottostringa usando T-SQL


128

Esiste un modo semplice per trovare l'indice dell'ultima occorrenza di una stringa usando SQL? Sto usando SQL Server 2000 in questo momento. Fondamentalmente ho bisogno della funzionalità System.String.LastIndexOffornita dal metodo .NET . Un piccolo google ha rivelato questo - Funzione per recuperare l'ultimo indice - ma non funziona se si passa un'espressione di colonna "testo". Altre soluzioni trovate altrove funzionano solo finché il testo che stai cercando è lungo 1 carattere.

Probabilmente dovrò preparare una funzione. Se lo faccio, lo posterò qui in modo che la gente possa guardarlo e magari utilizzarlo.

Risposte:


33

Sei limitato a un piccolo elenco di funzioni per il tipo di dati di testo.

Tutto quello che posso suggerire è iniziare PATINDEX, ma lavorare all'indietro da DATALENGTH-1, DATALENGTH-2, DATALENGTH-3ecc. Fino a quando non si ottiene un risultato o si finisce con zero (DATALENGTH-DATALENGTH)

Questo è davvero qualcosa che SQL Server 2000semplicemente non può gestire.

Modifica per altre risposte : REVERSE non è nell'elenco delle funzioni che possono essere utilizzate con i dati di testo in SQL Server 2000


1
Sì, è piuttosto imbarazzante. Sembra che dovrebbe essere semplice, solo che non lo è!
Raj,

... ecco perché SQL 2005 ha varchar (max) per consentire le normali funzioni
gbn

1
Ah! quindi "varchar (max)" è una cosa di SQL 2005, il che spiega perché non ha funzionato quando l'ho provato su SQL 2000.
Raj

DATALENGTH non riesce a produrre il risultato corretto per me, sebbene LENGTH funzioni.
Tequila,

@Tequila e altri: DATALENGTHrestituisce il numero di byte, non i caratteri. Pertanto, DATALENGTHrestituisce 2 volte il numero di caratteri in una stringa per le NVARCHARstringhe. LEN, tuttavia, restituisce il numero di caratteri, meno qualsiasi spazio bianco finale . Non uso mai DATALENGTHper il calcolo della lunghezza dei caratteri a meno che lo spazio bianco finale non sia significativo e so con certezza che i miei tipi di dati sono coerenti, siano essi VARCHARoNVARCHAR
rbsdca

175

Modo semplice? No, ma ho usato il contrario. Letteralmente.

Nelle routine precedenti, per trovare l'ultima occorrenza di una determinata stringa, ho usato la funzione REVERSE (), seguito CHARINDEX, seguito ancora da REVERSE per ripristinare l'ordine originale. Per esempio:

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

mostra come estrarre i nomi dei file di database effettivi dai loro "nomi fisici", non importa quanto profondamente nidificati nelle sottocartelle. Questo cerca solo un carattere (la barra rovesciata), ma puoi basarti su questo per stringhe di ricerca più lunghe.

L'unico aspetto negativo è che non so quanto funzionerà bene con i tipi di dati TEXT. Sono su SQL 2005 da alcuni anni e non ho più dimestichezza con il lavoro con TEXT - ma mi sembra di ricordare che potresti usare LEFT e RIGHT su di esso?

Philip


1
Siamo spiacenti, sono abbastanza sicuro di non averlo mai fatto quando stavo lavorando con 2000 e al momento non ho accesso a nessuna installazione di SQL 2000.
Philip Kelley,

Brillante! Non avrei mai pensato di attaccare questo problema in questo modo!
Jared,

4
Ben fatto! Ho modificato per le mie esigenze: email.Substring (0, email.lastIndexOf ('@')) == SELEZIONA SINISTRA (email, LEN (email) -CHARINDEX ('@', REVERSE (email)))
Fredrik Johansson

1
Roba intelligente come questa è la ragione per cui la programmazione è così divertente!
Chris,

perché non usare semplicemente a destra anziché a sinistra sull'originale anziché un rovescio in più
Phil

108

Il modo più semplice è ....

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

3
+1 Perché NON viene generato un errore come "Parametro lunghezza non valido passato alla funzione SINISTRA o SUBSTRING" se non viene trovata alcuna corrispondenza
Xilmiki

12
Se il tuo [expr]simbolo è più lungo di 1, devi anche invertirlo!
Andrius Naruševičius,

60

Se si utilizza Sqlserver 2005 o versioni successive, l'utilizzo della REVERSEfunzione più volte è dannoso per le prestazioni, mentre il codice di seguito è più efficiente.

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
Con il senno di poi potrebbe sembrare ovvio, ma se stai cercando una stringa anziché un singolo carattere, devi fare: 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

Domanda vecchia ma ancora valida, quindi ecco cosa ho creato sulla base delle informazioni fornite da altri qui.

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

7

Questo ha funzionato molto bene per me.

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

ha funzionato meglio per me


4

Hmm, so che questo è un vecchio thread, ma una tabella di conteggio potrebbe farlo in SQL2000 (o qualsiasi altro database):

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

Una tabella di conteggio è solo una tabella di numeri incrementali.

Il substring(@str, _Tally.n, 1) = @delimottiene la posizione di ogni delimitatore, quindi basta avere la posizione massima in quella retata.

I tavoli di Tally sono fantastici. Se non li hai mai usati prima, c'è un buon articolo su SQL Server Central (Free reg, o semplicemente usa Bug Me Not ( http://www.bugmenot.com/view/sqlservercentral.com )).

* EDIT: rimosso n <= LEN(TEXT_FIELD), in quanto non è possibile utilizzare LEN () sul tipo TESTO. Fintanto che substring(...) = @delimrimane, anche se il risultato è ancora corretto.


Bello. Penso che questa sia effettivamente la stessa soluzione della risposta accettata da gbn; stai solo usando una tabella per memorizzare gli interi 1, 2, 3 ecc. che vengono sottratti da DATALENGTH e stai leggendo dal primo carattere in avanti invece che dall'ultimo carattere indietro.
Michael Petito,

2

Invertire sia la stringa che la sottostringa, quindi cercare la prima occorrenza.


Buon punto. Non ho 2000 ora, e non ricordo se potevo farlo quando l'ho fatto.
AK,

2

Alcune delle altre risposte restituiscono una stringa effettiva mentre avevo più bisogno di conoscere l'indice reale int. E le risposte che lo fanno sembrano complicare troppo le cose. Usando alcune delle altre risposte come ispirazione, ho fatto quanto segue ...

Innanzitutto, ho creato una funzione:

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

Quindi, nella tua query puoi semplicemente fare questo:

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

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

Quanto sopra dovrebbe restituire 23 (l'ultimo indice di ':')

Spero che questo abbia reso un po 'più facile per qualcuno!


2

Mi rendo conto che questa è una domanda di diversi anni, ma ...

Su Access 2010, puoi usare InStrRev()per fare questo. Spero che questo ti aiuti.


2

Questa risposta utilizza MS SQL Server 2008 (non ho accesso a MS SQL Server 2000), ma il modo in cui la vedo secondo il PO sono 3 situazioni da prendere in considerazione. Da quello che ho provato nessuna risposta qui copre tutti e 3:

  1. Restituisce l'ultimo indice di un carattere di ricerca in una determinata stringa.
  2. Restituisce l'ultimo indice di una sottostringa di ricerca (più di un solo carattere) in una determinata stringa.
  3. Se il carattere di ricerca o la stringa secondaria non si trova nella stringa data restituita 0

La funzione che ho creato richiede 2 parametri:

@String NVARCHAR(MAX) : La stringa da cercare

@FindString NVARCHAR(MAX) : Un singolo carattere o una sottostringa per ottenere l'ultimo indice di in @String

Restituisce un INTindice positivo di @FindStringin @Stringo un 0significato che @FindStringnon è in@String

Ecco una spiegazione di cosa fa la funzione:

  1. Inizializza @ReturnValper 0indicare che @FindStringnon è presente@String
  2. Verifica l'indice dell'in @FindStringin @StringutilizzandoCHARINDEX()
  3. Se l'indice di @FindStringin @Stringè 0, @ReturnValviene lasciato come0
  4. Se l'indice di @FindStringin @Stringè > 0, @FindStringè in @String, calcola l'ultimo indice di @FindStringin @StringusandoREVERSE()
  5. Restituisce @ReturnValun numero positivo che è l'ultimo indice di @FindStringin @Stringo 0indica che @FindStringnon è in@String

Ecco lo script della funzione di creazione (copia e incolla pronti):

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

Ecco un po 'che verifica comodamente la funzione:

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)

L'ho eseguito solo su MS SQL Server 2008 perché non ho accesso a nessun'altra versione ma da quello che ho esaminato dovrebbe essere valido almeno per il 2008+.

Godere.


1

So che sarà inefficiente, ma hai pensato di lanciare il textcampo in varcharmodo da poter utilizzare la soluzione fornita dal sito Web che hai trovato? So che questa soluzione creerebbe problemi in quanto potresti potenzialmente troncare il record se la lunghezza nel textcampo traboccasse la lunghezza del tuo varchar(per non parlare del fatto che non sarebbe molto performante).

Poiché i tuoi dati sono all'interno di un textcampo (e stai utilizzando SQL Server 2000) le tue opzioni sono limitate.


Sì, il cast su "varchar" non è un'opzione poiché i dati elaborati spesso superano il massimo che può essere conservato in un "varchar". Grazie per la tua risposta però!
Raj,

1

Se vuoi ottenere l'indice dell'ultimo spazio in una stringa di parole, puoi usare questa espressione DESTRA (nome, (CHARINDEX ('', REVERSE (nome), 0)) per restituire l'ultima parola nella stringa. è utile se si desidera analizzare il cognome di un nome completo che include le iniziali per il nome e / o secondo nome.


1

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

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

Non sono stati testati, potrebbe essere disattivato da uno a causa dell'indice zero, ma funziona in SUBSTRINGfunzione quando si taglia da @indexOfcaratteri alla fine della stringa

SUBSTRING([MyField], 0, @LastIndexOf)


1

Questo codice funziona anche se la sottostringa contiene più di 1 carattere.

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

Avevo bisogno di trovare l'ennesima posizione di una barra rovesciata nel percorso di una cartella. Ecco la mia soluzione

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

Ecco i miei casi di test che passano

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

Per ottenere la parte prima dell'ultima occorrenza del delimitatore (funziona solo a NVARCHARcausa DATALENGTHdell'uso):

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

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

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

0

Questa risposta soddisfa i requisiti del PO. in particolare, consente all'ago di essere più di un singolo carattere e non genera un errore quando l'ago non viene trovato nel pagliaio. Mi è sembrato che la maggior parte (tutte?) Delle altre risposte non gestisse quei casi limite. Oltre a ciò ho aggiunto l'argomento "Posizione iniziale" fornito dalla funzione CharIndex del server MS SQL nativo. Ho provato a rispecchiare esattamente le specifiche di CharIndex tranne che per elaborare da destra a sinistra anziché da sinistra a destra. ad es. restituisco null se l'ago o il pagliaio sono null e restituisco zero se l'ago non viene trovato nel pagliaio. Una cosa che non ho potuto aggirare è che con la funzione integrata il terzo parametro è facoltativo. Con le funzioni definite dall'utente di SQL Server, tutti i parametri devono essere forniti nella chiamata a meno che la funzione non venga chiamata utilizzando "EXEC" . Mentre il terzo parametro deve essere incluso nell'elenco dei parametri, è possibile fornire la parola chiave "default" come segnaposto per esso senza dovergli assegnare un valore (vedere gli esempi di seguito). Dal momento che è più semplice rimuovere il terzo parametro da questa funzione se non lo si desidera che aggiungerlo se necessario, l'ho incluso qui come punto di partenza.

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

Mi sono imbattuto in questo thread durante la ricerca di una soluzione al mio problema simile che aveva gli stessi requisiti ma era per un diverso tipo di database che mancava anche la REVERSEfunzione.

Nel mio caso questo era per un database OpenEdge (Progress) , che ha una sintassi leggermente diversa. Ciò mi ha reso INSTRdisponibile la funzione offerta dalla maggior parte dei database tipizzati Oracle .

Quindi ho pensato al seguente codice:

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

Tuttavia, per la mia situazione specifica (essendo il database OpenEdge (Progress) ) ciò non ha comportato il comportamento desiderato perché la sostituzione del carattere con un carattere vuoto ha dato la stessa lunghezza della stringa originale. Questo non ha molto senso per me, ma sono stato in grado di aggirare il problema con il codice seguente:

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

Ora capisco che questo codice non risolverà il problema per T-SQL perché non esiste alternativa alla INSTRfunzione che offre la Occurenceproprietà.

Per essere precisi, aggiungerò il codice necessario per creare questa funzione scalare in modo che possa essere usato allo stesso modo come ho fatto negli esempi precedenti.

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

Per evitare l'ovvio, quando la REVERSEfunzione è disponibile non è necessario creare questa funzione scalare e si può semplicemente ottenere il risultato richiesto in questo modo:

SELECT
  LEN(foo.filepath) - CHARINDEX('/', REVERSE(foo.filepath))+1 AS LastIndexOfSlash 
FROM foo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.