Jak policzyć liczbę wystąpień określonego podciągu w zmiennej SQL varchar?

150

Mam kolumnę z wartościami w formacie a, b, c, d. Czy istnieje sposób, aby policzyć liczbę przecinków w tej wartości w T-SQL?

Orion Adrian
źródło

Odpowiedzi:

245

Pierwszym sposobem, który przychodzi na myśl, jest zrobienie tego pośrednio, zastępując przecinek pustym ciągiem i porównując długości

Declare @string varchar(1000)
Set @string = 'a,b,c,d'
select len(@string) - len(replace(@string, ',', ''))
cmsjr
źródło
13
To odpowiada na pytanie tak, jak napisano w tekście, ale nie tak, jak napisano w tytule. Aby to działało dla więcej niż jednego znaku, wystarczy dodać / len (termin wyszukiwania) wokół rzeczy. Wysłałem odpowiedź na wypadek, gdyby była przydatna dla kogoś.
Andrew Barrett
Ktoś zwrócił mi uwagę, że to nie zawsze działa zgodnie z oczekiwaniami. Rozważ następujące kwestie: SELECT LEN ('a, b, c, d,') - LEN (REPLACE ('a, b, c, d,', ',', '')) Z powodów, których jeszcze nie rozumiem , odstęp między d a ostatnią kolumną powoduje, że zwraca 5 zamiast 4. Opublikuję inną odpowiedź, która rozwiązuje ten problem, na wypadek, gdyby była przydatna komukolwiek.
Bubbleking
5
Może użycie DATALENGTH zamiast LEN byłoby lepsze, ponieważ LEN zwraca rozmiar przyciętego łańcucha.
rodrigocl
2
DATALENGTH () / 2 jest również trudne z powodu nieoczywistych rozmiarów znaków. Zajrzyj na stackoverflow.com/a/11080074/1094048, aby uzyskać prosty i dokładny sposób na określenie długości łańcucha.
pkuderov
@rodrigocl Dlaczego nie otoczyć LTRIMwokół napisu w następujący sposób: SELECT LEN(RTRIM(@string)) - LEN(REPLACE(RTRIM(@string), ',', ''))?
Alex Bello,
67

Szybkie rozszerzenie odpowiedzi cmsjr, które działa w przypadku łańcuchów zawierających więcej niż więcej znaków.

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

Stosowanie:

SELECT * FROM MyTable
where dbo.CountOccurrencesOfString(MyColumn, 'MyString') = 1
Andrew Barrett
źródło
16
Nieznacznym ulepszeniem byłoby użycie DATALENGTH () / 2 zamiast LEN (). LEN zignoruje wszelkie końcowe spacje, więc dbo.CountOccurancesOfString( 'blah ,', ',')zwróci 2 zamiast 1 i dbo.CountOccurancesOfString( 'hello world', ' ')zakończy się niepowodzeniem z dzieleniem przez zero.
Rory
5
Komentarz Rory jest pomocny. Odkryłem, że mogę po prostu zastąpić LEN DATALENGTH w funkcji Andrew i uzyskać pożądany wynik. Wydaje się, że dzielenie przez 2 nie jest konieczne ze względu na sposób działania matematyki.
Garland Pope
@AndrewBarrett: Co dodać, gdy kilka ciągów ma taką samą długość?
user2284570
2
DATALENGTH()/2jest również trudna z powodu nieoczywistych rozmiarów znaków. Zajrzyj na stackoverflow.com/a/11080074/1094048, aby uzyskać prosty i dokładny sposób.
pkuderov
26

Możesz porównać długość łańcucha z takim, w którym zostały usunięte przecinki:

len(value) - len(replace(value,',',''))
Guffa
źródło
8

Opierając się na rozwiązaniu @ Andrew, uzyskasz znacznie lepszą wydajność przy użyciu nieproceduralnej funkcji wartościującej tabelę i 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
Russell Fox
źródło
Używam tej samej funkcji w wielu moich starszych bazach danych, bardzo pomaga w wielu starych i nieprawidłowo zaprojektowanych bazach danych. Oszczędza dużo czasu i jest bardzo szybki nawet w przypadku dużych zestawów danych.
Caimen
6

