SQL Server - 在字符串中查找第n个匹配项

时间:2012-01-04 11:30:29

标签: sql-server

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

我想在上面的值中找到每个下划线的索引 _。只有四个下划线,但鉴于它们可以在弦中的任何位置,我怎么能实现这一点呢?

我尝试了substringcharindex功能,但我只能可靠地掌握第一个功能。有什么想法吗?

18 个答案:

答案 0 :(得分:34)

单程(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

答案 1 :(得分:10)

您可以在位置+1

中使用相同的功能
charindex('_', [TEXT], (charindex('_', [TEXT], 1))+1)

+1是你想要找到的第n次。

答案 2 :(得分:7)

您可以function使用以下split the valuesdelimiter。它return a table并找到第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

答案 3 :(得分:7)

您可以使用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

答案 4 :(得分:4)

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)')), '')

答案 5 :(得分:2)

你可以look for the four underscore in this way

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

你可以在这里测试一下。

或者用这种简单的方式:

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

答案 6 :(得分:2)

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)

答案 7 :(得分:2)

您可以尝试剥离变量/数组,假设列表中有清晰度

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

答案 8 :(得分:1)

DECLARE @T AS TABLE(pic_name VARCHAR(100));
INSERT INTO @T VALUES ('abc_1_2_3_4.gif'),('zzz_12_3_3_45.gif');

SELECT A.pic_name, P1.D, P2.D, P3.D, P4.D 
FROM @T A
CROSS APPLY (SELECT NULLIF(CHARINDEX('_', A.pic_name),0) AS D)  P1
CROSS APPLY (SELECT NULLIF(CHARINDEX('_', A.pic_name, P1.D+1), 0) AS D)  P2
CROSS APPLY (SELECT NULLIF(CHARINDEX('_', A.pic_name, P2.D+1),0) AS D)  P3
CROSS APPLY (SELECT NULLIF(CHARINDEX('_', A.pic_name, P3.D+1),0) AS D)  P4

答案 9 :(得分:1)

我决定使用递归函数,因为对我而言,遵循逻辑更容易。请注意,SQL Server的默认函数递归限制为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

答案 10 :(得分:1)

我这样做了几个单独的自定义函数,一个用于搜索字符的每个位置,即第二个,第三个:

创建功能[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);

然后,您可以将要搜索的字符和要搜索的字符串作为参数传入:

所以,如果你正在寻找&#39; f&#39;并想知道前3次出现的位置:

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

它对我有用!

答案 11 :(得分:1)

我的SQL支持substring_Index的功能,它将返回n出现的字符串中值的位置。可以编写类似的用户定义函数来实现此目的。 link

中的示例

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

编辑:NM Charindex是正确的功能

答案 12 :(得分:0)

我用更快的方式来做这件事,而不是简单地遍历字符串。

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

答案 13 :(得分:0)

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

答案 14 :(得分:0)

我使用了一个函数从分隔的字符串字段中获取“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    

答案 15 :(得分:0)

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

答案 16 :(得分:0)

DECLARE @x VARCHAR(32) = 'MS-SQL-Server';

SELECT 
SUBSTRING(@x,0,CHARINDEX('-',LTRIM(RTRIM(@x)))) A,
SUBSTRING(@x,CHARINDEX('-',LTRIM(RTRIM(@x)))+1,CHARINDEX('-' 
,LTRIM(RTRIM(@x)))) B,
SUBSTRING(@x,CHARINDEX('-',REVERSE(LTRIM(RTRIM(@x))))+1,LEN(@x)-1) C


A   B   C
MS  SQL Server

答案 17 :(得分:0)

尝试一下

CREATE FUNCTION [dbo].[CHARINDEX2] (
    @expressionToFind VARCHAR(MAX),
    @expressionToSearch VARCHAR(MAX),
    @occurrenceIndex INT,
    @startLocation INT = 0
)
RETURNS INT
AS BEGIN

IF @occurrenceIndex < 1 BEGIN
    RETURN CAST('The argument @occurrenceIndex must be a positive integer.' AS INT)
END

IF @startLocation < 0 BEGIN
    RETURN CAST('The argument @startLocation must be a non negative integer.' AS INT)
END

DECLARE @returnIndex INT

SET @returnIndex = CHARINDEX(@expressionToFind, @expressionToSearch, @startLocation)

IF (@occurrenceIndex = 1) BEGIN
    RETURN @returnIndex
END

DECLARE @target_length INT
SET @target_length = LEN(@expressionToFind)
SET @occurrenceIndex += -1

WHILE (@occurrenceIndex > 0 AND @returnIndex > 0) BEGIN
    SET @returnIndex = CHARINDEX(@expressionToFind, @expressionToSearch, @returnIndex + @target_length);
    SET @occurrenceIndex += -1
END

RETURN @returnIndex

END
GO