¿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?

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

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

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

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

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.


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)

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)

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

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

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.


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
          );

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

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

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

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)

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


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;

-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

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

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.


+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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.