Odpowiedź udzielona przez @csmjr zawiera w niektórych przypadkach problem.

Jego odpowiedź brzmiała:

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

Działa to w większości scenariuszy, jednak spróbuj uruchomić to:

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

Z jakiegoś powodu polecenie REPLACE usuwa ostatni przecinek, ale RÓWNIEŻ spację tuż przed nim (nie wiem dlaczego). Powoduje to zwrócenie wartości 5, gdy można się spodziewać 4. Oto inny sposób na zrobienie tego, który zadziała nawet w tym specjalnym scenariuszu:

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

Pamiętaj, że nie musisz używać gwiazdek. Każde zastąpienie dwóch znaków wystarczy. Chodzi o to, że wydłużasz ciąg o jeden znak dla każdego wystąpienia liczonego znaku, a następnie odejmujesz długość oryginału. Jest to w zasadzie metoda odwrotna do oryginalnej odpowiedzi, która nie wiąże się z dziwnym efektem ubocznym przycinania.

bulgotanie
źródło
5
„Z jakiegoś powodu REPLACE usuwa ostatni przecinek, ale RÓWNIEŻ spację tuż przed nim (nie wiem dlaczego)”. REPLACE nie usuwa ostatniego przecinka i spacji przed nim, w rzeczywistości jest to funkcja LEN, która ignoruje odstępy wynikające z końca ciągu z powodu tej spacji.
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)
NIKHIL THAKUR
źródło
to faktycznie zwraca 1 mniej niż faktyczna liczba
Integrator
1

Zaakceptowana odpowiedź jest poprawna, rozszerzając ją tak, aby używała 2 lub więcej znaków w podłańcuchu:

Declare @string varchar(1000)
Set @string = 'aa,bb,cc,dd'
Set @substring = 'aa'
select (len(@string) - len(replace(@string, @substring, '')))/len(@substring)
Imran Rizvi
źródło
1

Jeśli wiemy, że istnieje ograniczenie dotyczące LEN i przestrzeni, dlaczego nie możemy najpierw zastąpić spacji? Wtedy wiemy, że nie ma miejsca na pomylenie LEN.

len(replace(@string, ' ', '-')) - len(replace(replace(@string, ' ', '-'), ',', ''))
MartinC
źródło
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'
siedmiodniowa żałoba
źródło
0

Myślę, że Darrel Lee ma całkiem dobrą odpowiedź. Wymień CHARINDEX()się PATINDEX(), i można to zrobić trochę słaby regexposzukiwania wzdłuż łańcucha, zbyt ...

Powiedz, że używasz tego do @pattern:

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

Dlaczego miałbyś chcieć zrobić coś tak szalonego?

Powiedzmy, że ładujesz rozdzielone ciągi tekstowe do tabeli pomostowej, w której pole przechowujące dane jest czymś w rodzaju varchar (8000) lub nvarchar (max) ...

Czasami łatwiej / szybciej jest wykonać ELT (wyodrębnij-załaduj-transformację) z danymi zamiast ETL (wyodrębnij-transformuj-załaduj), a jednym ze sposobów na to jest załadowanie rozdzielonych rekordów w postaci, w jakiej są, do tabeli pomostowej, zwłaszcza jeśli możesz chcieć prostszego sposobu, aby zobaczyć wyjątkowe zapisy, zamiast traktować je jako część pakietu SSIS ... ale to święta wojna na inny wątek.

user1390375
źródło
0

Poniższe działania powinny załatwić sprawę zarówno w przypadku wyszukiwania pojedynczego znaku, jak i wyszukiwania wielu znaków:

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;

