【问题标题】:How do you count the number of occurrences of a certain substring in a SQL varchar?如何计算 SQL varchar 中某个子字符串的出现次数?
【发布时间】:2009-04-10 17:23:36
【问题描述】:

我有一列具有格式为 a、b、c、d 的值。有没有办法在 T-SQL 中计算该值中逗号的数量?

【问题讨论】:

    标签: sql-server tsql


    【解决方案1】:

    想到的第一种方法是通过用空字符串替换逗号并比较长度来间接做到这一点

    Declare @string varchar(1000)
    Set @string = 'a,b,c,d'
    select len(@string) - len(replace(@string, ',', ''))
    

    【讨论】:

    • 这回答了文本中所写的问题,但不是标题中所写的。要使其适用于多个字符,只需在事物周围添加 / len(searchterm) 即可。发布一个答案,以防它对某人有用。
    • 有人向我指出,这并不总是按预期工作。考虑以下内容: SELECT LEN('a,b,c,d ,') - LEN(REPLACE('a,b,c,d ,', ',', '')) 原因我还不明白, d 和最后一列之间的空格导致它返回 5 而不是 4。我将发布另一个解决此问题的答案,以防它对任何人有用。
    • 也许使用 DATALENGTH 而不是 LEN 会更好,因为 LEN 返回修剪后的字符串的大小。
    • DATALENGTH()/2 也很棘手,因为字符大小不明显。查看 stackoverflow.com/a/11080074/1094048 以获取获取字符串长度的简单而准确的方法。
    • @rodrigocl 为什么不将LTRIM 包裹在字符串周围,如下所示:SELECT LEN(RTRIM(@string)) - LEN(REPLACE(RTRIM(@string), ',', ''))
    【解决方案2】:

    cmsjr 答案的快速扩展,适用于具有多个字符的字符串。

    CREATE FUNCTION dbo.CountOccurrencesOfString
    (
        @searchString nvarchar(max),
        @searchTerm nvarchar(max)
    )
    RETURNS INT
    AS
    BEGIN
        return (LEN(@searchString)-LEN(REPLACE(@searchString,@searchTerm,'')))/LEN(@searchTerm)
    END
    

    用法:

    SELECT * FROM MyTable
    where dbo.CountOccurrencesOfString(MyColumn, 'MyString') = 1
    

    【讨论】:

    • 使用 DATALENGTH()/2 代替 LEN() 会稍有改进。 LEN 将忽略任何尾随空格,因此dbo.CountOccurancesOfString( 'blah ,', ',') 将返回 2 而不是 1,dbo.CountOccurancesOfString( 'hello world', ' ') 将失败并除以零。
    • Rory 的评论很有帮助。我发现我可以在 Andrew 的函数中将 LEN 替换为 DATALENGTH 并获得所需的结果。看起来除以 2 的数学运算方式是不必要的。
    • @AndrewBarrett : 当多个字符串长度相同时会追加什么?
    • DATALENGTH()/2 也很棘手,因为字符大小不明显。看看stackoverflow.com/a/11080074/1094048,方法简单准确。
    【解决方案3】:

    您可以将字符串的长度与去掉逗号的长度进行比较:

    len(value) - len(replace(value,',',''))
    

    【讨论】:

      【解决方案4】:

      @csmjr 的回答在某些情况下存在问题。

      他的回答是这样做:

      Declare @string varchar(1000)
      Set @string = 'a,b,c,d'
      select len(@string) - len(replace(@string, ',', ''))
      

      这在大多数情况下都有效,但是,请尝试运行:

      DECLARE @string VARCHAR(1000)
      SET @string = 'a,b,c,d ,'
      SELECT LEN(@string) - LEN(REPLACE(@string, ',', ''))
      

      由于某种原因,REPLACE 去掉了最后的逗号,但也去掉了它前面的空格(不知道为什么)。当您期望 4 时,这会导致返回值为 5。这是执行此操作的另一种方法,即使在这种特殊情况下也可以:

      DECLARE @string VARCHAR(1000)
      SET @string = 'a,b,c,d ,'
      SELECT LEN(REPLACE(@string, ',', '**')) - LEN(@string)
      

      请注意,您不需要使用星号。任何两个字符的替换都可以。这个想法是,对于要计算的字符的每个实例,将字符串加长一个字符,然后减去原始字符串的长度。这基本上是原始答案的相反方法,没有奇怪的修剪副作用。

      【讨论】:

      • “出于某种原因,REPLACE 去掉了最后的逗号,但也去掉了它前面的空格(不知道为什么)。” REPLACE 并没有去掉最后一个逗号和它前面的空格,实际上是 LEN 函数忽略了字符串末尾的空格,因为那个空格。
      【解决方案5】:

      以@Andrew 的解决方案为基础,使用非过程表值函数和 CROSS APPLY 可以获得更好的性能:

      SET ANSI_NULLS ON
      GO
      SET QUOTED_IDENTIFIER ON
      GO
      /*  Usage:
          SELECT t.[YourColumn], c.StringCount
          FROM YourDatabase.dbo.YourTable t
              CROSS APPLY dbo.CountOccurrencesOfString('your search string',     t.[YourColumn]) c
      */
      CREATE FUNCTION [dbo].[CountOccurrencesOfString]
      (
          @searchTerm nvarchar(max),
          @searchString nvarchar(max)
      
      )
      RETURNS TABLE
      AS
          RETURN 
          SELECT (DATALENGTH(@searchString)-DATALENGTH(REPLACE(@searchString,@searchTerm,'')))/NULLIF(DATALENGTH(@searchTerm), 0) AS StringCount
      

      【讨论】:

      • 我在许多遗留数据库中都使用了相同的功能,它对处理许多陈旧且设计不当的数据库有很大帮助。节省大量时间,即使在大型数据集上也非常快。
      【解决方案6】:
      Declare @string varchar(1000)
      
      DECLARE @SearchString varchar(100)
      
      Set @string = 'as as df df as as as'
      
      SET @SearchString = 'as'
      
      select ((len(@string) - len(replace(@string, @SearchString, ''))) -(len(@string) - 
              len(replace(@string, @SearchString, ''))) % 2)  / len(@SearchString)
      

      【讨论】:

      • 这实际上返回的实际计数减 1
      【解决方案7】:

      接受的答案是正确的, 将其扩展为在子字符串中使用 2 个或更多字符:

      Declare @string varchar(1000)
      Set @string = 'aa,bb,cc,dd'
      Set @substring = 'aa'
      select (len(@string) - len(replace(@string, @substring, '')))/len(@substring)
      

      【讨论】:

        【解决方案8】:

        我认为 Darrel Lee 有一个很好的答案。将CHARINDEX() 替换为PATINDEX(),你也可以沿着字符串做一些弱的regex 搜索...

        比如,假设你将它用于@pattern

        set @pattern='%[-.|!,'+char(9)+']%'
        

        为什么你可能想做这样疯狂的事情?

        假设您正在将分隔的文本字符串加载到临时表中,其中保存数据的字段类似于 varchar(8000) 或 nvarchar(max)...

        有时使用数据执行 ELT(提取-加载-转换)比使用 ETL(提取-转换-加载)更容易/更快,一种方法是将分隔记录按原样加载到临时表中,尤其是如果您可能想要一种更简单的方法来查看异常记录,而不是将它们作为 SSIS 包的一部分进行处理...但这是针对不同线程的圣战。

        【讨论】:

          【解决方案9】:

          如果我们知道 LEN 和空间有限制,为什么不能先替换空间? 那么我们就知道没有地方可以混淆LEN了。

          len(replace(@string, ' ', '-')) - len(replace(replace(@string, ' ', '-'), ',', ''))
          

          【讨论】:

            【解决方案10】:

            使用此代码,它运行良好。 我创建了一个接受两个参数的 sql 函数,第一个参数是我们要在其中搜索的长字符串,它可以接受最长为 1500 个字符的字符串(当然您可以扩展它甚至将其更改为文本数据类型)。 第二个参数是我们要计算其出现次数的子字符串(长度最多为200个字符,当然你可以根据需要更改)。并且输出是一个整数,代表频率的个数.....享受吧。


            CREATE FUNCTION [dbo].[GetSubstringCount]
            (
              @InputString nvarchar(1500),
              @SubString NVARCHAR(200)
            )
            RETURNS int
            AS
            BEGIN 
                    declare @K int , @StrLen int , @Count int , @SubStrLen int 
                    set @SubStrLen = (select len(@SubString))
                    set @Count = 0
                    Set @k = 1
                    set @StrLen =(select len(@InputString))
                While @K <= @StrLen
                    Begin
                        if ((select substring(@InputString, @K, @SubStrLen)) = @SubString)
                            begin
                                if ((select CHARINDEX(@SubString ,@InputString)) > 0)
                                    begin
                                    set @Count = @Count +1
                                    end
                            end
                                            Set @K=@k+1
                    end
                    return @Count
            end
            

            【讨论】:

              【解决方案11】:

              在 SQL 2017 或更高版本中,您可以使用:

              declare @hits int = 0
              set @hits = (select value from STRING_SPLIT('F609,4DFA,8499',','));
              select count(@hits)
              

              【讨论】:

                【解决方案12】:

                基于最佳答案和其他答案的改进版本:

                用分隔符包裹字符串可确保 LEN 正常工作。使替换字符串比匹配字符串长一个字符就不需要除法了。

                CREATE FUNCTION dbo.MatchCount(@value nvarchar(max), @match  nvarchar(max))
                RETURNS int
                BEGIN
                    RETURN LEN('[' + REPLACE(@value,@match,REPLICATE('*', LEN('[' + @match + ']') - 1)) + ']') - LEN('['+@value+']')
                END
                

                【讨论】:

                  【解决方案13】:
                  DECLARE @records varchar(400)
                  SELECT @records = 'a,b,c,d'
                  select  LEN(@records) as 'Before removing Commas' , LEN(@records) - LEN(REPLACE(@records, ',', '')) 'After Removing Commans'
                  

                  【讨论】:

                    【解决方案14】:

                    对于单字符和多字符搜索,以下内容应该可以解决问题:

                    CREATE FUNCTION dbo.CountOccurrences
                    (
                       @SearchString VARCHAR(1000),
                       @SearchFor    VARCHAR(1000)
                    )
                    RETURNS TABLE
                    AS
                       RETURN (
                                 SELECT COUNT(*) AS Occurrences
                                 FROM   (
                                           SELECT ROW_NUMBER() OVER (ORDER BY O.object_id) AS n
                                           FROM   sys.objects AS O
                                        ) AS N
                                        JOIN (
                                                VALUES (@SearchString)
                                             ) AS S (SearchString)
                                             ON
                                             SUBSTRING(S.SearchString, N.n, LEN(@SearchFor)) = @SearchFor
                              );
                    GO
                    
                    ---------------------------------------------------------------------------------------
                    -- Test the function for single and multiple character searches
                    ---------------------------------------------------------------------------------------
                    DECLARE @SearchForComma      VARCHAR(10) = ',',
                            @SearchForCharacters VARCHAR(10) = 'de';
                    
                    DECLARE @TestTable TABLE
                    (
                       TestData VARCHAR(30) NOT NULL
                    );
                    
                    INSERT INTO @TestTable
                         (
                            TestData
                         )
                    VALUES
                         ('a,b,c,de,de ,d e'),
                         ('abc,de,hijk,,'),
                         (',,a,b,cde,,');
                    
                    SELECT TT.TestData,
                           CO.Occurrences AS CommaOccurrences,
                           CO2.Occurrences AS CharacterOccurrences
                    FROM   @TestTable AS TT
                           OUTER APPLY dbo.CountOccurrences(TT.TestData, @SearchForComma) AS CO
                           OUTER APPLY dbo.CountOccurrences(TT.TestData, @SearchForCharacters) AS CO2;
                    

                    使用数字表(dbo.Nums)可以稍微简化该函数:

                       RETURN (
                                 SELECT COUNT(*) AS Occurrences
                                 FROM   dbo.Nums AS N
                                        JOIN (
                                                VALUES (@SearchString)
                                             ) AS S (SearchString)
                                             ON
                                             SUBSTRING(S.SearchString, N.n, LEN(@SearchFor)) = @SearchFor
                              );
                    

                    【讨论】:

                      【解决方案15】:

                      我终于写了这个应该涵盖所有可能情况的函数,在输入中添加一个 char 前缀和后缀。此 char 被评估为与搜索参数中包含的任何 char 不同,因此它不会影响结果。

                      CREATE FUNCTION [dbo].[CountOccurrency]
                      (
                      @Input nvarchar(max),
                      @Search nvarchar(max)
                      )
                      RETURNS int AS
                      BEGIN
                          declare @SearhLength as int = len('-' + @Search + '-') -2;
                          declare @conteinerIndex as int = 255;
                          declare @conteiner as char(1) = char(@conteinerIndex);
                          WHILE ((CHARINDEX(@conteiner, @Search)>0) and (@conteinerIndex>0))
                          BEGIN
                              set @conteinerIndex = @conteinerIndex-1;
                              set @conteiner = char(@conteinerIndex);
                          END;
                          set @Input = @conteiner + @Input + @conteiner
                          RETURN (len(@Input) - len(replace(@Input, @Search, ''))) / @SearhLength
                      END 
                      

                      用法

                      select dbo.CountOccurrency('a,b,c,d ,', ',')
                      

                      【讨论】:

                        【解决方案16】:
                        Declare @MainStr nvarchar(200)
                        Declare @SubStr nvarchar(10)
                        Set @MainStr = 'nikhildfdfdfuzxsznikhilweszxnikhil'
                        Set @SubStr = 'nikhil'
                        Select (Len(@MainStr) - Len(REPLACE(@MainStr,@SubStr,'')))/Len(@SubStr)
                        

                        【讨论】:

                          【解决方案17】:

                          此 T-SQL 代码查找并打印句子 @s 中所有出现的模式 @p。之后可以对句子进行任何处理。

                          declare @old_hit int = 0
                          declare @hit int = 0
                          declare @i int = 0
                          declare @s varchar(max)='alibcalirezaalivisualization'
                          declare @p varchar(max)='ali'
                           while @i<len(@s)
                            begin
                             set @hit=charindex(@p,@s,@i)
                             if @hit>@old_hit 
                              begin
                              set @old_hit =@hit
                              set @i=@hit+1
                              print @hit
                             end
                            else
                              break
                           end
                          

                          结果是: 1 6 13 20

                          【讨论】:

                            【解决方案18】:

                            我最终为此使用了 CTE 表,

                            CREATE TABLE #test (
                             [id] int,
                             [field] nvarchar(500)
                            )
                            
                            INSERT INTO #test ([id], [field])
                            VALUES (1, 'this is a test string http://url, and https://google.com'),
                                   (2, 'another string, hello world http://example.com'),
                                   (3, 'a string with no url')
                            
                            SELECT *
                            FROM #test
                            
                            ;WITH URL_count_cte ([id], [url_index], [field])
                            AS
                            (
                                SELECT [id], CHARINDEX('http', [field], 0)+1 AS [url_index], [field]
                                FROM #test AS [t]
                                WHERE CHARINDEX('http', [field], 0) != 0
                                UNION ALL
                                SELECT [id], CHARINDEX('http', [field], [url_index])+1 AS [url_index], [field]
                                FROM URL_count_cte
                                WHERE CHARINDEX('http', [field], [url_index]) > 0
                            )
                            
                            -- total urls
                            SELECT COUNT(1)
                            FROM URL_count_cte
                            
                            -- urls per row
                            SELECT [id], COUNT(1) AS [url_count]
                            FROM URL_count_cte
                            GROUP BY [id]
                            

                            【讨论】:

                              【解决方案19】:

                              使用这个函数,你可以得到一个文本中单词的重复次数。

                              /****** Object:  UserDefinedFunction [dbo].[fn_getCountKeywords]    Script Date: 22/11/2021 17:52:00 ******/
                              DROP FUNCTION IF EXISTS [dbo].[fn_getCountKeywords]
                              GO
                              /****** Object:  UserDefinedFunction [dbo].[fn_getCountKeywords]    Script Date: 2211/2021 17:52:00 ******/
                              SET ANSI_NULLS OFF
                              GO
                              SET QUOTED_IDENTIFIER ON
                              GO
                              -- =============================================
                              -- Author:      m_Khezrian
                              -- Create date: 2021/11/22-17:52
                              -- Description: Return Count Keywords In Input Text
                              -- =============================================
                              
                              Create OR Alter Function [dbo].[fn_getCountKeywords]
                                  (@Text      nvarchar(max)
                                  ,@Keywords  nvarchar(max)
                                  )
                              RETURNS @Result TABLE
                              (    
                                 [ID]         int Not Null IDENTITY PRIMARY KEY
                                ,[Keyword]    nvarchar(max) Not Null
                                ,[Cnt]        int Not Null Default(0)
                              
                              )
                              /*With ENCRYPTION*/ As 
                              Begin
                                  Declare @Key    nvarchar(max);
                                  Declare @Cnt    int;
                                  Declare @I      int;
                              
                                  Set @I = 0 ;
                                  --Set @Text = QUOTENAME(@Text);
                              
                                  Insert Into @Result
                                      ([Keyword])
                                  Select Trim([value])
                                  From String_Split(@Keywords,N',')
                                  Group By [value]
                                  Order By Len([value]) Desc;
                              
                                  Declare CntKey_Cursor Insensitive Cursor For
                                  Select [Keyword]
                                  From @Result
                                  Order By [ID];
                              
                                  Open CntKey_Cursor;
                                  Fetch Next From CntKey_Cursor Into @Key;
                                  While (@@Fetch_STATUS = 0) Begin
                                      Set @Cnt = 0;
                              
                                      While (PatIndex(N'%'+@Key+'%',@Text) > 0) Begin
                                          Set @Cnt += 1;
                                          Set @I += 1 ;
                                          Set @Text = Stuff(@Text,PatIndex(N'%'+@Key+'%',@Text),len(@Key),N'{'+Convert(nvarchar,@I)+'}');
                                          --Set @Text = Replace(@Text,@Key,N'{'+Convert(nvarchar,@I)+'}');
                                      End--While
                              
                                      Update @Result
                                          Set [Cnt] = @Cnt
                                      Where ([Keyword] = @Key);
                              
                                      Fetch Next From CntKey_Cursor Into @Key;
                                  End--While
                                  Close CntKey_Cursor;
                                  Deallocate CntKey_Cursor;
                                  Return
                               End
                              GO
                              
                              --Test
                              Select *
                              From dbo.fn_getCountKeywords(
                                      N'<U+0001F4E3> MARKET IMPACT Euro area Euro CPIarea annual inflation up to 3.0% MaCPIRKET forex'
                                      ,N'CPI ,core,MaRKET , Euro area'
                                      )       
                              
                              Go
                              

                              【讨论】:

                                【解决方案20】:

                                参考https://docs.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-ver15

                                示例:

                                SELECT  s.*
                                    ,s.[Number1] - (SELECT COUNT(Value)
                                                        FROM string_split(s.[StringColumn],',')
                                                        WHERE RTRIM(VALUE) <> '')
                                FROM TableName AS s
                                

                                适用于:SQL Server 2016 (13.x) 及更高版本

                                【讨论】:

                                  【解决方案21】:

                                  您可以使用以下存储过程来获取值。

                                  IF  EXISTS (SELECT * FROM sys.objects 
                                  WHERE object_id = OBJECT_ID(N'[dbo].[sp_parsedata]') AND type in (N'P', N'PC'))
                                      DROP PROCEDURE [dbo].[sp_parsedata]
                                  GO
                                  create procedure sp_parsedata
                                  (@cid integer,@st varchar(1000))
                                  as
                                    declare @coid integer
                                    declare @c integer
                                    declare @c1 integer
                                    select @c1=len(@st) - len(replace(@st, ',', ''))
                                    set @c=0
                                    delete from table1 where complainid=@cid;
                                    while (@c<=@c1)
                                      begin
                                        if (@c<@c1) 
                                          begin
                                            select @coid=cast(replace(left(@st,CHARINDEX(',',@st,1)),',','') as integer)
                                            select @st=SUBSTRING(@st,CHARINDEX(',',@st,1)+1,LEN(@st))
                                          end
                                        else
                                          begin
                                            select @coid=cast(@st as integer)
                                          end
                                        insert into table1(complainid,courtid) values(@cid,@coid)
                                        set @c=@c+1
                                      end
                                  

                                  【讨论】:

                                  • 此存储过程的第 4 行将 @c1 设置为他需要的答案。考虑到它需要一个名为table1 的预先存在的表才能工作,有一个硬编码的分隔符,并且不能像两个月前接受的答案那样内联使用,剩下的代码有什么用?
                                  【解决方案22】:

                                  Replace/Len 测试很可爱,但可能效率很低(尤其是在内存方面)。 一个带有循环的简单函数就可以完成这项工作。

                                  CREATE FUNCTION [dbo].[fn_Occurences] 
                                  (
                                      @pattern varchar(255),
                                      @expression varchar(max)
                                  )
                                  RETURNS int
                                  AS
                                  BEGIN
                                  
                                      DECLARE @Result int = 0;
                                  
                                      DECLARE @index BigInt = 0
                                      DECLARE @patLen int = len(@pattern)
                                  
                                      SET @index = CHARINDEX(@pattern, @expression, @index)
                                      While @index > 0
                                      BEGIN
                                          SET @Result = @Result + 1;
                                          SET @index = CHARINDEX(@pattern, @expression, @index + @patLen)
                                      END
                                  
                                      RETURN @Result
                                  
                                  END
                                  

                                  【讨论】:

                                  • 在任何大小可观的表格中,使用过程函数效率低下
                                  • 好点。构建的 Len 调用是否比使用定义的函数快得多?
                                  • 在大量记录中,是的。尽管要确定您必须在带有大字符串的大型记录集上进行测试。如果可以避免的话,永远不要在 SQL 中编写任何程序化的东西(即循环)
                                  【解决方案23】:

                                  也许您不应该那样存储数据。在字段中存储逗号分隔列表是一种不好的做法。 IT 的查询效率非常低。这应该是一个相关的表。

                                  【讨论】:

                                  • +1 考虑到这一点。当有人在字段中使用逗号分隔的数据时,我通常会这样开始。
                                  • 这个问题的部分目的是获取现有数据并适当地拆分。
                                  • 我们中的一些人获得了旧数据库,但我们对此无能为力。
                                  • @Mulmoth,当然这是一个答案。你解决的是问题而不是症状。问题出在数据库设计上。
                                  • @HLGEM 这个问题可能指向一个问题,但可以理解得更笼统。对于非常规范化的数据库,这个问题是完全合理的。
                                  猜你喜欢
                                  • 2013-02-24
                                  • 2017-10-09
                                  • 2010-11-12
                                  • 2013-12-25
                                  • 2012-09-02
                                  相关资源
                                  最近更新 更多