Comment comptez-vous le nombre d'occurrences d'une certaine sous-chaîne dans un varchar SQL?


150

J'ai une colonne qui a des valeurs formatées comme a, b, c, d. Existe-t-il un moyen de compter le nombre de virgules dans cette valeur dans T-SQL?

Réponses:


245

La première façon qui me vient à l'esprit est de le faire indirectement en remplaçant la virgule par une chaîne vide et en comparant les longueurs

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

13
Cela répond à la question telle qu'elle est écrite dans le texte, mais pas telle qu'elle est écrite dans le titre. Pour que cela fonctionne pour plus d'un caractère, il suffit d'ajouter un / len (searchterm) autour de l'objet. Publié une réponse au cas où cela serait utile pour quelqu'un.
Andrew Barrett

Quelqu'un m'a fait remarquer que cela ne fonctionnait pas toujours comme prévu. Considérez ce qui suit: SELECT LEN ('a, b, c, d,') - LEN (REPLACE ('a, b, c, d,', ',', '')) Pour des raisons que je ne comprends pas encore , l'espace entre le d et la dernière colonne fait que cela renvoie 5 au lieu de 4. Je publierai une autre réponse qui corrige cela, au cas où cela serait utile à quiconque.
bubbleking

5
Il serait peut-être préférable d'utiliser DATALENGTH à la place de LEN, car LEN renvoie la taille de la chaîne coupée.
rodrigocl

2
DATALENGTH () / 2 est également délicat à cause des tailles de caractères non évidentes. Consultez stackoverflow.com/a/11080074/1094048 pour un moyen simple et précis d'obtenir la longueur de la chaîne.
pkuderov

@rodrigocl Pourquoi ne pas envelopper un LTRIMautour de la chaîne comme suit: SELECT LEN(RTRIM(@string)) - LEN(REPLACE(RTRIM(@string), ',', ''))?
Alex Bello

67

Extension rapide de la réponse de cmsjr qui fonctionne pour les chaînes de plus que plus de caractères.

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

Usage:

SELECT * FROM MyTable
where dbo.CountOccurrencesOfString(MyColumn, 'MyString') = 1

16
Une légère amélioration serait d'utiliser DATALENGTH () / 2 au lieu de LEN (). LEN ignorera tout espace blanc de fin et dbo.CountOccurancesOfString( 'blah ,', ',')retournera donc 2 au lieu de 1 et dbo.CountOccurancesOfString( 'hello world', ' ')échouera avec une division par zéro.
Rory

5
Le commentaire de Rory est utile. J'ai trouvé que je pouvais simplement remplacer LEN par DATALENGTH dans la fonction d'Andrew et obtenir le résultat souhaité. Il semble que la division par 2 ne soit pas nécessaire avec la façon dont le calcul fonctionne.
Garland Pope

@AndrewBarrett: Qu'est-ce qui s'ajoute quand plusieurs chaînes ont la même longueur?
user2284570

2
DATALENGTH()/2est également délicat à cause des tailles de caractères non évidentes. Regardez stackoverflow.com/a/11080074/1094048 pour une manière simple et précise.
pkuderov

26

Vous pouvez comparer la longueur de la chaîne avec celle où les virgules sont supprimées:

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

8

En vous basant sur la solution de @ Andrew, vous obtiendrez de bien meilleures performances en utilisant une fonction table non procédurale et 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

J'utilise cette même fonction dans plusieurs de mes anciennes bases de données, cela aide beaucoup avec beaucoup de bases de données anciennes et mal conçues. Économise beaucoup de temps et est très rapide même sur de grands ensembles de données.
Caimen

6

La réponse de @csmjr pose un problème dans certains cas.

Sa réponse a été de faire ceci:

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

Cela fonctionne dans la plupart des scénarios, cependant, essayez d'exécuter ceci:

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

Pour une raison quelconque, REPLACE supprime la virgule finale mais AUSSI l'espace juste avant (je ne sais pas pourquoi). Cela se traduit par une valeur renvoyée de 5 lorsque vous vous attendez à 4. Voici une autre façon de procéder qui fonctionnera même dans ce scénario spécial:

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

Notez que vous n'avez pas besoin d'utiliser d'astérisques. Tout remplacement de deux caractères fera l'affaire. L'idée est d'allonger la chaîne d'un caractère pour chaque instance du caractère que vous comptez, puis de soustraire la longueur de l'original. C'est fondamentalement la méthode opposée de la réponse originale qui ne vient pas avec l'étrange effet secondaire de coupe.


5
"Pour une raison quelconque, REPLACE supprime la virgule finale mais AUSSI l'espace juste avant (je ne sais pas pourquoi)." REPLACE ne supprime pas la dernière virgule et l'espace avant, c'est en fait la fonction LEN qui ignore l'espace blanc résultant à la fin de la chaîne à cause de cet espace.
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)

cela renvoie en fait 1 moins le nombre réel
The Integrator

1

