SQL Server – 在string中find第n个匹配项

我有一个表列,其中包含值如abc_1_2_3_4.gifzzz_12_3_3_45.gif

我想在上面的值中find每个下划线 _ 的索引 。 只会有四个下划线,但考虑到它们可以位于string中的任何位置,我怎样才能做到这一点?

我已经尝试了子string和charindex函数,但我只能可靠地得到第一个。 有任何想法吗?

一种方法(2K8);

 select 'abc_1_2_3_4.gif  ' as img into #T insert #T values ('zzz_12_3_3_45.gif') ;with T as ( select 0 as row, charindex('_', img) pos, img from #T union all select pos + 1, charindex('_', img, pos + 1), img from T where pos > 0 ) select img, pos from T where pos > 0 order by img, pos >>>> img pos abc_1_2_3_4.gif 4 abc_1_2_3_4.gif 6 abc_1_2_3_4.gif 8 abc_1_2_3_4.gif 10 zzz_12_3_3_45.gif 4 zzz_12_3_3_45.gif 7 zzz_12_3_3_45.gif 9 zzz_12_3_3_45.gif 11 

更新

 ;with T(img, starts, pos) as ( select img, 1, charindex('_', img) from #t union all select img, pos + 1, charindex('_', img, pos + 1) from t where pos > 0 ) select *, substring(img, starts, case when pos > 0 then pos - starts else len(img) end) token from T order by img, starts >>> img starts pos token abc_1_2_3_4.gif  1 4 abc abc_1_2_3_4.gif  5 6 1 abc_1_2_3_4.gif  7 8 2 abc_1_2_3_4.gif  9 10 3 abc_1_2_3_4.gif  11 0 4.gif zzz_12_3_3_45.gif 1 4 zzz zzz_12_3_3_45.gif 5 7 12 zzz_12_3_3_45.gif 8 9 3 zzz_12_3_3_45.gif 10 11 3 zzz_12_3_3_45.gif 12 0 45.gif 

您可以使用以下function通过delimiter split the values 。 它会return a table并find第n个发生只是做一个select ! 或者稍微改变一下,以便return你所需要的而不是table

 CREATE FUNCTION dbo.Split ( @RowData nvarchar(2000), @SplitOn nvarchar(5) ) RETURNS @RtnValue table ( Id int identity(1,1), Data nvarchar(100) ) AS BEGIN Declare @Cnt int Set @Cnt = 1 While (Charindex(@SplitOn,@RowData)>0) Begin Insert Into @RtnValue (data) Select Data = ltrim(rtrim(Substring(@RowData,1,Charindex(@SplitOn,@RowData)-1))) Set @RowData = Substring(@RowData,Charindex(@SplitOn,@RowData)+1,len(@RowData)) Set @Cnt = @Cnt + 1 End Insert Into @RtnValue (data) Select Data = ltrim(rtrim(@RowData)) Return END 

您可以使用CHARINDEX并指定起始位置:

 DECLARE @x VARCHAR(32) = 'MS-SQL-Server'; SELECT STUFF(STUFF(@x,3 , 0, '/'), 8, 0, '/') InsertString ,CHARINDEX('-',LTRIM(RTRIM(@x))) FirstIndexOf ,CHARINDEX('-',LTRIM(RTRIM(@x)), (CHARINDEX('-', LTRIM(RTRIM(@x)) )+1)) SecondIndexOf ,CHARINDEX('-',@x,CHARINDEX('-',@x, (CHARINDEX('-',@x)+1))+1) ThirdIndexOf ,CHARINDEX('-',REVERSE(LTRIM(RTRIM(@x)))) LastIndexOf; GO 

您可以通过这种方式查找四个下划线 :

 create table #test ( t varchar(50) ); insert into #test values ( 'abc_1_2_3_4.gif'), ('zzz_12_3_3_45.gif'); declare @t varchar(50); declare @t_aux varchar(50); declare @t1 int; declare @t2 int; declare @t3 int; declare @t4 int; DECLARE t_cursor CURSOR FOR SELECT t FROM #test OPEN t_cursor FETCH NEXT FROM t_cursor into @t;​ set @t1 = charindex( '_', @t ) set @t2 = charindex( '_', @t , @t1+1) set @t3 = charindex( '_', @t , @t2+1) set @t4 = charindex( '_', @t , @t3+1) select @t1, @t2, t3, t4 --do a loop to iterate over all table 

你可以在这里testing。

或者以这种简单的方式:

 select charindex( '_', t ) as first, charindex( '_', t, charindex( '_', t ) + 1 ) as second, ... from #test 

你可以尝试剥离variables/数组,假设你的列表清晰

 declare @array table ----table of values ( id int identity(1,1) ,value nvarchar(max) ) DECLARE @VALUE NVARCHAR(MAX)='val1_val2_val3_val4_val5_val6_val7'----string array DECLARE @CURVAL NVARCHAR(MAX) ---current value DECLARE @DELIM NVARCHAR(1)='_' ---delimiter DECLARE @BREAKPT INT ---current index of the delimiter WHILE EXISTS (SELECT @VALUE) BEGIN SET @BREAKPT=CHARINDEX(@DELIM,@VALUE) ---set the current index --- If @BREAKPT<> 0 ---index at 0 breaks the loop begin SET @CURVAL=SUBSTRING(@VALUE,1,@BREAKPT-1) ---current value set @VALUE=REPLACE(@VALUE,SUBSTRING(@VALUE,1,@BREAKPT),'') ---current value and delimiter, replace insert into @array(value) ---insert data select @CURVAL end else begin SET @CURVAL=@VALUE ---current value now last value insert into @array(value) ---insert data select @CURVAL break ---break loop end end select * from @array ---find nth occurance given the id 
 DECLARE @str AS VARCHAR(100) SET @str='1,2 , 3, 4, 5,6' SELECT COALESCE(LTRIM(CAST(('<X>'+REPLACE(@str,',' ,'</X><X>')+'</X>') AS XML).value('(/X)[1]', 'varchar(128)')), ''), COALESCE(LTRIM(CAST(('<X>'+REPLACE(@str,',' ,'</X><X>')+'</X>') AS XML).value('(/X)[2]', 'varchar(128)')), ''), COALESCE(LTRIM(CAST(('<X>'+REPLACE(@str,',' ,'</X><X>')+'</X>') AS XML).value('(/X)[3]', 'varchar(128)')), ''), COALESCE(LTRIM(CAST(('<X>'+REPLACE(@str,',' ,'</X><X>')+'</X>') AS XML).value('(/X)[4]', 'varchar(128)')), ''), COALESCE(LTRIM(CAST(('<X>'+REPLACE(@str,',' ,'</X><X>')+'</X>') AS XML).value('(/X)[5]', 'varchar(128)')), ''), COALESCE(LTRIM(CAST(('<X>'+REPLACE(@str,',' ,'</X><X>')+'</X>') AS XML).value('(/X)[6]', 'varchar(128)')), ''), COALESCE(LTRIM(CAST(('<X>'+REPLACE(@str,',' ,'</X><X>')+'</X>') AS XML).value('(/X)[7]', 'varchar(128)')), ''), COALESCE(LTRIM(CAST(('<X>'+REPLACE(@str,',' ,'</X><X>')+'</X>') AS XML).value('(/X)[8]', 'varchar(128)')), ''), COALESCE(LTRIM(CAST(('<X>'+REPLACE(@str,',' ,'</X><X>')+'</X>') AS XML).value('(/X)[9]', 'varchar(128)')), '') 
 DECLARE @LEN INT DECLARE @VAR VARCHAR(20) SET @VAR = 'HELLO WORLD' SET @LEN = LEN(@VAR) --SELECT @LEN SELECT PATINDEX('%O%',SUBSTRING(@VAR,PATINDEX('%O%' ,@VAR) + 1 ,PATINDEX('%O%',@VAR) + 1)) + PATINDEX('%O%',@VAR) 

我的SQL支持一个substring_Index的函数,在那里它将返回一个string中n值的位置值。 可以写一个类似的用户定义函数来实现这一点。 链接中的示例

或者你可以使用charindex函数调用它x次来报告每个_的位置,给定一个先前find的实例的起始位置+1。 直到find0

编辑:NM Charindex是正确的function

我做了这个创build几个单独的自定义函数,每个search字符的位置,即第二,第三:

CREATE FUNCTION [dbo]。[fnCHARPOS2](@SEARCHCHAR VARCHAR(255),@SEARCHSTRING VARCHAR(255))返回INT作为开始返回CHARINDEX(@ SEARCHCHAR,@ SEARCHSTRING(CHARINDEX(@ SEARCHCHAR,@ SEARCHSTRING,0)+1) );

 CREATE FUNCTION [dbo].[fnCHARPOS3] (@SEARCHCHAR VARCHAR(255), @SEARCHSTRING VARCHAR(255)) RETURNS INT AS BEGIN RETURN CHARINDEX(@SEARCHCHAR,@SEARCHSTRING, (CHARINDEX(@SEARCHCHAR,@SEARCHSTRING, (CHARINDEX(@SEARCHCHAR,@SEARCHSTRING,0)+1)))+1); 

然后,您可以将您正在search的字符和正在search的string作为参数传入:

所以如果你正在寻找'f'并想知道第一次出现的位置:

 select database.dbo.fnCHARPOS2('f',tablename.columnname), database.dbo.fnCHARPOS3('f',tablename.columnname) from tablename 

它为我工作!

你可以在里面使用相同的函数+1 charindex(' ',[TEXT],(charindex(' ',[TEXT],1))+ 1),其中+1是你想要查找的第n次。

我使用了一个函数来获取分隔string字段中的“nth”元素,并取得了巨大的成功。 就像上面提到的那样,这不是处理事情的“快速”方式,但确实是方便的。

 create function GetArrayIndex(@delimited nvarchar(max), @index int, @delimiter nvarchar(100) = ',') returns nvarchar(max) as begin declare @xml xml, @result nvarchar(max) set @xml = N'<root><r>' + replace(@delimited, @delimiter,'</r><r>') + '</r></root>' select @result = r.value('.','varchar(max)') from @xml.nodes('//root/r[sql:variable("@index")]') as records(r) return @result end 

一个简单的例子来做到这一点与XML转换:

 SELECT 'A|B|C' , concat('<x>', REPLACE('A|B|C', '|', '</x><x>'), '</x>') , cast(concat('<x>', REPLACE('A|B|C', '|', '</x><x>'), '</x>') as xml).query('/x[2]') , cast(concat('<x>', REPLACE('A|B|C', '|', '</x><x>'), '</x>') as xml).value('/x[2]', 'varchar'); 

在这里为您的示例翻译:

 SELECT gifname ,cast(concat('<x>', REPLACE(gifname, '_', '</x><x>'), '</x>') as xml).query('/x[2]') as xmlelement , cast(concat('<x>', REPLACE(gifname, '_', '</x><x>'), '</x>') as xml).value('/x[2]', 'varchar(10)') as result FROM ( SELECT 'abc_1_2_3_4.gif' as gifname UNION ALL SELECT 'zzz_12_3_3_45.gif' ) tmp 

我决定使用recursion函数,因为对我来说,更容易遵循逻辑。 请注意,SQL Server的默认函数recursion限制为32,所以这只适用于较小的工作负载。

 create function dbo._charindex_nth ( @FindThis varchar(8000), @InThis varchar(max), @StartFrom int, @NthOccurence tinyint ) returns bigint as begin /* Recursive helper used by dbo.charindex_nth to return the position of the nth occurance of @FindThis in @InThis Who When What PJR 160421 Initial */ declare @Pos bigint if isnull(@NthOccurence, 0) <= 0 or isnull(@StartFrom, 0) <= 0 begin select @Pos = 0 end else begin if @NthOccurence = 1 begin select @Pos = charindex(@FindThis, @InThis, @StartFrom) end else begin select @Pos = dbo._charindex_nth(@FindThis, @InThis, nullif(charindex(@FindThis, @InThis, @StartFrom), 0) + 1, @NthOccurence - 1) end end return @Pos end create function dbo.charindex_nth ( @FindThis varchar(8000), @InThis varchar(max), @NthOccurence tinyint ) returns bigint as begin /* Returns the position of the nth occurance of @FindThis in @InThis Who When What PJR 160421 Initial */ return dbo._charindex_nth(@FindThis, @InThis, 1, @NthOccurence) end declare @val varchar(max) = 'zzz_12_3_3_45.gif' select dbo.charindex_nth('_', @val, 1) Underscore1 , dbo.charindex_nth('_', @val, 2) Underscore2 , dbo.charindex_nth('_', @val, 3) Underscore3 , dbo.charindex_nth('_', @val, 4) Underscore4 

我在SQL Server中使用PATINDEX和一个返回值数组的Regex CLR程序集做了类似的事情。 如果你想尝试,我可以上class时上传样品。

我正在用更快的方式来做这个,而不是简单地遍历string。

 CREATE FUNCTION [ssf_GetNthSeparatorPosition] ( @TargetString VARCHAR(MAX) , @Sep VARCHAR(25) , @n INTEGER ) RETURNS INTEGER /**************************************************************************************** --############################################################################# -- Returns the position of the Nth Charactor sequence -- 1234567890123456789 -- Declare @thatString varchar(max) = 'hi,there,jay,yo' Select dbo.ssf_GetNthSeparatorPosition(@thatString, ',', 3) --would return 13 --############################################################################ ****************************************************************************************/ AS BEGIN DECLARE @Retval INTEGER = 0 DECLARE @CurPos INTEGER = 0 DECLARE @LenSep INTEGER = LEN(@Sep) SELECT @CurPos = CHARINDEX(@Sep, @TargetString) IF ISNULL(@LenSep, 0) > 0 AND @CurPos > 0 BEGIN SELECT @CurPos = 0 ;with lv0 AS (SELECT 0 g UNION ALL SELECT 0) ,lv1 AS (SELECT 0 g FROM lv0 a CROSS JOIN lv0 b) -- 4 ,lv2 AS (SELECT 0 g FROM lv1 a CROSS JOIN lv1 b) -- 16 ,lv3 AS (SELECT 0 g FROM lv2 a CROSS JOIN lv2 b) -- 256 ,lv4 AS (SELECT 0 g FROM lv3 a CROSS JOIN lv3 b) -- 65,536 ,lv5 AS (SELECT 0 g FROM lv4 a CROSS JOIN lv4 b) -- 4,294,967,296 ,Tally (n) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM lv5), results AS ( SELECT n - LEN(@Sep) AS Nth , row_number() OVER ( ORDER BY n ) - 1 AS Position FROM Tally t WHERE n BETWEEN 1 AND DATALENGTH(@TargetString) + DATALENGTH(@Sep) AND SUBSTRING(@Sep + @TargetString, n, LEN(@Sep)) = @Sep) SELECT @CurPos = Nth FROM results WHERE results.Position = @n END RETURN @CurPos END GO 

检查使用parsename作为这个stackexchange职位的潜在替代品。

https://dba.stackexchange.com/questions/42393/get-the-2nd-or-3rd-occurrence-of-a-value-in-a-delimited-string

 declare @a nvarchar(50)='Enter Your string ' declare @character char='e' declare @nthoccurence int = 2 declare @i int = 1 declare @j int =0 declare @count int = len(@a)-len(replace(@a,@character,'')) if(@count >= @nthoccurence) begin while (@I <= @nthoccurence) begin set @j= CHARINDEX(@character,@a,@j+1) set @i= @i+1 end print @j end else Print 'you have only '+convert(nvarchar ,@count)+' occurrences of '+@character end