如何计算SQL varchar中某个子字符串出现的次数?


150

我有一列,其值的格式像a,b,c,d。有没有一种方法可以在T-SQL中计算该值中的逗号数量?

Answers:


244

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

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

13
回答的问题与文本中所写的一样,但与标题中所写的不一样。要使它适用于多个字符,只需在事物周围添加一个/ len(searchterm)。贴出答案,以防有人使用。
安德鲁·巴雷特

有人向我指出,这并不总是按预期进行。请考虑以下内容:SELECT LEN('a,b,c,d,')-LEN(REPLACE('a,b,c,d,',',',)))由于我尚不了解的原因,则d和最后一列之间的空格将使其返回5而不是4。我将发布另一个解决此问题的答案,以防它对任何人有用。
起泡

5
也许使用DATALENGTH代替LEN会更好,因为LEN返回修剪后的字符串的大小。
rodrigocl

2
由于字符大小不明显,DATALENGTH()/ 2也很棘手。请查看stackoverflow.com/a/11080074/1094048,以获取获取字符串长度的简单而准确的方法。
pkuderov '16

@rodrigocl为什么不LTRIM按如下所示在字符串周围包裹SELECT LEN(RTRIM(@string)) - LEN(REPLACE(RTRIM(@string), ',', ''))
亚历克斯贝洛

67

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

16
稍有改进就是使用DATALENGTH()/ 2而不是LEN()。LEN将忽略任何结尾的空格,因此 dbo.CountOccurancesOfString( 'blah ,', ',')将返回2而不是1,并且dbo.CountOccurancesOfString( 'hello world', ' ')除以零将失败。
罗里

5
罗里的评论很有帮助。我发现我可以用Andrew函数中的DATALENGTH替换LEN并获得所需的结果。看来数学运算的方式不必除以2。
加兰·教皇

@AndrewBarrett:当几个字符串长度相同时,追加什么内容?
user2284570 2014年

2
DATALENGTH()/2由于char大小不明显,因此也很棘手。请访问stackoverflow.com/a/11080074/1094048,以获取简单准确的方法。
pkuderov

26

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

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

8

在@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

@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)

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


5
“由于某种原因,REPLACE删除了最后一个逗号,但也删除了它前面的空格(不确定原因)。” REPLACE并没有消除最后一个逗号和它前面的空格,实际上是LEN函数由于该空格而忽略了字符串结尾处的空格。
Imranullah Khan

2
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
The Integrator

1

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

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

1

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

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

0
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'

0

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

例如,说您将此用于@pattern

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

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

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

有时,对数据进行ELT(提取-加载-转换)比ETL(提取-转换-加载)更容易/更快,并且这样做的一种方法是将定界记录按原样加载到登台表中,特别是在您可能想要一种更简单的方式来查看异常记录,而不是将它们作为SSIS程序包的一部分进行处理……但这对另一个线程来说是一场圣战。


0

对于单字符搜索和多字符搜索,以下方法都可以解决问题:

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
          );

0

使用此代码,它运行良好。我创建了一个接受两个参数的sql函数,第一个参数是我们要搜索的长字符串,它可以接受最多1500个字符的字符串长度(当然,您可以扩展它甚至将其更改为text数据类型)。第二个参数是我们要计算其出现次数的子字符串(其长度最多为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

0

最后,我写了一个应涵盖所有可能情况的函数,在输入中添加了char前缀和后缀。此char被评估为与search参数中包含的任何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 ,', ',')

0
Declare @MainStr nvarchar(200)
Declare @SubStr nvarchar(10)
Set @MainStr = 'nikhildfdfdfuzxsznikhilweszxnikhil'
Set @SubStr = 'nikhil'
Select (Len(@MainStr) - Len(REPLACE(@MainStr,@SubStr,'')))/Len(@SubStr)


0

该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


0

对于SQL Server 2017

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

-1

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

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才能工作,具有硬编码的分隔符并且不能像两个月前接受的答案那样被内联使用,其余的代码有什么用?
Nick.McDermaid 2014年

-1

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

在任何大小可观的表格中,使用程序功能的效率都低得多
Nick.McDermaid 2014年

好点子。内置的Len调用是否比使用定义的函数快得多?
Darrel Lee

是的,在大量记录中。尽管可以肯定,您必须在带有大字符串的大记录集上进行测试。如果可以避免(例如循环),请不要在SQL中编写任何过程
Nick.McDermaid 2014年

-3

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


考虑到+1。这是我通常在某人在字段中使用逗号分隔的数据时开始的内容。
Guffa

6
这个问题的部分目的是采用类似的现有数据,并将其适当地分开。
Orion Adrian

7
我们当中有些人被提供了遗留数据库,而遗留数据库是在此完成的,而我们对此无能为力。
eddieroger 2014年

@Mulmoth当然是一个答案。您解决问题而不是症状。问题在于数据库设计。
HLGEM

1
@HLGEM该问题可能指向一个问题,但可以更一般地理解。对于非常规范化的数据库,这个问题是完全合法的。
Zeemee 2014年
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.