Wie zählt man die Anzahl der Vorkommen eines bestimmten Teilstrings in einem SQL-Varchar?

150

Ich habe eine Spalte mit Werten, die wie a, b, c, d formatiert sind. Gibt es eine Möglichkeit, die Anzahl der Kommas in diesem Wert in T-SQL zu zählen?

Orion Adrian
quelle

Antworten:

244

Der erste Weg, der mir in den Sinn kommt, besteht darin, dies indirekt zu tun, indem das Komma durch eine leere Zeichenfolge ersetzt und die Längen verglichen werden

Declare @string varchar(1000)
Set @string = 'a,b,c,d'
select len(@string) - len(replace(@string, ',', ''))
cmsjr
quelle
13
Das beantwortet die Frage wie im Text geschrieben, aber nicht wie im Titel geschrieben. Damit es für mehr als ein Zeichen funktioniert, müssen Sie nur ein / len (Sucher) um das Ding hinzufügen. Hat eine Antwort gepostet, falls es für jemanden nützlich ist.
Andrew Barrett
Jemand hat mich darauf hingewiesen, dass dies nicht immer wie erwartet funktioniert. Beachten Sie Folgendes: SELECT LEN ('a, b, c, d,') - LEN (REPLACE ('a, b, c, d,', ',', '')) Aus Gründen, die ich noch nicht verstehe Der Abstand zwischen dem d und der letzten Spalte bewirkt, dass 5 statt 4 zurückgegeben wird. Ich werde eine weitere Antwort veröffentlichen, die dies behebt, falls es für jemanden nützlich ist.
Bubbleking
5
Vielleicht wäre es besser, DATALENGTH anstelle von LEN zu verwenden, da LEN die Größe der zugeschnittenen Zeichenfolge zurückgibt.
Rodrigocl
2
DATALENGTH () / 2 ist auch wegen nicht offensichtlicher Zeichengrößen schwierig. Unter stackoverflow.com/a/11080074/1094048 finden Sie eine einfache und genaue Möglichkeit, die Stringlänge zu ermitteln.
pkuderov
@rodrigocl Warum nicht LTRIMwie folgt um die Zeichenfolge wickeln : SELECT LEN(RTRIM(@string)) - LEN(REPLACE(RTRIM(@string), ',', ''))?
Alex Bello
67

Schnelle Erweiterung der Antwort von cmsjr, die für Zeichenfolgen mit mehr als mehr Zeichen funktioniert.

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

Verwendung:

SELECT * FROM MyTable
where dbo.CountOccurrencesOfString(MyColumn, 'MyString') = 1
Andrew Barrett
quelle
16
Eine leichte Verbesserung wäre die Verwendung von DATALENGTH () / 2 anstelle von LEN (). LEN ignoriert alle nachgestellten Leerzeichen, gibt also dbo.CountOccurancesOfString( 'blah ,', ',')2 anstelle von 1 zurück und dbo.CountOccurancesOfString( 'hello world', ' ')schlägt mit der Division durch Null fehl.
Rory
5
Rorys Kommentar ist hilfreich. Ich fand heraus, dass ich LEN in Andrews Funktion einfach durch DATALENGTH ersetzen und das gewünschte Ergebnis erzielen konnte. Es scheint, dass das Teilen durch 2 bei der Art und Weise, wie die Mathematik funktioniert, nicht notwendig ist.
Garland Pope
@ AndrewBarrett: Was wird angehängt, wenn mehrere Zeichenfolgen dieselbe Länge haben?
user2284570
2
DATALENGTH()/2ist auch wegen nicht offensichtlicher Zeichengrößen schwierig. Unter stackoverflow.com/a/11080074/1094048 finden Sie eine einfache und genaue Möglichkeit.
pkuderov
26

Sie können die Länge der Zeichenfolge mit einer Länge vergleichen, bei der die Kommas entfernt werden:

len(value) - len(replace(value,',',''))
Guffa
quelle
8

