web-dev-qa-db-fra.com

Trouver l'index de la dernière occurrence d'une sous-chaîne avec l'aide de T-SQL

Existe-t-il un moyen simple de rechercher l'index de la dernière occurrence d'une chaîne à l'aide de SQL? J'utilise SQL Server 2000 en ce moment. J'ai essentiellement besoin de la fonctionnalité fournie par la méthode .NET System.String.LastIndexOf. Une petite recherche sur Google a révélé ceci - Fonction Pour récupérer le dernier index - mais cela ne fonctionne pas si vous passez une expression de colonne "texte". Les autres solutions trouvées ailleurs ne fonctionnent que si le texte que vous recherchez contient un caractère.

Je vais probablement devoir cuisiner une fonction. Si je le fais, je le posterai ici pour que vous puissiez le consulter et peut-être vous en servir.

106
Raj

Vous êtes limité à petite liste de fonctions pour le type de données texte.

Tout ce que je peux suggérer, c'est de commencer par PATINDEX, mais de revenir en arrière à partir de DATALENGTH-1, DATALENGTH-2, DATALENGTH-3 etc. jusqu'à obtenir un résultat ou d'arriver à zéro (DATALENTH-DATALENGTH)

C'est vraiment quelque chose que SQL Server 2000 ne peut tout simplement pas gérer.

Modifier pour d'autres réponses: REVERSE ne figure pas dans la liste des fonctions pouvant être utilisées avec des données texte dans SQL Server 2000

27
gbn

Manière simple? Non, mais j'ai utilisé l'inverse. Littéralement.

Dans les routines précédentes, pour trouver la dernière occurrence d'une chaîne donnée, j'avais utilisé la fonction REVERSE (), suivie de CHARINDEX, puis de nouveau par REVERSE pour rétablir l'ordre d'origine. Par exemple:

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

montre comment extraire les noms de fichiers de la base de données à partir de leurs "noms physiques", quel que soit le degré d'imbrication dans les sous-dossiers. Ceci ne recherche qu'un seul caractère (la barre oblique inversée), mais vous pouvez en tirer parti pour des chaînes de recherche plus longues.

Le seul inconvénient est que je ne sais pas si cela fonctionnera bien avec les types de données TEXT. Je suis sur SQL 2005 depuis quelques années maintenant et je ne suis plus au courant de l’utilisation de TEXT - mais il me semble me souvenir que vous pourriez utiliser les fonctions GAUCHE et DROITE?

Philippe

159
Philip Kelley

Le moyen le plus simple est ....

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

Si vous utilisez Sqlserver 2005 ou une version ultérieure, l’utilisation de la fonction REVERSE à plusieurs reprises est préjudiciable aux performances. Le code ci-dessous est plus efficace.

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
44
Binoj Antony
DECLARE @FilePath VARCHAR(50) = 'My\Super\Long\String\With\Long\Words'
DECLARE @FindChar VARCHAR(1) = '\'

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

Cela a très bien fonctionné pour moi.

REVERSE(SUBSTRING(REVERSE([field]), CHARINDEX(REVERSE('[expr]'), REVERSE([field])) + DATALENGTH('[expr]'), DATALENGTH([field])))
7
Karthik D V
REVERSE(SUBSTRING(REVERSE(ap_description),CHARINDEX('.',REVERSE(ap_description)),len(ap_description)))  

a mieux fonctionné pour moi

6
mark brito

Ancienne question, mais toujours valable, voici donc ce que j’ai créé sur la base des informations fournies par d’autres ici.

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

Hmm, je sais que c'est un ancien thread, mais une table de décompte pourrait le faire dans SQL2000 (ou toute autre base de données):

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

Une table de pointage est juste un tableau de nombres incrémentiels.

substring(@str, _Tally.n, 1) = @delim obtient la position de chaque délimiteur, puis vous obtenez juste la position maximale dans cet ensemble.

