2009-06-21 22 views
94

Esiste un modo semplice per trovare l'indice dell'ultima occorrenza di una stringa utilizzando SQL? Sto usando SQL Server 2000 in questo momento. Fondamentalmente ho bisogno della funzionalità fornita dal metodo .NET System.String.LastIndexOf. Un po 'google ha rivelato questo - Function To Retrieve Last Index - ma questo non funziona se si passa in una espressione di colonna "testo". Altre soluzioni trovate altrove funzionano solo fino a quando il testo che stai cercando è lungo 1 carattere.Trova l'indice dell'ultima occorrenza di una sottostringa utilizzando T-SQL

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

risposta

22

Si è limitato a small list of functions per il tipo di dati di testo.

Tutto quello che posso suggerire è cominciare con PATINDEX, ma lavorare a ritroso da DATALENGTH-1, DATALENGTH-2, DATALENGTH-3 ecc fino ad ottenere un risultato o finisce a zero (DATALENGTH-DATALENGTH)

Questo è davvero qualcosa che SQL Server 2000 semplicemente non in grado di gestire.

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

+0

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

+0

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

+1

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

1

so che sarà inefficiente, ma hai considerato lanciare il campo text-varchar in modo da poter utilizzare la soluzione fornita dal sito web che hai trovato? So che questa soluzione creerebbe problemi poiché potreste potenzialmente troncare il record se la lunghezza nel campo text ha superato la lunghezza del vostro varchar (per non dire che non sarebbe molto performante).

Poiché i dati si trovano all'interno di un campo text (e si utilizza SQL Server 2000), le opzioni sono limitate.

+0

Sì, colata a "varchar "non è un'opzione in quanto i dati in elaborazione spesso superano il massimo che può essere contenuto in un" varchar ". Grazie per la tua risposta! – Raj

+0

Bummer, mi spiace sentirlo! –

2

inversione sia la stringa e la vostra stringa, quindi cercare la prima occorrenza.

+0

Hai provato REVERSE su un campo di testo in SQL Server 2000? – gbn

+0

Buon punto. Non ho 2000 ora e non riesco a ricordare se potessi farlo quando l'ho fatto. –

145

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

Nelle routine precedenti, per trovare l'ultima occorrenza di una determinata stringa, ho utilizzato la funzione REVERSE(), seguito CHARINDEX, seguito di nuovo 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 reali da dai loro "nomi fisici", non importa quanto profondamente annidato in sottocartelle. Questo cerca solo un carattere (il backslash), ma puoi costruire su questo per stringhe di ricerca più lunghe.

L'unico svantaggio è che non so quanto funzionerà correttamente sui tipi di dati TEXT. Sono stato su SQL 2005 per alcuni anni e non sono più abituato a lavorare con TEXT - ma mi sembra di ricordare che potresti usare LEFT e RIGHT su di esso?

Philip

+1

Hai provato REVERSE su un campo di testo in SQL Server 2000? – gbn

+1

Scusa - Sono abbastanza sicuro di non averlo mai fatto quando lavoravo con il 2000 e attualmente non ho accesso a nessuna installazione di SQL 2000. –

+0

Brillante! Mai avrei pensato di attaccare questo problema in questo modo! – Jared

4

Hmm, so che questo è un vecchio thread, ma un tavolo di riscontro potrebbe fare questo 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 conteggio è solo una tabella di numeri incrementali.

Il substring(@str, _Tally.n, 1) = @delim ottiene la posizione di ciascun delimitatore, quindi si ottiene solo la posizione massima in quel set.

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

* MODIFICA: Rimosso n <= LEN(TEXT_FIELD), poiché non è possibile utilizzare LEN() sul tipo di TESTO. Finché rimane il substring(...) = @delim sebbene il risultato sia ancora corretto.

+0

