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

160

有没有一种简单的方法使用SQL查找字符串的最后一次出现的索引?我目前正在使用SQL Server 2000。基本上,我需要 .NET System.String.LastIndexOf 方法提供的功能。通过一些谷歌搜索发现了这个链接 - 检索最后索引的函数 - 但是如果您传递 "text" 列表达式,它将无法工作。在其他地方找到的解决方案只在搜索的文本长度为1字符时有效。

我可能需要编写一个函数。如果我这样做,我会在这里发布它,以便您们可以查看并可能利用它。

23个回答

209

有直接的方法吗?没有,但我使用了反转。字面意思。

在之前的例程中,要查找给定字符串的最后一个出现位置,我使用了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?

Philip


1
抱歉 - 我相当确定我在使用2000时从未这样做过,而且我目前没有访问任何SQL 2000安装。 - Philip Kelley
太棒了!从来没有想过可以用这种方式解决这个问题! - Jared
4
好的!我修改了一下以适应我的需要:email.Substring(0, email.lastIndexOf('@')) == SELECT LEFT(email, LEN(email)-CHARINDEX('@', REVERSE(email))) 将其翻译为:不错!我根据自己的需求进行了修改:email.Substring(0,email.lastIndexOf('@'))== SELECT LEFT(email,LEN(email)-CHARINDEX('@',REVERSE(email))) - Fredrik Johansson
1
聪明的编程技巧就是编程如此有趣的原因! - Chris
完美的解决方案。我从UNIX FTP执行了一个目录列表,将结果存储在一个名为#CommandOutput的临时表中,但是没有简单的方法来提取文件名。这个语句可以实现:SELECT Readline, REVERSE(LEFT(REVERSE(Readline), CHARINDEX(' ', REVERSE(Readline)) -1)) from #CommandOutput - Geoff Griswald
显示剩余2条评论

138

最简单的方法是....

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

3
如果没有找到匹配项,则不会出现“LEFT”或“SUBSTRING”函数的“无效长度参数”错误,结果为+1。 - Xilmiki
24
如果你的 [expr] 长度超过1个符号,你也需要将其反转! - Andrius Naruševičius

75
如果您正在使用 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

1
事后看来可能很明显,但如果你正在搜索一个字符串而不是单个字符,你必须执行以下操作:LEN(@FilePath) - CHARINDEX(REVERSE(@FindString),REVERSE(@FilePath))。 - pkExec

37
您在文本数据类型方面受到了少量函数的限制
我能建议的是从PATINDEX开始,但是从DATALENGTH-1,DATALENGTH-2,DATALENGTH-3等逆向推导,直到得出结果或者回溯到零(DATALENGTH-DATALENGTH)为止。
这确实是SQL Server 2000无法处理的事情。 其他答案的编辑:在SQL Server 2000中,REVERSE不在可用于文本数据的函数列表中。

1
是的,这很尴尬。这似乎应该很简单,但实际上并不是! - Raj
这就是为什么SQL 2005有varchar(max)来允许正常的函数。 - gbn
1
啊!所以"varchar(max)"是SQL 2005的东西,这就解释了为什么我在SQL 2000上尝试时它不起作用。 - Raj
DATALENGTH 对我来说无法产生正确的结果,但 LENGTH 可以。 - Tequila
@Tequila和其他人:DATALENGTH返回的是字节数而不是字符数。因此,对于NVARCHAR字符串,DATALENGTH返回的是字符串中字符数的2倍。然而,LEN返回的是字符数,减去任何尾随空格。我从不使用DATALENGTH来计算字符长度,除非尾随空格很重要,并且我确定我的数据类型是一致的,无论它们是VARCHAR还是NVARCHAR - rbsdca

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

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

7

虽然这是一个古老但仍然有效的问题,以下是我根据其他人提供的信息所创建的答案。

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

如果字符是字符串的第一个字符,则返回-1。 - Adrián E

7
这对我非常有效。
REVERSE(SUBSTRING(REVERSE([field]), CHARINDEX(REVERSE('[expr]'), REVERSE([field])) + DATALENGTH('[expr]'), DATALENGTH([field])))

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

在我看来,它的表现更好


6

嗯,我知道这是一个旧贴子,但在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上找到一篇好文章。
*编辑:删除了n <= LEN(TEXT_FIELD),因为您不能在TEXT类型上使用LEN()。 只要substring(...) = @delim仍然正确,结果仍然正确。

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

3
此答案使用MS SQL Server 2008(我无法访问MS SQL Server 2000),但根据OP的看法,有三种情况需要考虑。根据我尝试的内容,这里没有任何一个答案涵盖了所有三个问题:
1. 返回给定字符串中搜索字符的最后一个索引。 2. 返回给定字符串中搜索子字符串(不仅是单个字符)的最后一个索引。 3. 如果给定字符串中没有搜索字符或子字符串,则返回0
我想到的函数有两个参数:
@String NVARCHAR(MAX):要搜索的字符串
@FindString NVARCHAR(MAX):要在@字符串中获取最后一个索引的单个字符或子字符串
它返回一个INT,它可以是@ FindString在@ String中的正数索引,也可以是0,这意味着@ FindString不在@ String中。
以下是函数所做的内容的解释:
1. 将@ ReturnVal初始化为0,表示@ FindString不在@ String中 2. 使用CHARINDEX()检查@ String中@ FindString的索引 3. 如果在@ String中@ FindString的索引为0,则将@ ReturnVal保留为0 4. 如果在@ String中@ FindString的索引> 0,则@ FindString在@ String中,因此通过使用REVERSE()计算@ String中@ FindString的最后一个索引 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+。

祝使用愉快。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接