La réponse acceptée est correcte, l'étendant pour utiliser 2 caractères ou plus dans la sous-chaîne:

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

1

Si nous savons qu'il existe une limitation de la LEN et de l'espace, pourquoi ne pouvons-nous pas remplacer l'espace en premier? Ensuite, nous savons qu'il n'y a pas de place pour confondre 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

Je pense que Darrel Lee a une assez bonne réponse. Remplacez CHARINDEX()par PATINDEX(), et vous pouvez également effectuer une regexrecherche faible le long d'une chaîne ...

Comme, disons que vous utilisez ceci pour @pattern:

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

Pourquoi voudriez-vous peut-être faire quelque chose de fou comme ça?

Supposons que vous chargiez des chaînes de texte délimitées dans une table intermédiaire, où le champ contenant les données ressemble à un varchar (8000) ou un nvarchar (max) ...

Parfois, il est plus facile / plus rapide de faire ELT (Extract-Load-Transform) avec des données plutôt que ETL (Extract-Transform-Load), et une façon de le faire est de charger les enregistrements délimités tels quels dans une table intermédiaire, en particulier si vous voudrez peut-être un moyen plus simple de voir les enregistrements exceptionnels plutôt que de les traiter dans le cadre d'un package SSIS ... mais c'est une guerre sainte pour un thread différent.


0

Ce qui suit devrait faire l'affaire pour les recherches à un seul caractère et à plusieurs caractères:

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;

La fonction peut être un peu simplifiée à l'aide d'une table de nombres (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

Utilisez ce code, il fonctionne parfaitement. J'ai créé une fonction sql qui accepte deux paramètres, le premier paramètre est la longue chaîne que nous voulons rechercher, et elle peut accepter une longueur de chaîne allant jusqu'à 1500 caractères (bien sûr, vous pouvez l'étendre ou même la changer en type de données texte ). Et le deuxième paramètre est la sous-chaîne dont nous voulons calculer le numéro de son occurrence (sa longueur peut aller jusqu'à 200 caractères, bien sûr, vous pouvez la modifier selon vos besoins). et la sortie est un entier, représente le nombre de fréquence ..... profitez-en.


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

J'écris enfin cette fonction qui devrait couvrir toutes les situations possibles, en ajoutant un préfixe char et un suffixe à l'entrée. ce caractère est évalué comme étant différent de tout caractère contenu dans le paramètre de recherche, il ne peut donc pas affecter le résultat.

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 

usage

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

Dans SQL 2017 ou version ultérieure, vous pouvez utiliser ceci:

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

0

ce code T-SQL trouve et imprime toutes les occurrences du motif @p dans la phrase @s. vous pouvez faire n'importe quel traitement sur la phrase par la suite.

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

le résultat est: 1 6 13 20


0

pour SQL Server 2017

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

-1

Vous pouvez utiliser la procédure stockée suivante pour récupérer des valeurs.

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

la ligne 4 de cette procédure stockée définit @c1la réponse qu'il requiert. À quoi sert le reste du code, étant donné qu'il a besoin d'une table préexistante appelée table1pour fonctionner, a un délimètre codé en dur et ne peut pas être utilisé en ligne comme la réponse acceptée de deux mois auparavant?
Nick.McDermaid

-1

Le test Replace / Len est mignon, mais probablement très inefficace (surtout en termes de mémoire). Une simple fonction avec une boucle fera l'affaire.

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

Dans n'importe quelle table de taille appréciable, l'utilisation d'une fonction procédurale est beaucoup plus inefficace
Nick.McDermaid

Bon point. L'appel Len construit est-il beaucoup plus rapide qu'une fonction définie par l'utilisation?
Darrel Lee

À une grande échelle d'enregistrements, oui. Cependant, pour être certain, vous devrez tester sur un grand jeu d'enregistrements avec de grandes chaînes. N'écrivez jamais rien de procédural en SQL si vous pouvez l'éviter (c'est-à-dire des boucles)
Nick.McDermaid

-3

Vous ne devriez peut-être pas stocker les données de cette façon. Il est déconseillé de stocker une liste délimitée par des virgules dans un champ. L'IT est très inefficace pour les requêtes. Cela devrait être une table liée.


+1 pour y penser. C'est ce par quoi je commence habituellement lorsque quelqu'un utilise des données séparées par des virgules dans un champ.
Guffa

6
Une partie du but de cette question était de prendre des données existantes comme ça et de les séparer de manière appropriée.
Orion Adrian

7
Certains d'entre nous reçoivent des bases de données héritées où cela a été fait et nous ne pouvons rien y faire.
eddieroger

@Mulmoth, bien sûr, c'est une réponse. vous résolvez le problème et non le symptôme. Le problème vient de la conception de la base de données.
HLGEM

1
@HLGEM La question peut indiquer un problème, mais elle peut être comprise de manière plus générale. La question est tout à fait légitime pour des bases de données très bien normalisées.
Zeemee
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.