使用 T-SQL 查找子字符串最后一次出现的索引

Posted

技术标签:

【中文标题】使用 T-SQL 查找子字符串最后一次出现的索引【英文标题】:Find index of last occurrence of a sub-string using T-SQL 【发布时间】:2010-11-04 17:29:56 【问题描述】:

有没有一种直接的方法可以使用 SQL 查找字符串最后一次出现的索引?我现在正在使用 SQL Server 2000。我基本上需要.NET System.String.LastIndexOf 方法提供的功能。一点谷歌搜索揭示了这一点 - Function To Retrieve Last Index - 但如果你传入“文本”列表达式,这将不起作用。仅当您要搜索的文本长度为 1 个字符时,在其他地方找到的其他解决方案才有效。

我可能需要编写一个函数。如果我这样做了,我会在此处发布,以便大家查看并使用。

【问题讨论】:

【参考方案1】:

直截了当?不,但我使用了相反的方法。从字面上看。

在以前的例程中,为了找到给定字符串的最后一次出现,我使用了 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?

菲利普

【讨论】:

抱歉 -- 我很确定我在使用 2000 时从未返回过,而且我目前无权访问任何 SQL 2000 安装。 太棒了!从来没有想过以这种方式解决这个问题! 不错的一个!我根据自己的需要进行了修改: email.Substring(0, email.lastIndexOf('@')) == SELECT LEFT(email, LEN(email)-CHARINDEX('@', REVERSE(email))) 像这样的聪明东西就是编程如此有趣的原因! 为什么不在原件上使用右而不是左而不是额外的反向【参考方案2】:

最简单的方法是......

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

【讨论】:

+1 因为如果未找到匹配项,则不会触发错误,例如“传递给 LEFT 或 SUBSTRING 函数的长度参数无效” 如果您的[expr] 超过1个符号,您也需要反转它!【参考方案3】:

如果你使用的是Sqlserver 2005或以上版本,多次使用REVERSE函数会影响性能,下面的代码效率更高。

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

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

【讨论】:

事后看来似乎很明显,但是如果您要搜索字符串而不是单个字符,则必须这样做: LEN(@FilePath) - CHARINDEX(REVERSE(@FindString),REVERSE( @FilePath))【参考方案4】:

对于文本数据类型,您仅限于 small list of functions。

我只能建议从PATINDEX 开始,但从DATALENGTH-1, DATALENGTH-2, DATALENGTH-3 等向后工作,直到获得结果或最终为零(DATALENGTH-DATALENGTH)

这真的是SQL Server 2000 根本无法处理的事情。

编辑其他答案:REVERSE 不在 SQL Server 2000 中可用于文本数据的函数列表中

【讨论】:

是的,这很尴尬。这看起来应该很简单,但事实并非如此! ...这就是 SQL 2005 有 varchar(max) 以允许正常功能的原因 啊!所以“varchar(max)”是 SQL 2005 的东西,这就解释了为什么当我在 SQL 2000 上尝试它时它不起作用。 DATALENGTH 无法为我生成正确的结果,尽管 LENGTH 有效。 @Tequila 和其他人:DATALENGTH 返回字节数而不是字符数。因此,DATALENGTHNVARCHAR 字符串返回字符串中字符数的 2 倍。但是,LEN 返回字符数,减去任何尾随空格。我从不使用DATALENGTH 进行字符长度计算,除非尾随空格很重要并且我确定我的数据类型是一致的,无论它们是VARCHAR 还是NVARCHAR【参考方案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

【讨论】:

如果 char 是字符串的第一个字符,则返回 -1【参考方案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 获取每个分隔符的位置,然后您只需获取该集合中的最大位置。

Tally 表很棒。如果你之前没用过,SQL Server Central上有一篇不错的文章。

*EDIT:删除n <= LEN(TEXT_FIELD),因为您不能在 TEXT 类型上使用 LEN()。只要substring(...) = @delim 仍然存在,尽管结果仍然正确。

【讨论】:

不错。我认为这实际上是与 gbn 接受的答案相同的解决方案;您只是使用一个表来存储从 DATALENGTH 中减去的整数 1、2、3 等,并从第一个字符向前读取,而不是从最后一个字符向后读取。【参考方案10】:

此答案使用 MS SQL Server 2008(我无权访问 MS SQL Server 2000),但我根据 OP 看到的方式有 3 种情况需要考虑。从我尝试过的情况来看,这里没有答案涵盖所有 3 个:

    返回给定字符串中搜索字符的最后一个索引。 返回搜索子串的最后一个索引(不止一个 字符)在给定的字符串中。 如果搜索字符或子字符串不在给定字符串中,则返回0

我想出的函数有两个参数:

@String NVARCHAR(MAX) : 要搜索的字符串

@FindString NVARCHAR(MAX) :获取最后一个字符或子字符串 @String中的索引

它返回一个INT,它是@String0@FindString 的正索引,这意味着@FindString 不在@String

下面是函数作用的解释:

    @ReturnVal初始化为0,表示@FindString不在@String中 使用CHARINDEX()检查@FindString@String中的索引 如果@String@FindString的索引为0,则@ReturnVal保留为0 如果@FindString@String的索引是> 0@FindString@String所以 它使用REVERSE() 计算@String@FindString 的最后一个索引 返回@ReturnVal,它是一个正数,它是 @FindString@String0 中表示@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 年以上应该是好的。

享受吧。

【讨论】:

【参考方案11】:

反转你的字符串和你的子字符串,然后搜索第一个匹配项。

【讨论】:

好点。我现在没有 2000 个,我不记得我当时是否可以做到。【参考方案12】:

如果要获取一串单词中最后一个空格的索引,可以使用这个表达式 RIGHT(name, (CHARINDEX(' ',REVERSE(name),0)) 返回字符串中的最后一个单词。如果您想解析包含第一个和/ 或中间名。

【讨论】:

【参考方案13】:

其他一些答案返回一个实际的字符串,而我更需要知道实际的索引 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(':' 的最后一个索引)

希望这对某人来说更容易一些!

【讨论】:

【参考方案14】:

我知道这是一个几年前的问题,但是......

Access 2010 上,您可以使用InStrRev() 来执行此操作。希望这会有所帮助。

【讨论】:

【参考方案15】:

我知道这会效率低下,但您是否考虑过将 text 字段转换为 varchar 以便您可以使用您找到的网站提供的解决方案?我知道这个解决方案会产生问题,因为如果text 字段中的长度超出了varchar 的长度,您可能会截断记录(更不用说它不会非常高效)。

由于您的数据位于 text 字段中(并且您使用的是 SQL Server 2000),因此您的选择是有限的。

【讨论】:

是的,转换为“varchar”不是一种选择,因为正在处理的数据经常超过“varchar”中可以容纳的最大值。不过感谢您的回答!【参考方案16】:

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

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

尚未测试,由于索引为零,它可能会减一,但在从 @indexOf 字符切到字符串末尾时可以在 SUBSTRING 函数中使用

SUBSTRING([MyField], 0, @LastIndexOf)

【讨论】:

【参考方案17】:

即使子字符串包含超过 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

【讨论】:

【参考方案18】:

我需要在文件夹路径中找到反斜杠的倒数第 n 个位置。 这是我的解决方案。

/*
http://***.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

【讨论】:

【参考方案19】:

获取最后一次出现分隔符之前的部分(由于DATALENGTH 的使用,仅适用于NVARCHAR):

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

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

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

【讨论】:

【参考方案20】:

此答案符合 OP 的要求。具体来说,它允许针不止一个字符,并且当在 haystack 中找不到针时,它不会产生错误。在我看来,大多数(全部?)其他答案都没有处理那些边缘情况。除此之外,我还添加了本机 MS SQL 服务器 CharIndex 函数提供的“起始位置”参数。我试图完全反映 CharIndex 的规范,除了从右到左而不是从左到右处理。例如,如果 needle 或 haystack 为 null,则返回 null,如果在 haystack 中找不到 needle,则返回零。我无法解决的一件事是,对于内置函数,第三个参数是可选的。对于 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

【讨论】:

【参考方案21】:

我在寻找类似问题的解决方案时遇到了这个线程,该问题具有完全相同的要求,但适用于另一种也缺少 REVERSE 函数的数据库。

在我的例子中,这是一个 OpenEdge (Progress) 数据库,它的语法略有不同。这使我可以使用 INSTR 函数,而不是 most Oracle typed databases offer。

所以我想出了以下代码:

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 的问题,因为除了提供 Occurence 属性的 INSTR 函数之外,别无选择。

为了彻底起见,我将添加创建此标量函数所需的代码,以便可以像在上面的示例中那样使用它。

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

【讨论】:

【参考方案22】:

句柄查找大于 1 个字符的内容。 如果您愿意,可以随意增加参数大小。

忍不住发帖

drop function if exists lastIndexOf
go 
create function lastIndexOf(@searchFor varchar(100),@searchIn varchar(500))
returns int
as
begin 

if LEN(@searchfor) > LEN(@searchin) return 0 
declare @r varchar(500), @rsp varchar(100)
select @r = REVERSE(@searchin)
select @rsp = REVERSE(@searchfor)
return len(@searchin) - charindex(@rsp, @r) - len(@searchfor)+1
end 

和测试

select dbo.lastIndexof('greg','greg greg asdflk; greg sadf' )  -- 18
select dbo.lastIndexof('greg','greg greg asdflk; grewg sadf' )  --5
select dbo.lastIndexof(' ','greg greg asdflk; grewg sadf' ) --24

【讨论】:

【参考方案23】:

这个话题已经有一段时间了。我将通过示例提供涵盖不同基础的解决方案:

declare @aStringData varchar(100) = 'The quick brown/fox jumps/over the/lazy dog.pdf'
/*
The quick brown/fox jumps/over the/lazy dog.pdf
fdp.god yzal/eht revo/spmuj xof/nworb kciuq ehT
*/

select
    Len(@aStringData) - CharIndex('/', Reverse(@aStringData)) + 1 [Char Index],
    -- Get left side of character, without the character '/'
    Left(@aStringData, Len(@aStringData) - CharIndex('/', Reverse(@aStringData))) [Left excluding char],
    -- Get left side of character, including the character '/'
    Left(@aStringData, Len(@aStringData) - CharIndex('/', Reverse(@aStringData)) + 1) [Left including char],
    -- Get right side of character, without the character '/'
    Right(@aStringData, CharIndex('/', Reverse(@aStringData)) - 1) [Right including char]

要获得 char 位置,需要反转字符串,因为 CharIndex 获得第一次出现。请记住,当我们正在反转时,CharIndex 光标将落在我们正在查找的字符的另一侧。因此,期望补偿 -1 或 +1,具体取决于是否要获取字符串的左侧或右侧部分。

【讨论】:

以上是关于使用 T-SQL 查找子字符串最后一次出现的索引的主要内容,如果未能解决你的问题,请参考以下文章

查找字符串中子字符串最后一次出现的索引

字符串的常用操作

T-SQL 子字符串 - 最后 3 个字符

在 C# 中查找子数组的第一个出现/起始索引

XSLT:查找字符串中的最后一次出现

根据子字符串索引查找内容