Aufbauend auf der Lösung von @ Andrew erhalten Sie eine viel bessere Leistung, wenn Sie eine nicht prozedurale Tabellenwertfunktion und CROSS APPLY verwenden:

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
quelle
Ich verwende dieselbe Funktion in vielen meiner alten Datenbanken. Sie hilft sehr bei vielen alten und falsch gestalteten Datenbanken. Spart viel Zeit und ist auch bei großen Datenmengen sehr schnell.
Caimen
6

Die Antwort von @csmjr hat in einigen Fällen ein Problem.

Seine Antwort war:

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

Dies funktioniert in den meisten Szenarien. Versuchen Sie jedoch Folgendes:

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

Aus irgendeinem Grund entfernt REPLACE das letzte Komma, aber AUCH das Leerzeichen davor (nicht sicher warum). Dies führt zu einem zurückgegebenen Wert von 5, wenn Sie 4 erwarten würden. Hier ist eine andere Möglichkeit, die auch in diesem speziellen Szenario funktioniert:

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

Beachten Sie, dass Sie keine Sternchen verwenden müssen. Jeder Ersatz durch zwei Zeichen reicht aus. Die Idee ist, dass Sie die Zeichenfolge für jede Instanz des Zeichens, das Sie zählen, um ein Zeichen verlängern und dann die Länge des Originals subtrahieren. Es ist im Grunde die entgegengesetzte Methode der ursprünglichen Antwort, die nicht mit dem seltsamen Nebeneffekt des Trimmens einhergeht.

Bubbleking
quelle
5
"Aus irgendeinem Grund entfernt REPLACE das letzte Komma, aber AUCH das Leerzeichen davor (nicht sicher warum)." REPLACE entfernt nicht das letzte Komma und das Leerzeichen davor, sondern die LEN-Funktion ignoriert den Leerraum am Ende der Zeichenfolge aufgrund dieses Leerzeichens.
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
quelle
Dies gibt tatsächlich 1 weniger als die tatsächliche Anzahl zurück
The Integrator
1

Die akzeptierte Antwort ist korrekt und erweitert sie auf die Verwendung von 2 oder mehr Zeichen in der Teilzeichenfolge:

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

Wenn wir wissen, dass LEN und Speicherplatz begrenzt sind, warum können wir den Speicherplatz nicht zuerst ersetzen? Dann wissen wir, dass es keinen Raum gibt, um LEN zu verwirren.

len(replace(@string, ' ', '-')) - len(replace(replace(@string, ' ', '-'), ',', ''))
MartinC
quelle
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'
Shiva
quelle
0

Darrel Lee hat meiner Meinung nach eine ziemlich gute Antwort. Ersetzen Sie CHARINDEX()durch PATINDEX(), und Sie können auch regexentlang einer Zeichenfolge schwach suchen ...

Angenommen, Sie verwenden dies für @pattern:

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

Warum willst du vielleicht so etwas Verrücktes machen?

Angenommen, Sie laden begrenzte Textzeichenfolgen in eine Staging-Tabelle, in der das Feld, in dem sich die Daten befinden, so etwas wie ein Varchar (8000) oder ein Nvarchar (max) ist.

Manchmal ist es einfacher / schneller, ELT (Extract-Load-Transform) mit Daten anstelle von ETL (Extract-Transform-Load) durchzuführen, und eine Möglichkeit, dies zu tun, besteht darin, die begrenzten Datensätze unverändert in eine Staging-Tabelle zu laden, insbesondere wenn Vielleicht möchten Sie die außergewöhnlichen Datensätze einfacher anzeigen, als sie als Teil eines SSIS-Pakets zu behandeln ... aber das ist ein heiliger Krieg für einen anderen Thread.

user1390375
quelle
0

Folgendes sollte den Trick sowohl für die Suche nach einzelnen als auch nach mehreren Zeichen ausführen:

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;

Die Funktion kann mithilfe einer Zahlentabelle (dbo.Nums) etwas vereinfacht werden:

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

