Найти индекс последнего вхождения подстроки с помощью T-SQL

есть ли простой способ найти индекс последнего вхождения строки с помощью SQL? Я использую SQL Server 2000 прямо сейчас. Мне в основном нужна функциональность, которую .NET System.String.LastIndexOf метод обеспечивает. Немного гуглить показал это - Функция Для Извлечения Последнего Индекса - но это не работает, если вы передаете выражение столбца "текст". Другие решения, найденные в другом месте, работают только до тех пор, пока текст, который вы ищете, равен 1 символу длинный.

Мне, вероятно,придется приготовить функцию. Если я это сделаю, я опубликую его здесь, чтобы вы могли посмотреть на него и, возможно, использовать.

20 ответов


вы ограничены небольшой список функций для текстового типа данных.

все, что я могу предложить начать с PATINDEX, а затем DATALENGTH-1, DATALENGTH-2, DATALENGTH-3 etc, пока вы не получите результат или не окажетесь на нуле (DATALENGTH-DATALENGTH)

Это действительно то, что SQL Server 2000 просто не могут справиться.

редактирование других ответов : REVERSE нет в списке функций, которые могут использоваться с текстовыми данными в SQL Server 2000


простым способом? Нет, но я использовал обратное. Буквально.

в предыдущих подпрограммах, чтобы найти последнее появление данной строки, я использовал функцию REVERSE (), затем CHARINDEX, а затем REVERSE для восстановления исходного порядка. Например:

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

показывает, как извлечь фактические имена файлов базы данных из их" физических имен", независимо от того, насколько глубоко вложены во вложенные папки. Это делает поиск только одного символа( обратная косая черта), но вы может основываться на этом для более длинных строк поиска.

единственным недостатком является то, что я не знаю, насколько хорошо это будет работать с текстовыми типами данных. Я нахожусь в SQL 2005 уже несколько лет и больше не знаком с работой с текстом , но я, кажется, помню, что вы могли бы использовать слева и справа на нем?

Филипп


самый простой способ-это....

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

Если вы используете 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

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

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

Это очень хорошо для меня.

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

старый, но все еще действительный вопрос, так вот что я создал на основе информации, предоставленной другим здесь.

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

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

работал лучше для меня


Хм, я знаю, что это старый поток, но таблица подсчета может сделать это в 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 получает позицию каждого разделителя, затем вы просто получаете максимальную позицию в этом наборе.

Tally таблицы являются удивительными. Если вы не использовали их раньше, есть хорошая статья на SQL Server Central (Free reg, или просто использовать ошибку меня не (http://www.bugmenot.com/view/sqlservercentral.com)).

*правка: удалены n <= LEN(TEXT_FIELD), поскольку вы не можете использовать LEN() для типа текста. Пока substring(...) = @delim остается, хотя результат все равно правильный.


Реверсируйте как строку, так и подстроку, затем выполните поиск первого вхождения.


Я понимаю, что это вопрос нескольких лет, но...

On Access 2010, вы можете использовать InStrRev() для этого. Надеюсь, это поможет.


Я знаю, что это будет неэффективно, но вы подумали о кастинге


Если вы хотите получить индекс последнего пробела в строке слова, вы можете использовать это выражение Справа (name, (CHARINDEX (' ', REVERSE (name),0)), чтобы вернуть последнее слово в строке. Это полезно, если вы хотите разобрать фамилию полного имени, которое включает инициалы для первого и /или среднего имени.


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

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

не протестировали, он может быть выключен на один из-за нулевого индекса, но работает в SUBSTRING функция при отключении от @indexOf символы до конца строки

SUBSTRING([MyField], 0, @LastIndexOf)


мне нужно было найти 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','1234567893456789\', 1) as indexOf -- expect 0 (no instances)
SELECT dbo.GetLastIndexOf('\','1234567893456789\', 1) as indexOf -- expect 20
SELECT dbo.GetLastIndexOf('\','1234567893456789\', 2) as indexOf -- expect 10
SELECT dbo.GetLastIndexOf('\','1234893456789\', 3) as indexOf -- expect 5

чтобы получить часть до последнего появления разделителя (работает только для 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)));

некоторые из других ответов возвращают фактическую строку, тогда как мне больше нужно было знать фактический индекс 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 (последний индекс ':')

надеюсь, это сделало его немного легче кто-нибудь!


этот ответ соответствует требованиям OP. в частности, он позволяет игле быть более одного символа, и он не генерирует ошибку, когда игла не найдена в стоге сена. Мне казалось, что больше всего (все?) из других ответов не обрабатывались эти крайние случаи. Кроме того, я добавил аргумент "Начальная позиция", предоставленный собственной функцией MS SQL server CharIndex. Я попытался точно отразить спецификацию для CharIndex, за исключением обработки справа налево вместо слева направо. например, я возвращаю null, если игла или стог сена равно null, и я возвращаю ноль, если игла не найдена в стоге сена. Одна вещь, которую я не мог обойти, заключается в том, что со встроенной функцией третий параметр является необязательным. С пользовательскими функциями SQL Server все параметры должны быть предоставлены в вызове, если функция не вызывается с помощью "EXEC". Хотя третий параметр должен быть включен в список параметров, вы можете указать ключевое слово "default" в качестве заполнителя для него без необходимость дать ему значение (см. примеры ниже). Поскольку легче удалить третий параметр из этой функции, если это нежелательно, чем добавить его, если это необходимо, я включил его здесь в качестве отправной точки.

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

я наткнулся на этот поток при поиске решения моей аналогичной проблемы, которая имела точно такое же требование, но была для другого вида базы данных, которой также не хватало


этот код работает, даже если подстрока содержит более 1 персонажа.

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

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