Nice. Penso che questa sia effettivamente la stessa soluzione anche se come risposta accettata da gbn; stai solo usando una tabella per memorizzare i numeri interi 1, 2, 3 ecc. che vengono sottratti da DATALENGTH e leggendo dal primo carattere in avanti invece dell'ultimo carattere indietro. –

84

Il modo più semplice è ....

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

+1 Perché NON si verifica un errore di fuoco come "Parametro di lunghezza non valida passato alla funzione SINISTRA o SOTTOSTRATO" se non è stata trovata alcuna corrispondenza – Xilmiki

+6

Se il tuo [[espr] 'è più lungo di 1 simbolo, è necessario invertirlo anche tu! –

6

Vecchio ma ancora valido questione, in modo da heres quello che ho creato sulla base di 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 
0

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

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

funzionato meglio per me

2

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

Su Access 2010, è possibile utilizzare InStrRev() per eseguire questa operazione. Spero che questo ti aiuti.

33

Se si utilizza Sqlserver 2005 o versione successiva, utilizzare la funzione REVERSE molte volte è dannoso per le prestazioni, al di sotto del codice è 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 
+4

Questa risposta merita più attenzione. Molto bene. – Yuck

0

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

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

non ho ancora testato, potrebbe essere fuori da uno a causa di indice zero, ma lavora a SUBSTRING funzione quando tagliando da @indexOf caratteri alla fine della stringa

SUBSTRING([MyField], 0, @LastIndexOf)

6

Questo ha funzionato molto noi ll per me.

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

Avevo bisogno di trovare l'ultima posizione di un backslash in un percorso di 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 

Qui sono 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 per NVARCHAR a causa di DATALENGTH utilizzo):

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

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

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

Alcune delle altre risposte restituiscono una stringa effettiva mentre io avevo più bisogno di conoscere l'indice reale int. E le risposte che sembrano complicano troppo le cose. Utilizzando alcune delle altre risposte come ispirazione, ho fatto la seguente ...

In primo luogo, 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 

Poi, nella query si può 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ù semplice per qualcuno!

0

Questa risposta soddisfa i requisiti dell'OP. 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 sembrava 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 per CharIndex tranne che per elaborare da destra a sinistra anziché da sinistra a destra. es. restituisco null se ago o pagliaio sono nulli e restituisco zero se l'ago non viene trovato nel pagliaio. Una cosa che non ho potuto evitare è che con la funzione integrata il terzo parametro è opzionale. 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 dover dargli un valore (vedere esempi di seguito). Dal momento che è più facile rimuovere il terzo parametro da questa funzione se non desiderato rispetto a quello sarebbe di 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 
12
DECLARE @FilePath VARCHAR(50) = 'My\Super\Long\String\With\Long\Words' 
DECLARE @FindChar VARCHAR(1) = '\' 

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

mi sono imbattuto in questa discussione durante la ricerca di una soluzione al mio problema simile che ha avuto esattamente la stessa esigenza, ma è stato per un diverso tipo di database che è stato anche a mancare la funzione REVERSE.

Nel mio caso questo era per un OpenEdge (Progress) database, che ha una sintassi leggermente diversa. Ciò ha reso disponibile la funzione INSTR a most Oracle typed databases offer.

Così mi si avvicinò con il 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 OpenEdge (Progress) database) questo non ha comportato nel comportamento desiderato, perché la sostituzione del personaggio con un carattere vuoto ha 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 qui sotto:

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 un'alternativa alla funzione INSTR che offre la proprietà Occurence.

Giusto per essere accurato aggiungo il codice necessario per creare questa funzione scalare in modo che possa essere utilizzata allo stesso modo come ho fatto negli esempi sopra.

-- 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 funzione REVERSE è disponibile non è necessario per creare questa funzione scalare e si può solo ottenere il risultato richiesto in questo modo:

SELECT 
    LEN(foo.filepath) - CHARINDEX('/', REVERSE(foo.filepath))+1 AS LastIndexOfSlash 
FROM foo 
Problemi correlati