Как вы подсчитываете количество вхождений определенной подстроки в 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, ',', ''))

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

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)

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


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

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 ... но это священная война для другого потока.


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

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;

-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

-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

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


+1 за размышления об этом. Это то, с чего я обычно начинаю, когда кто-то использует данные через запятую в поле.
— Гуффа

6
Часть цели этого вопроса состояла в том, чтобы взять существующие данные и разделить их соответствующим образом.
— Орион Адриан

7
Некоторым из нас предоставляются устаревшие базы данных, где это было сделано, и мы ничего не можем с этим поделать.
— eddieroger

@ Mulmoth, конечно, это ответ. Вы решаете проблему, а не симптом. Проблема с дизайном базы данных.
— HLGEM

1
@HLGEM Вопрос может указывать на проблему, но ее можно понять более широко . Вопрос вполне законен для очень хорошо нормализованных баз данных.
— Зими
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.