Знайдіть індекс останнього появи підрядків за допомогою T-SQL


127

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

Мені, мабуть, доведеться готувати функцію. Якщо я це зроблю, я опублікую його тут, щоб ви, люди, змогли його подивитися і, можливо, використати.

Відповіді:


33

Ви обмежені невеликим переліком функцій для типу текстових даних.

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

Це справді щось, що SQL Server 2000просто не вдається впоратися.

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


1
Так, це досить незручно. Здається, це повинно бути простим, тільки це не так!
Радж

... ось чому SQL 2005 має varchar (max) для дозволу нормальних функцій
gbn

1
Ах! тому "varchar (max)" - це SQL 2005 року, що пояснює, чому це не спрацювало, коли я спробував це на SQL 2000.
Радж,

DATALENGTH не дає результату для мене, хоча LENGTH працює.
Текіла

@Tequila та інші: DATALENGTHповертає кількість байтів, а не символів. Тому DATALENGTHповертає 2 x кількість символів у рядку для NVARCHARрядків. LENоднак повертає кількість символів за мінусом будь-якого пробілу . Я ніколи не використовую DATALENGTHдля обчислення довжини символів, якщо тільки пробільний пробіл не є значним, і я точно знаю, що мої типи даних є послідовними, чи вони є, VARCHARабоNVARCHAR
rbsdca

174

Прямий шлях? Ні, але я використав реверс. Буквально.

У попередніх процедурах, щоб знайти останнє виникнення заданого рядка, я використав функцію 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 - але, схоже, я пам'ятаю, ви могли використовувати на ньому ліву та праву?

Філіп


1
Вибачте - я впевнений, що ніколи не робив цього, коли працював з 2000 роком, і в даний час не маю доступу до будь-яких установок SQL 2000.
Філіп Келлі

Блискуче! Ніколи б не подумав напасти на цю проблему таким чином!
Джаред

4
Хороший! Я змінив для власних потреб: email.Substring (0, email.lastIndexOf ('@')) == ВИБІР ВЛІТЬ (email, LEN (email) -CHARINDEX ('@', REVERSE (email))
Fredrik Johansson

1
Розумні речі, як це, тому програмування так весело!
Кріс

чому б просто не використати право замість ліворуч на оригіналі замість додаткового реверсу
Phil

108

Найпростіший спосіб - це….

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

3
+1 Через те, що НЕ помилка пожежі на зразок "Недійсний параметр довжини передано в ліву або підрядну функцію", якщо не знайдено відповідності
Xilmiki

12
Якщо ваш [expr]символ довший 1 символу, його потрібно також повернути назад!
Андрій Нарушевічус

60

Якщо ви використовуєте 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

1
Це може здатися очевидним заднім числом, але якщо ви шукаєте рядок замість одного символу, вам потрібно зробити: 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

Старе, але все-таки актуальне питання, тому ось що я створив на основі інформації, наданої іншими.

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([field]), CHARINDEX(REVERSE('[expr]'), REVERSE([field])) + DATALENGTH('[expr]'), DATALENGTH([field])))

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

краще працював для мене


4

