【问题标题】:SQL Server - find nth occurrence in a stringSQL Server - 在字符串中查找第 n 个出现
【发布时间】:2012-01-04 11:30:28
【问题描述】:

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

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

我已经尝试过substringcharindex 函数,但我只能可靠地掌握第一个函数。有什么想法吗?

【问题讨论】:

  • 光标是你的解决方案?
  • @adrianos - 您期望的数据集有多大?什么版本的 SQL 服务器?

标签: sql-server


【解决方案1】:

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

【讨论】:

  • 嗯,无效的对象名称“#t”。
  • 它来自我的测试数据@顶部... into #T替换正确的对象名称
  • 真的很抱歉,无法以这种方式工作 - sqlserver 不是我的强项! +1 表示感谢。
  • 有没有办法将它与 NVARCHAR(MAX) 或 TEXT 列一起使用?到目前为止,如果没有定义长度,它将失败,并显示“锚点和 X 列中的递归部分之间的类型不匹配”
  • 对于 NVARCHAR(MAX) 尝试将 select img, 1, charindex('_', img) from #t 更改为 select img, cast(1 as bigint), charindex('_', img) from #t
【解决方案2】:

你可以在+1这个位置使用相同的函数

charindex('_', [TEXT], (charindex('_', [TEXT], 1))+1)

+1 是您第 n 次要查找的位置。

【讨论】:

  • 此方法存在错误。此方法仅在您确定出现 n 时才有效。如果您尝试查找第 3 次出现并且您在第一次出现 charindex 时出现了 1 次,那么您会得到 x,但是当您搜索“x+1”时,您会得到 0,但现在您从 1 开始搜索,然后再次得到 x。所以如果你只有一次出现,你会得到它的位置或 1 作为输出。
  • 这会找到第二个下划线但charindex('_', [TEXT], (charindex('_', [TEXT], 1))+2) 不会找到第三个,这就是我认为你所暗示的。相反,charindex('_', [TEXT], (charindex('_', [TEXT], 1))+charindex('_', [TEXT], (charindex('_', [TEXT], 1))+1)) 会找到第三个下划线。
【解决方案3】:

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

    您可以通过delimiter 使用以下functionsplit the values。它会是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
    

    【讨论】:

    • 不是速度恶魔功能,但对于我似乎必须不断编写的这些一次性查询非常有用!效果很好!
    • @Limey 如果它多次发生,请尝试使用其他解决方案。游标不是 sql server 的方式;)
    【解决方案5】:
    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)')), '')
    

    【讨论】:

    • 谢谢。只需要将列表中的 3 个项目分成一个表格,所有其他解决方案都太 looooooooooong:)
    【解决方案6】:

    你可以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
    

    【讨论】:

      【解决方案7】:

      您可以尝试剥离变量/数组,假设您的列表中有区别

      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】:
        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)
        

        【讨论】:

        • 在您的代码中添加解释是值得的。代码转储经常被否决,可能会被删除。见How to Answer
        • 用于字符串中第二次出现的字符
        • 您的答案下方有一个小编辑按钮。选择它并在其中添加说明。您看到的评论可能会被删除,我不希望发生这种情况。
        • 感谢您提供此代码 sn-p,它可能会提供一些即时帮助。正确解释would greatly improve 其教育价值,通过展示为什么这是一个很好的解决问题的方法,并将使其对未来有类似但不相同问题的读者更有用。请edit您的答案添加解释,并说明适用的限制和假设。
        【解决方案9】:

        My SQL 支持 substring_Index 函数,它将返回字符串中第 n 次出现的值的位置。可以编写一个类似的用户定义函数来实现这一点。 link 中的示例

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

        编辑:NM Charindex 是正确的函数

        【讨论】:

        • 我喜欢 MySQL 中的 substring_index,它通过一个查询来完成。不知道为什么 MS 认为这不能在 SQL Server 中实现超出了我的范围。不需要火箭科学家就能实现这样的东西!
        【解决方案10】:

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

        创建函数 [dbo].[fnCHARPOS2] (@SEARCHCHAR VARCHAR(255), @SEARCHSTRING VARCHAR(255)) 返回整数 作为 开始 返回 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);
        

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

        因此,如果您正在搜索“f”并想知道第 3 次出现的位置:

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

        它对我有用!

        【讨论】:

          【解决方案11】:

          我决定使用递归函数,因为对我来说更容易遵循逻辑。请注意,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
          

          【讨论】:

            【解决方案12】:
            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
            

            【讨论】:

              【解决方案13】:

              我已经使用一个函数从一个分隔的字符串字段中获取“第 n 个”元素,并取得了巨大的成功。就像上面提到的,这不是一种“快速”的处理方式,但它确实很方便。

              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    
              

              【讨论】:

                【解决方案14】:

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

                【讨论】:

                  【解决方案15】:

                  我正在尝试一种比简单地遍历字符串更快的方法。

                  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
                  

                  【讨论】:

                    【解决方案16】:
                    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
                    

                    【讨论】:

                    • 请解释您的代码以及它与其他已回答的解决方案的不同之处。
                    【解决方案17】:
                    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
                    

                    【讨论】:

                      【解决方案18】:

                      受 Alex K 的回复 One way (2k8) 的启发,我为 SQL Server 的令牌函数创建了一个脚本,用于从字符串返回特定令牌。 我需要这个来将 SSIS 包重构为 T-SQL,而无需手动多次实施 Alex 的解决方案。 我的函数有一个缺点:它将令牌值作为表(一列,一行)而不是作为 varchar 值返回。如果有人对此有解决方案,请告诉我。

                      DROP FUNCTION [RDW].[token]
                      GO
                      
                      create function [RDW].[token] (@string varchar(8000), @split varchar(50), @returnIndex int) 
                      returns table  
                      as 
                          return with T(img, starts, pos, [index]) as ( 
                              select @string, 1, charindex(@split, @string), 0 
                              union all 
                              select @string, pos + 1, charindex(@split, @string, pos + 1), [index]+1 
                              from t 
                              where pos > 0
                          )
                          select substring(img, starts, case when pos > 0 then pos - starts else len(img) end) token
                          from T
                          where [index] = @returnIndex 
                      GO
                      

                      【讨论】:

                        【解决方案19】:

                        试试这个

                        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
                        

                        【讨论】:

                          猜你喜欢
                          • 2011-04-27
                          • 2011-02-04
                          • 2010-12-25
                          • 1970-01-01
                          • 1970-01-01
                          • 2021-05-19
                          • 1970-01-01
                          相关资源
                          最近更新 更多