Les tables de pointage sont géniales. Si vous ne les avez pas encore utilisées, il existe un bon article sur SQL Server Central (Free reg ou utilisez simplement Bug Me Not ( http://www.bugmenot.com/view/sqlservercentral.com )).

* EDIT: Suppression de n <= LEN(TEXT_FIELD), car vous ne pouvez pas utiliser LEN () sur le type TEXT. Tant que le substring(...) = @delim reste, le résultat est toujours correct.

4
Chris

Inversez votre chaîne et votre sous-chaîne, puis recherchez la première occurrence.

2
A-K

Je réalise que cette question remonte à plusieurs années, mais ...

Sur Access 2010, vous pouvez utiliser InStrRev() pour le faire. J'espère que cela t'aides.

2
Dan

Certaines des autres réponses retournent une chaîne réelle alors que j'avais plus besoin de connaître l'index réel int. Et les réponses qui en découlent semblent compliquer les choses de manière excessive. En utilisant certaines des autres réponses comme source d’inspiration, j’ai fait ce qui suit ...

Tout d'abord, j'ai créé une fonction:

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

Ensuite, dans votre requête, vous pouvez simplement faire ceci:

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

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

Ce qui précède doit renvoyer 23 (le dernier index de ':')

J'espère que cela a facilité les choses pour quelqu'un!

1
Matt Goodwin

Cette réponse utilise MS SQL Server 2008 (je n'ai pas accès à MS SQL Server 2000), mais la façon dont je le vois selon le PO comporte 3 situations à prendre en compte. De ce que je n'ai essayé aucune réponse ici couvre tous les 3 d'entre eux:

  1. Renvoie le dernier index d'un caractère de recherche dans une chaîne donnée.
  2. Renvoie le dernier index d'une sous-chaîne de recherche (plus d'un simple caractère ) Dans une chaîne donnée.
  3. Si le caractère ou la sous-chaîne de recherche ne figure pas dans la chaîne donnée, retournez 0

La fonction que j'ai créée prend 2 paramètres:

@String NVARCHAR(MAX): la chaîne à rechercher

@FindString NVARCHAR(MAX): un seul caractère ou une sous-chaîne pour obtenir le dernier index de in @String

Il retourne une INT qui correspond à l'index positif de @FindString dans @String ou 0, ce qui signifie que @FindString n'est pas dans @String.

Voici une explication de ce que fait la fonction:

  1. Initialise @ReturnVal à 0 en indiquant que @FindString n'est pas dans @String
  2. Vérifie l'index du @FindString dans @String en utilisant CHARINDEX() 
  3. Si l'index de @FindString dans @String est 0, @ReturnVal est laissé sous la forme 0
  4. Si l'index de @FindString dans @String est > 0, @FindString est dans @String et donc Il calcule le dernier index de @FindString dans @String en utilisant REVERSE()
  5. Retourne @ReturnVal, qui est un nombre positif correspondant au dernier index de @FindString dans @String ou 0, indiquant que @FindString n'est pas dans @String.

Voici le script de création de fonction (copier-coller prêt):

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

Voici un peu qui teste commodément la fonction:

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)

Je ne l'ai exécuté que sur MS SQL Server 2008, car je n'ai accès à aucune autre version, mais d'après ce que j'ai examiné, cela devrait au moins être valable pour 2008+.

Prendre plaisir.

1
Gharbad The Weak

Je sais que cela sera inefficace, mais avez-vous envisagé de transformer le champ text en varchar afin de pouvoir utiliser la solution fournie par le site Web que vous avez trouvé? Je sais que cette solution créerait des problèmes car vous pourriez potentiellement tronquer l'enregistrement si la longueur du champ text débordait de la longueur de votre varchar (pour ne pas dire qu'il ne serait pas très performant).

Étant donné que vos données se trouvent dans un champ text (et que vous utilisez SQL Server 2000), vos options sont limitées.

1
Andrew Hare

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

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

