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


127

有没有一种简单的方法可以使用SQL查找字符串最后一次出现的索引?我现在正在使用SQL Server 2000。我基本上需要.NET System.String.LastIndexOf方法提供的功能。稍作谷歌搜索就发现了这个功能 - 检索上一个索引的功能 -但是如果您传递“文本”列表达式则无法使用。只要您要搜索的文本长度为1个字符,其他地方找到的其他解决方案就可以使用。

我可能必须准备一个功能。如果这样做,我会将其发布在这里,以便大家查看并使用。

Answers:


33

文本数据类型的功能仅限于一小部分

我所建议的只是从开始PATINDEX,但从DATALENGTH-1, DATALENGTH-2, DATALENGTH-3etc 开始反向进行,直到获得结果或结束为零(DATALENGTH-DATALENGTH)

这确实是SQL Server 2000无法处理的。

编辑其他答案:REVERSE不在SQL Server 2000中可与文本数据一起使用的功能列表中


1
是的,这很尴尬。这似乎应该很简单,但事实并非如此!
拉吉(Raj)

...这就是为什么SQL 2005具有varchar(max)以允许正常功能的原因
gbn

1
啊! 所以“VARCHAR(最大)”是一个SQL 2005的事情,这可以解释为什么它没有工作时,我试图在SQL 2000
拉吉

尽管LENGTH可以工作,但DATALENGTH无法为我产生正确的结果。
龙舌兰酒

@Tequila等: DATALENGTH返回字节数而不是字符。因此,DATALENGTH对于NVARCHAR字符串,返回2 x字符串中的字符数。 LEN但是,返回的字符数减去任何结尾的空格。我从来没有使用DATALENGTH字符长度计算,除非结尾的空白是显著,我肯定知道我的数据类型是一致的,不管他们是VARCHARNVARCHAR
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了-但我似乎还记得您可以在其上使用LEFT和RIGHT吗?

菲利普


1
抱歉-我很确定我在使用2000时从未回过头,并且我目前无法访问任何SQL 2000安装。
菲利普·凯利2009年

辉煌!从来没有想过以这种方式解决这个问题!
贾里德(Jared)2010年

4
好一个!我根据自己的需要进行了修改:email.Substring(0,email.lastIndexOf('@'))== SELECT LEFT(电子邮件,LEN(电子邮件)-CHARINDEX('@',REVERSE(电子邮件)))
Fredrik Johansson

1
像这样的聪明东西就是为什么编程如此有趣!
克里斯

为什么不只在原件上使用向右而不是向左而不是额外的反向
菲尔

108

最简单的方法是...

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

3
1,因为不火的错误一样,如果没有匹配“传递给左或子字符串函数无效长度参数”
Xilmiki

12
如果您的[expr]符号超过1个,则也需要将其反转!
AndriusNaruševičius15年

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(@VERSED(@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获取每个分隔符的位置,那么你只能在该组的最大位置。

理货桌子真棒。如果您以前从未使用过它们,则可以在SQL Server Central上找到一篇不错的文章(免费注册,或者直接使用Bug Me Not(http://www.bugmenot.com/view/sqlservercentral.com))。

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


真好 我认为这实际上与gbn接受的答案是相同的解决方案。您只是使用一个表来存储从DATALENGTH中减去的整数1、2、3等,并从第一个字符开始读取,而不是从最后一个字符开始读取。
Michael Petito 2010年

2

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


好点子。我现在没有2000,而且我不记得自己做的时候是否能做到。
AK 2009年

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的权限),但是根据OP的观察方式,有3种情况需要考虑。根据我没有尝试的答案,这里涵盖了所有这三个方面:

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

我想出的函数需要两个参数:

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

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

它返回一个in INT的正索引或表示@FindString@String0@FindString@String

这是对该功能的解释:

  1. 初始化@ReturnVal0指示@FindString不在@String
  2. 检查的指标@FindString@String使用CHARINDEX()
  3. 如果@FindStringin 的索引@String0@ReturnVal则保留为0
  4. 如果指数@FindString@String> 0@FindString@String这样它计算的最后一个索引@FindString@String使用REVERSE()
  5. 返回@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+起了作用。

请享用。


1

我知道这将是低效的,但是您是否考虑过将text字段强制转换为varchar可以使用发现的网站提供的解决方案?我知道此解决方案会产生问题,因为如果text字段中的长度超过了您的长度,您可能会截断记录varchar(更不用说它的性能不是很高)。

由于您的数据位于text字段中(并且您正在使用SQL Server 2000),因此您的选择受到限制。


是的,因为要处理的数据经常超过“ varchar”中可以保存的最大值,所以不强制转换为“ varchar”。谢谢你的回答!
拉吉(Raj)

1

如果要获取单词字符串中最后一个空格的索引,可以使用此表达式RIGHT(name,(CHARINDEX('',REVERSE(name),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

即使子字符串包含多个字符,此代码也有效。

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

该答案符合OP的要求。特别是它允许针不止一个字符,并且在干草堆中找不到针时也不会产生错误。在我看来,大多数(全部?)其他答案都没有处理那些极端情况。除此之外,我还添加了本机MS SQL Server 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

0

我在寻找类似问题的解决方案时遇到了这个问题,该解决方案具有完全相同的要求,但针对的是另一种缺少数据库的数据库 REVERSE功能。

在我的情况下,这是针对OpenEdge(Progress)数据库的,该数据库的语法略有不同。这使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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.