Verwenden Sie diesen Code, es funktioniert einwandfrei. Ich habe eine SQL-Funktion erstellt, die zwei Parameter akzeptiert. Der erste Parameter ist die lange Zeichenfolge, nach der gesucht werden soll, und sie kann eine Zeichenfolgenlänge von bis zu 1500 Zeichen akzeptieren (natürlich können Sie sie erweitern oder sogar in einen Textdatentyp ändern ). Und der zweite Parameter ist der Teilstring, mit dem wir die Anzahl seiner Vorkommen berechnen möchten (seine Länge beträgt bis zu 200 Zeichen, natürlich können Sie ihn nach Ihren Wünschen ändern). und die Ausgabe ist eine ganze Zahl, repräsentieren die Anzahl der Frequenzen ..... genießen Sie es.


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
Eines Tages
quelle
0

Ich schreibe schließlich diese Funktion, die alle möglichen Situationen abdecken soll, indem ich der Eingabe ein Zeichenpräfix und ein Suffix hinzufüge. Dieses Zeichen unterscheidet sich von dem im Suchparameter enthaltenen Zeichen und kann daher das Ergebnis nicht beeinflussen.

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 

Verwendung

select dbo.CountOccurrency('a,b,c,d ,', ',')
Arden drinnen
quelle
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
quelle
0

In SQL 2017 oder höher können Sie Folgendes verwenden:

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

Dieser T-SQL-Code findet und druckt alle Vorkommen von Muster @p in Satz @s. Sie können den Satz anschließend beliebig bearbeiten.

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

das Ergebnis ist: 1 6 13 20

Hasan Zafari
quelle
0

für SQL Server 2017

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

Sie können die folgende gespeicherte Prozedur verwenden, um Werte abzurufen.

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
quelle
Zeile 4 dieser gespeicherten Prozedur setzt @c1auf die Antwort, die er benötigt. Was nützt der Rest des Codes, wenn man bedenkt, dass er eine bereits vorhandene Tabelle benötigt, um table1zu funktionieren, einen fest codierten Begrenzer hat und nicht wie die akzeptierte Antwort von zwei Monaten zuvor inline verwendet werden kann?
Nick.McDermaid
-1

Der Replace / Len-Test ist niedlich, aber wahrscheinlich sehr ineffizient (insbesondere in Bezug auf den Speicher). Eine einfache Funktion mit einer Schleife erledigt den Job.

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
quelle
In jeder Tabelle von nennenswerter Größe ist die Verwendung einer prozeduralen Funktion weitaus ineffizienter
Nick.McDermaid
Guter Punkt. Sind die erstellten Len-Aufrufe viel schneller als eine verwendungsdefinierte Funktion?
Darrel Lee
Bei einer großen Anzahl von Aufzeichnungen ja. Um sicher zu sein, müssten Sie auf einem großen Recordset mit großen Strings testen. Schreiben Sie niemals etwas Prozedurales in SQL, wenn Sie es vermeiden können (dh Schleifen)
Nick.McDermaid
-3

Vielleicht sollten Sie Daten nicht so speichern. Es ist eine schlechte Praxis, jemals eine durch Kommas getrennte Liste in einem Feld zu speichern. IT ist für die Abfrage sehr ineffizient. Dies sollte eine verwandte Tabelle sein.

HLGEM
quelle
+1 dafür. Es ist das, womit ich normalerweise beginne, wenn jemand durch Kommas getrennte Daten in einem Feld verwendet.
Guffa
6
Ein Teil des Zwecks dieser Frage bestand darin, vorhandene Daten auf diese Weise zu erfassen und angemessen aufzuteilen.
Orion Adrian
7
Einige von uns erhalten Legacy-Datenbanken, in denen dies geschehen ist, und wir können nichts dagegen tun.
Eddieroger
@ Mulmoth, natürlich ist es eine Antwort. Sie beheben das Problem, nicht das Symptom. Das Problem liegt im Datenbankdesign.
HLGEM
1
@HLGEM Die Frage kann auf ein Problem hinweisen, kann aber allgemeiner verstanden werden. Die Frage ist für sehr gut normalisierte Datenbanken völlig legitim.
Zeemee