¿Cómo se cuenta el número de ocurrencias de una determinada subcadena en un varchar SQL?

150

Tengo una columna que tiene valores formateados como a, b, c, d. ¿Hay alguna manera de contar la cantidad de comas en ese valor en T-SQL?

Orion Adrian
fuente

Respuestas:

245

La primera forma que viene a la mente es hacerlo indirectamente reemplazando la coma con una cadena vacía y comparando las longitudes

Declare @string varchar(1000)
Set @string = 'a,b,c,d'
select len(@string) - len(replace(@string, ',', ''))
cmsjr
fuente
13
Eso responde la pregunta como está escrita en el texto, pero no como está escrita en el título. Para que funcione para más de un personaje, solo necesita agregar un / len (término de búsqueda) alrededor de la cosa. Publicó una respuesta en caso de que sea útil para alguien.
Andrew Barrett
Alguien me señaló que esto no siempre funciona como se esperaba. Considere lo siguiente: SELECCIONAR LEN ('a, b, c, d,') - LEN (REPLACE ('a, b, c, d,', ',', '')) Por razones que aún no entiendo , el espacio entre d y la columna final hace que devuelva 5 en lugar de 4. Publicaré otra respuesta que solucione esto, en caso de que sea útil para alguien.
burbujeante
55
Quizás usar DATALENGTH en lugar de LEN sería mejor, porque LEN devuelve el tamaño de la cadena recortada.
rodrigocl
2
DATALENGTH () / 2 también es complicado debido a los tamaños de caracteres no obvios. Mire stackoverflow.com/a/11080074/1094048 para obtener una forma simple y precisa de obtener la longitud de la cadena.
pkuderov
@rodrigocl ¿Por qué no envolver una LTRIMcadena alrededor de la siguiente manera SELECT LEN(RTRIM(@string)) - LEN(REPLACE(RTRIM(@string), ',', '')):?
Alex Bello
67

Extensión rápida de la respuesta de cmsjr que funciona para cadenas de más que más caracteres.

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

Uso:

SELECT * FROM MyTable
where dbo.CountOccurrencesOfString(MyColumn, 'MyString') = 1
Andrew Barrett
fuente
16
Una ligera mejora sería usar DATALENGTH () / 2 en lugar de LEN (). LEN ignorará cualquier espacio en blanco al final, por dbo.CountOccurancesOfString( 'blah ,', ',')lo que devolverá 2 en lugar de 1 y dbo.CountOccurancesOfString( 'hello world', ' ')fallará al dividir por cero.
Rory
55
El comentario de Rory es útil. Descubrí que podía reemplazar LEN con DATALENGTH en la función de Andrew y obtener el resultado deseado. Parece que dividir entre 2 no es necesario con la forma en que funcionan las matemáticas.
Garland Pope
@ AndrewBarrett: ¿Qué se agrega cuando varias cadenas tienen la misma longitud?
user2284570
2
DATALENGTH()/2También es complicado debido a los tamaños de caracteres no obvios. Mire stackoverflow.com/a/11080074/1094048 para obtener una forma simple y precisa.
pkuderov
26

Puede comparar la longitud de la cadena con una donde se eliminan las comas:

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

Basándose en la solución de @ Andrew, obtendrá un rendimiento mucho mejor utilizando una función de tabla de valores no procesal y APLICACIÓN CRUZADA:

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
fuente
Utilizo esta misma función en muchas de mis bases de datos heredadas, me ayuda mucho con muchas bases de datos viejas y mal diseñadas. Ahorra mucho tiempo y es muy rápido incluso en grandes conjuntos de datos.
Caimen
6

La respuesta de @csmjr tiene un problema en algunos casos.

Su respuesta fue hacer esto:

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

Esto funciona en la mayoría de los escenarios, sin embargo, intente ejecutar esto:

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

Por alguna razón, REPLACE elimina la coma final, pero TAMBIÉN el espacio justo antes (no estoy seguro de por qué). Esto da como resultado un valor devuelto de 5 cuando esperarías 4. Aquí hay otra forma de hacerlo que funcionará incluso en este escenario especial:

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

Tenga en cuenta que no necesita usar asteriscos. Cualquier reemplazo de dos caracteres servirá. La idea es que alargue la cadena en un carácter para cada instancia del carácter que está contando, luego reste la longitud del original. Básicamente es el método opuesto de la respuesta original que no viene con el extraño efecto secundario de recorte.

burbujeante
fuente
55
"Por alguna razón, REPLACE elimina la coma final, pero TAMBIÉN el espacio justo antes (no estoy seguro de por qué)". REPLACE no se deshace de la última coma y el espacio anterior, en realidad es la función LEN la que ignora el espacio en blanco resultante al final de la cadena debido a ese espacio.
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
fuente
esto realmente devuelve 1 menos el recuento real
The Integrator
1

