Как вы подсчитываете количество вхождений определенной подстроки в SQL varchar?

150

У меня есть столбец, значения которого отформатированы как a, b, c, d. Есть ли способ подсчитать количество запятых в этом значении в T-SQL?

Орион Адриан
источник

Ответы:

245

Первый способ, который приходит на ум, - сделать это косвенно, заменив запятую пустой строкой и сравнив длину

Declare @string varchar(1000)
Set @string = 'a,b,c,d'
select len(@string) - len(replace(@string, ',', ''))
cmsjr
источник
13
Это отвечает на вопрос, как написано в тексте, но не так, как написано в заголовке. Чтобы это работало для более чем одного символа, просто нужно добавить / len (searchterm) вокруг этой вещи. Написал ответ на случай, если он кому-нибудь пригодится.
Эндрю Барретт
Кто-то указал мне, что это не всегда работает, как ожидалось. Рассмотрите следующее: ВЫБЕРИТЕ ЛЕН («a, b, c, d,») - ЛЕН (ЗАМЕНИТЕ («a, b, c, d, ',', ',' ')) По причинам, которые я пока не понимаю , пробел между d и последним столбцом заставляет это возвращать 5 вместо 4. Я опубликую другой ответ, который исправляет это, в случае, если это полезно для кого-либо.
бурлит
5
Возможно, лучше использовать DATALENGTH вместо LEN, потому что LEN возвращает размер обрезанной строки.
Родригок
2
DATALENGTH () / 2 также сложно из-за неочевидных размеров символов. Посмотрите на stackoverflow.com/a/11080074/1094048 для простого и точного способа получить длину строки.
пкудеров
@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
Комментарий Рори полезен. Я обнаружил, что могу просто заменить LEN на DATALENGTH в функции Эндрю и получить желаемый результат. Кажется, что деление на 2 не обязательно с тем, как работает математика.
Гирлянда Папа Римский
@AndrewBarrett: Что добавить, когда несколько строк имеют одинаковую длину?
user2284570
2
DATALENGTH()/2также сложно из-за неочевидных размеров символов. Посмотрите на stackoverflow.com/a/11080074/1094048 для простого и точного способа.
пкудеров
26

Вы можете сравнить длину строки с той, где запятые удалены:

len(value) - len(replace(value,',',''))
Guffa
источник
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 избавляется от последней запятой, но ТАКЖЕ и непосредственно перед ней (не знаю почему). Это приводит к возвращаемому значению 5, когда вы ожидаете 4. Вот другой способ сделать это, который будет работать даже в этом специальном сценарии:

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

Обратите внимание, что вам не нужно использовать звездочки. Подойдет любая двухсимвольная замена. Идея состоит в том, что вы удлиняете строку на один символ для каждого экземпляра счетного символа, а затем вычитаете длину оригинала. Это в основном противоположный метод первоначального ответа, который не сопровождается странным побочным эффектом обрезки.

bubbleking
источник
5
«По какой-то причине, REPLACE избавляется от последней запятой, но ТАКЖЕ места перед ней (не знаю, почему)». REPLACE не избавляется от последней запятой и пробела перед ней, на самом деле это функция LEN, которая игнорирует пробел, возникающий в конце строки из-за этого пробела.
Имранулла Хан
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 меньше фактического количества
Интегратор
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(replace(@string, ' ', '-')) - len(replace(replace(@string, ' ', '-'), ',', ''))
MartinC
источник
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

Даррел Ли, я думаю, имеет довольно хороший ответ. Замените CHARINDEX()на PATINDEX(), и вы также можете выполнить слабый regexпоиск по строке ...

Как, скажем, вы используете это для @pattern:

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

Почему вы хотите сделать что-то сумасшедшее?

Допустим, вы загружаете текстовые строки с разделителями в промежуточную таблицу, где поле, содержащее данные, является чем-то вроде varchar (8000) или nvarchar (max) ...

Иногда проще / быстрее выполнить ELT (Extract-Load-Transform) с данными, а не ETL (Extract-Transform-Load), и один из способов сделать это - загрузить записи с разделителями как есть в промежуточную таблицу, особенно если Вы можете захотеть более простой способ увидеть исключительные записи, чем рассматривать их как часть пакета служб SSIS ... но это священная война для другого потока.

user1390375
источник
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
          );
cmfox1970
источник
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 и суффикс к входу. этот символ оценивается как отличный от любого символа, указанного в параметре поиска, поэтому он не может повлиять на результат.

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

В SQL 2017 или выше вы можете использовать это:

declare @hits int = 0
set @hits = (select value from STRING_SPLIT('F609,4DFA,8499',','));
select count(@hits)
Руди Инохоса
источник
0

этот код T-SQL находит и печатает все вхождения шаблона @p в предложении @s. Вы можете сделать любую обработку предложения позже.

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;
masemanUK2000
источник
-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
Nilesh
источник
строка 4 этой хранимой процедуры устанавливает @c1ответ, который ему требуется. Какая польза от остальной части кода, учитывая, что ему нужна уже существующая таблица, призванная table1работать, имеет жестко закодированный разделитель и ее нельзя использовать в строке, как принятый ответ за два месяца до этого?
Nick.McDermaid
-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
Хорошая точка зрения. Является ли встроенный вызов Len намного быстрее, чем использование определенной функции?
Даррел Ли
При большом количестве записей, да. Хотя, чтобы быть уверенным, вам придется тестировать большой набор записей с большими строками. Никогда не пишите ничего процедурного в SQL, если вы можете избежать этого (например, циклы)
Nick.McDermaid
-3

Возможно, вам не следует хранить данные таким образом. Не рекомендуется хранить список с разделителями-запятыми в поле. Это очень неэффективно для запросов. Это должна быть связанная таблица.

HLGEM
источник
+1 за размышления об этом. Это то, с чего я обычно начинаю, когда кто-то использует данные через запятую в поле.
Гуффа
6
Часть цели этого вопроса состояла в том, чтобы взять существующие данные и разделить их соответствующим образом.
Орион Адриан
7
Некоторым из нас предоставляются устаревшие базы данных, где это было сделано, и мы ничего не можем с этим поделать.
eddieroger
@ Mulmoth, конечно, это ответ. Вы решаете проблему, а не симптом. Проблема с дизайном базы данных.
HLGEM
1
@HLGEM Вопрос может указывать на проблему, но ее можно понять более широко . Вопрос вполне законен для очень хорошо нормализованных баз данных.
Зими