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

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

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

Ответ 1

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

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

Это действительно то, что SQL Server 2000 просто не может обрабатывать.

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

Ответ 2

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

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

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

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

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

Филипп

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

Это очень сработало для меня.

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

Ответ 9

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

Таблицы таблиц являются удивительными. Если вы еще не использовали их раньше, есть хорошая статья о SQL Server Central (Бесплатная регистрация или просто использовать Bug Me Not (http://www.bugmenot.com/view/sqlservercentral.com)).

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

Ответ 10

Отмените как свою строку, так и свою подстроку, затем найдите первое вхождение.

Ответ 11

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

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

Ответ 12

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

Поскольку ваши данные находятся внутри поля text (и вы используете SQL Server 2000), ваши параметры ограничены.

Ответ 13

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

Ответ 14

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

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

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

SUBSTRING([MyField], 0, @LastIndexOf)

Ответ 15

Мне нужно было найти последнюю позицию обратной косой черты в пути к папке. Вот мое решение.

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

Ответ 16

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

Ответ 17

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

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

Ответ 18

Этот ответ отвечает требованиям OP. в частности, это позволяет игле быть более чем одним символом и не вызывает ошибки, когда игла не найдена в стоге сена. Мне казалось, что большинство (всех?) Других ответов не справлялись с этими крайними случаями. Кроме того, я добавил аргумент "Начальная позиция", предоставляемый функцией CharIndex собственной MS SQL-сервера. Я попытался точно отразить спецификацию для CharIndex, за исключением обработки справа налево, а не слева направо. например, я возвращаю значение 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

Ответ 19

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

В моем случае это было для базы данных OpenEdge (Progress), которая имеет несколько иной синтаксис. Это сделало функцию INSTR доступной для меня тем, что большинство баз данных, основанных на Oracle, предлагают.

Итак, я придумал следующий код:

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

Однако для моей конкретной ситуации (являющейся базой данных OpenEdge (Progress)) это не приводило к желаемому поведению, потому что замена символа пустым char давала ту же длину, что и исходная строка. Это не имеет большого значения для меня, но я смог обойти проблему с помощью кода ниже:

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

Теперь я понимаю, что этот код не решит проблему для T-SQL, потому что нет альтернативы функции INSTR, которая предлагает свойство Occurence.

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

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

Чтобы избежать очевидного, когда функция REVERSE доступна, вам не нужно создавать эту скалярную функцию, и вы можете просто получить требуемый результат следующим образом:

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