Хм, я знаю, що це стара тема, але таблиця з таблицями може це зробити в 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 (Безкоштовна рег., Або просто використовуйте Bug Me Not ( http://www.bugmenot.com/view/sqlservercentral.com )).

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


Приємно. Я думаю, що це фактично те саме рішення, хоча і прийнята відповідь gbn; ви просто використовуєте таблицю для зберігання цілих чисел 1, 2, 3 і т.д., які віднімаються від DATALENGTH і читаються від першого символу вперед, а не останнього символу назад.
Майкл Петіто

2

Зворотну сторону і рядка, і підрядки, а потім шукайте перше виникнення.


Гарна думка. Зараз у мене немає 2000, і я не можу згадати, чи міг би я це зробити, коли робив.
АК

2

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

Сподіваюсь, це комусь трохи полегшило!


2

Я усвідомлюю, що це вже кілька років питання, але ...

Увімкнути Access 2010, ви можете використовувати InStrRev()для цього. Сподіваюся, це допомагає.


2

У цій відповіді використовується MS SQL Server 2008 (я не маю доступу до MS SQL Server 2000), але те, як я бачу це відповідно до ОП, слід враховувати 3 ситуації. З того, що я не намагався відповісти, тут висвітлюються всі 3 з них:

  1. Повернути останній індекс символу пошуку у заданому рядку.
  2. Повернути останній індекс пошукової підрядки (більше ніж один символ) у заданому рядку.
  3. Якщо символу пошуку або підрядка немає в заданому рядку, повертається 0

Функція, яку я придумав, бере 2 параметри:

@String NVARCHAR(MAX) : Рядок для пошуку

@FindString NVARCHAR(MAX) : Або один символ, або підряд, щоб отримати останній індекс в @String

Він повертає то, INTщо є або позитивним індексом @FindStringв, @Stringабо 0значенням, яке @FindStringне є@String

Ось пояснення того, що функція виконує:

  1. Ініціалізується, @ReturnValщоб 0вказати, що @FindStringнемає в@String
  2. Перевіряє індекс @FindStringв @String, використовуючиCHARINDEX()
  3. Якщо індекс @FindStringin @Stringє 0, @ReturnValзалишається як0
  4. Якщо індекс @FindStringдюйма @Stringє > 0, @FindStringв @Stringтак обчислює останній індекс @FindStringв @Stringза допомогоюREVERSE()
  5. Повертає, @ReturnValщо є або додатним числом, яке є останнім індексом @FindStringв, @Stringабо 0вказує, що @FindStringйого немає@String

Ось сценарій створення функції (скопіюйте та вставте готово):

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

Ось трохи, що зручно тестує функцію:

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)

Я запускав це лише на MS SQL Server 2008, тому що я не маю доступу до будь-якої іншої версії, але з того, що я розглядав, це повинно бути принаймні для 2008+.

Насолоджуйтесь.


1

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

Оскільки ваші дані знаходяться у textполі (а ви використовуєте SQL Server 2000), ваші параметри обмежені.


Так, передача на "varchar" не є варіантом, оскільки дані, що обробляються, часто перевищують максимум, який може міститись у "varchar". Дякую за вашу відповідь!
Радж

1

Якщо ви хочете отримати індекс останнього пробілу в рядку слів, ви можете використовувати це вираження ПРАВО (ім'я, (CHARINDEX ('', REVERSE (ім'я), 0)), щоб повернути останнє слово в рядку. корисно, якщо ви хочете розібрати прізвище повного імені, яке містить ініціали для імені та / або імені.


1

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

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

Не перевірена, може бути вимкнено одним через нульовий індекс, але працює у SUBSTRINGфункції, коли відсікається від @indexOfсимволів до кінця рядка

SUBSTRING([MyField], 0, @LastIndexOf)


1

Цей код працює, навіть якщо підрядок містить більше 1 символу.

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

Мені потрібно було знайти 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','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

Щоб отримати деталь до останнього появи роздільника (працює лише за 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)));

0

Ця відповідь відповідає вимогам ОП. конкретно, це дозволяє голці бути більше одного символу і не створює помилок, коли голка не знайдена в стозі сіна. Мені здалося, що більшість (усіх?) Інших відповідей не стосуються тих крайових випадків. Крім цього я додав аргумент "Початкова позиція", наданий нативним функцією сервера MS SQL CharIndex. Я намагався точно відобразити специфікацію для CharIndex, за винятком того, щоб обробити справа наліво, а не зліва направо. Наприклад, я повертаю null, якщо голка або стог сіна є нульовим, і я повертаю нуль, якщо голка не знайдена в копиці сіна. Одне, що мені не вдалося обійти, це те, що з вбудованою функцією третій параметр не є обов'язковим. З визначеними користувачем функціями SQL Server усі параметри повинні бути надані під час виклику, якщо функція не викликається за допомогою "EXEC" . Хоча третій параметр повинен бути включений до списку параметрів, ви можете надати ключове слово "за замовчуванням" як заповнювач для нього, не потребуючи його значення (див. Приклади нижче). Оскільки третій параметр легше видалити з цієї функції, якщо це не бажано, ніж було б додати його за потреби, я включив його сюди як вихідну точку.

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

Я натрапив на цю тему, шукаючи рішення для моєї подібної проблеми, яка мала точно таку ж вимогу, але стосувалася бази даних іншого типу, яка також не мала REVERSEфункції.

У моєму випадку це було для бази даних OpenEdge (Прогрес) , яка має дещо інший синтаксис. Це зробило INSTRмені доступною функцію, яку пропонує більшість баз даних типу Oracle .

Тому я придумав наступний код:

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

Однак для моєї конкретної ситуації (будучи базою даних OpenEdge (Progress) ) це не призвело до бажаної поведінки, оскільки заміна символу порожнім знаком дала таку ж довжину, що і початкова рядок. Для мене це не має великого сенсу, але мені вдалося обійти проблему з наведеним нижче кодом:

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.