La respuesta aceptada es correcta, extendiéndola para usar 2 o más caracteres en la subcadena:

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

Si sabemos que hay una limitación en LEN y espacio, ¿por qué no podemos reemplazar el espacio primero? Entonces sabemos que no hay espacio para confundir a LEN.

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

Darrel Lee creo que tiene una respuesta bastante buena. Reemplace CHARINDEX()con PATINDEX(), y también puede hacer una regexbúsqueda débil a lo largo de una cadena ...

Como, digamos que usa esto para @pattern:

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

¿Por qué querrías hacer algo loco como esto?

Digamos que está cargando cadenas de texto delimitadas en una tabla de etapas, donde el campo que contiene los datos es algo así como un varchar (8000) o nvarchar (max) ...

A veces es más fácil / rápido hacer ELT (Extract-Load-Transform) con datos en lugar de ETL (Extract-Transform-Load), y una forma de hacerlo es cargar los registros delimitados tal como están en una tabla de etapas, especialmente si Es posible que desee una forma más simple de ver los registros excepcionales en lugar de tratarlos como parte de un paquete SSIS ... pero esa es una guerra santa para un hilo diferente.

usuario1390375
fuente
0

Lo siguiente debería hacer el truco para las búsquedas de caracteres únicos y múltiples:

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 función se puede simplificar un poco usando una tabla de números (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
fuente
0

Use este código, está funcionando perfectamente. He creado una función sql que acepta dos parámetros, el primer parámetro es la cadena larga que queremos buscar en él, y puede aceptar una longitud de cadena de hasta 1500 caracteres (por supuesto, puede extenderlo o incluso cambiarlo al tipo de datos de texto ) Y el segundo parámetro es la subcadena que queremos calcular el número de su ocurrencia (su longitud es de hasta 200 caracteres, por supuesto, puede cambiarlo a lo que necesite). y la salida es un número entero, representa el número de frecuencia ..... disfrútalo.


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
Un día
fuente
0

Finalmente escribo esta función que debería cubrir todas las situaciones posibles, agregando un prefijo y sufijo char a la entrada. Este carácter se evalúa como diferente de cualquiera de los caracteres contenidos en el parámetro de búsqueda, por lo que no puede afectar el resultado.

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 

uso

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

En SQL 2017 o superior, puede usar esto:

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

Este código T-SQL encuentra e imprime todas las apariciones del patrón @p en la oración @s. Puedes hacer cualquier procesamiento en la oración despué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

el resultado es: 1 6 13 20

Hasan Zafari
fuente
0

para SQL Server 2017

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

Puede usar el siguiente procedimiento almacenado para obtener valores.

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
fuente
la línea 4 de este procedimiento almacenado establece @c1la respuesta que requiere. ¿De qué sirve el resto del código, considerando que necesita una tabla preexistente llamada table1al trabajo, tiene un delimitador codificado y no puede usarse en línea como la respuesta aceptada de dos meses antes?
Nick.McDermaid
-1

La prueba Reemplazar / Len es linda, pero probablemente muy ineficiente (especialmente en términos de memoria). Una función simple con un bucle hará el trabajo.

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
fuente
En cualquier mesa de tamaño apreciable, el uso de una función de procedimiento es mucho más ineficiente
Nick.McDermaid
Buen punto. ¿La llamada de Len construida es mucho más rápida que una función definida de uso?
Darrel Lee
A gran escala de registros, sí. Sin embargo, para estar seguro, tendría que probar en un conjunto de registros grande con cadenas grandes. Nunca escriba nada de procedimiento en SQL si puede evitarlo (es decir, bucles)
Nick.McDermaid
-3

Quizás no debería almacenar datos de esa manera. Es una mala práctica almacenar una lista delimitada por comas en un campo. Es muy ineficiente para las consultas. Esta debería ser una tabla relacionada.

HLGEM
fuente
+1 por pensar en eso. Es con lo que generalmente empiezo cuando alguien usa datos separados por comas en un campo.
Guffa
66
Parte del propósito de esta pregunta era tomar datos existentes como ese y dividirlos apropiadamente.
Orion Adrian
77
Algunos de nosotros tenemos bases de datos heredadas donde se hizo eso y no podemos hacer nada al respecto.
eddieroger
@Mulmoth, por supuesto, es una respuesta. Arreglas el problema, no el síntoma. El problema es con el diseño de la base de datos.
HLGEM
1
@HLGEM La pregunta puede apuntar a un problema, pero puede entenderse de manera más general. La pregunta es totalmente legítima para bases de datos muy bien normalizadas.
Zeemee