Funkcję można nieco uprościć za pomocą tabeli liczb (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
źródło
0

Użyj tego kodu, działa idealnie. Stworzyłem funkcję sql akceptującą dwa parametry, pierwszym parametrem jest długi ciąg, który chcemy w nim przeszukać i może on przyjmować długość ciągu do 1500 znaków (oczywiście możesz go przedłużyć lub nawet zmienić na typ danych tekstowych ). A drugim parametrem jest podłańcuch, który chcemy obliczyć liczbę jego wystąpień (jego długość to maksymalnie 200 znaków, oczywiście możesz go zmienić na taką, jakiej potrzebujesz). a wyjście jest liczbą całkowitą, reprezentuje liczbę częstotliwości… ciesz się tym.


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
Pewnego dnia
źródło
0

W końcu piszę tę funkcję, która powinna obejmować wszystkie możliwe sytuacje, dodając prefiks i sufiks znaku do wejścia. ten znak jest oceniany jako inny niż którykolwiek ze znaków kontekstowych w parametrze wyszukiwania, więc nie może wpływać na wynik.

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 

stosowanie

select dbo.CountOccurrency('a,b,c,d ,', ',')
Arden Inside
źródło
0
Declare @MainStr nvarchar(200)
Declare @SubStr nvarchar(10)
Set @MainStr = 'nikhildfdfdfuzxsznikhilweszxnikhil'
Set @SubStr = 'nikhil'
Select (Len(@MainStr) - Len(REPLACE(@MainStr,@SubStr,'')))/Len(@SubStr)
NIKHIL THAKUR
źródło
0

W programie SQL 2017 lub nowszym możesz użyć tego:

declare @hits int = 0
set @hits = (select value from STRING_SPLIT('F609,4DFA,8499',','));
select count(@hits)
Rudy Hinojosa
źródło
0

ten kod T-SQL wyszukuje i wypisuje wszystkie wystąpienia wzorca @p w zdaniu @s. możesz później przetworzyć wyrok.

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

wynik to: 1 6 13 20

Hasan Zafari
źródło
0

dla programu SQL Server 2017

declare @hits int = 0;
set @hits = (select count(*) from (select value from STRING_SPLIT('F609,4DFA,8499',',')) a);
select @hits;
masemanUK2000
źródło
-1

Aby pobrać wartości, można użyć następującej procedury składowanej.

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
źródło
wiersz 4 tej procedury składowanej ustawia @c1odpowiedź, której on potrzebuje. Jaki pożytek jest z pozostałej części kodu, biorąc pod uwagę, że wymaga on wcześniej istniejącej tabeli wywoływanej table1do pracy, ma zakodowany na stałe ogranicznik i nie może być używany w tekście, jak zaakceptowana odpowiedź sprzed dwóch miesięcy?
Nick.McDermaid
-1

Test Replace / Len jest uroczy, ale prawdopodobnie bardzo nieefektywny (szczególnie pod względem pamięci). Wystarczy prosta funkcja z pętlą.

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
Darrel Lee
źródło
W przypadku każdej znaczącej tabeli użycie funkcji proceduralnej jest znacznie mniej wydajne
Nick.McDermaid
Słuszna uwaga. Czy zbudowane wywołanie Len jest znacznie szybsze niż funkcja zdefiniowana przez użycie?
Darrel Lee
Na dużą skalę, tak. Chociaż, aby mieć pewność, musiałbyś przeprowadzić testy na dużym zestawie rekordów z dużymi ciągami. Nigdy nie pisz niczego proceduralnego w SQL, jeśli możesz tego uniknąć (np.
Pętli
-3

Być może nie powinieneś przechowywać danych w ten sposób. Przechowywanie listy rozdzielanej przecinkami w polu jest złą praktyką. IT jest bardzo nieefektywne do wykonywania zapytań. To powinna być powiązana tabela.

HLGEM
źródło
+1 za myślenie o tym. Od tego zwykle zaczynam, gdy ktoś używa danych oddzielonych przecinkami w polu.
Guffa,
6
Częściowo celem tego pytania było zebranie takich istniejących danych i odpowiednie rozdzielenie ich na części.
Orion Adrian
7
Niektórzy z nas otrzymują starsze bazy danych, w których zostało to zrobione i nie możemy nic z tym zrobić.
eddieroger
@Mulmoth, oczywiście, że to odpowiedź. naprawiasz problem, a nie objaw. Problem dotyczy projektu bazy danych.
HLGEM
1
@HLGEM Pytanie może wskazywać na problem, ale można je rozumieć bardziej ogólnie. Pytanie jest całkowicie uzasadnione w przypadku bardzo dobrze znormalizowanych baz danych.
Zeemee