N'a pas été testé, il pourrait être décalé de un à cause de l'index nul, mais fonctionne dans la fonction SUBSTRING lorsqu'il coupe de @indexOf caractères à la fin de votre chaîne.

SUBSTRING([MyField], 0, @LastIndexOf)

1
Roan

Cette réponse répond aux exigences du PO. spécifiquement, cela permet à l’aiguille d’être plus d’un caractère unique et ne génère pas d’erreur lorsque l’aiguille n’est pas trouvée dans une botte de foin. Il m'a semblé que la plupart (toutes?) Des autres réponses ne traitaient pas de ces cas Edge. Au-delà, j’ai ajouté l’argument "Position de départ" fourni par la fonction CharIndex native du serveur MS SQL. J'ai essayé de refléter exactement la spécification de CharIndex, sauf de traiter de droite à gauche au lieu de gauche à droite. Par exemple, je retourne null si aiguille ou meule de foin est null et je retourne zéro si aiguille n'est pas trouvée dans meule de foin. Une chose que je n'ai pas pu contourner, c'est qu'avec la fonction intégrée, le troisième paramètre est facultatif. Avec les fonctions définies par l'utilisateur SQL Server, tous les paramètres doivent être fournis dans l'appel, sauf si la fonction est appelée à l'aide de "EXEC". Bien que le troisième paramètre doive être inclus dans la liste des paramètres, vous pouvez indiquer le mot clé "default" sans avoir à lui attribuer de valeur (voir les exemples ci-dessous). Comme il est plus facile de supprimer le troisième paramètre de cette fonction si vous ne le souhaitez pas, il serait préférable de l'ajouter si nécessaire. Je l'ai inclus ici comme point de départ.

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

Pour obtenir la pièce avant la dernière occurrence du séparateur (fonctionne uniquement pour NVARCHAR en raison de l'utilisation de 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
Hans M

Ce code fonctionne même si la sous-chaîne contient plus d'un caractère.

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

Je suis tombé sur ce fil en cherchant une solution à mon problème similaire, qui répondait exactement aux mêmes exigences, mais concernait un type de base de données différent pour lequel il manquait également la fonction REVERSE.

Dans mon cas, il s’agissait d’une base de données OpenEdge (Progress), dont la syntaxe est légèrement différente. Ceci a rendu la fonction INSTR disponible que la plupart des bases de données typées Oracle offrent .

Alors je suis venu avec le code suivant:

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

Cependant, pour ma situation spécifique (étant la base de données OpenEdge (Progress)), cela ne s'est pas traduit par le comportement souhaité, car remplacer le caractère par un caractère vide donnait la même longueur que la chaîne d'origine. Cela n'a pas beaucoup de sens pour moi mais j'ai pu contourner le problème avec le code ci-dessous:

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

Je comprends maintenant que ce code ne résoudra pas le problème de T-SQL car il n’existe pas d’alternative à la fonction INSTR qui offre la propriété Occurence.

Par souci de précision, je vais ajouter le code nécessaire à la création de cette fonction scalaire pour pouvoir l’utiliser de la même manière que dans les exemples ci-dessus.

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

Pour éviter l'évidence, lorsque la fonction REVERSE est disponible, vous n'avez pas besoin de créer cette fonction scalaire et vous pouvez simplement obtenir le résultat souhaité comme ceci:

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

Je devais trouver la nième dernière position d'une barre oblique inverse dans un chemin de dossier ..__ Voici ma solution.

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

Voici mes cas de test qui passent

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
fiat

Si vous voulez obtenir l'index du dernier espace dans une chaîne de mots, vous pouvez utiliser cette expression RIGHT (name, (CHARINDEX ('', REVERSE (name), 0)) pour renvoyer le dernier mot de la Ceci est utile si vous souhaitez analyser le nom de famille d'un nom complet comprenant les initiales du prénom et/ou du deuxième prénom.

0
Justin Stephens