Bir SQL varchar içinde belirli bir alt dizenin oluşum sayısını nasıl hesaplıyorsunuz?


150

Ben a, b, c, d gibi biçimlendirilmiş değerleri olan bir sütun var. T-SQL'de bu değerdeki virgül sayısını saymanın bir yolu var mı?

Yanıtlar:


245

Akla gelen ilk yol, boş bir dize ile virgül değiştirerek ve uzunlukları karşılaştırarak dolaylı olarak yapmaktır.

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

13
Bu soruya metinde yazıldığı gibi cevap verir, ancak başlıkta yazıldığı gibi cevaplanmaz. Birden fazla karakter için çalışmasını sağlamak için bir / len (searchterm) eklemeniz yeterlidir. Birisi için yararlı bir cevap gönderdi.
Andrew Barrett

Birisi bana bunun her zaman beklendiği gibi çalışmadığını belirtti. Aşağıdakileri düşünün: SELEN LEN ('a, b, c, d,') - LEN (DEĞİŞTİR ('a, b, c, d,', ',', '')) Henüz anlamadığım nedenlerle , d ile son sütun arasındaki boşluk 4 yerine 5 döndürür.
bubbleking

5
Belki LEN yerine DATALENGTH kullanmak daha iyi olurdu, çünkü LEN kesilen dizenin boyutunu döndürür.
rodrigocl

2
DATALENGTH () / 2, belirgin olmayan karakter boyutları nedeniyle de zordur. Dize uzunluğu elde etmenin basit ve doğru yolu için stackoverflow.com/a/11080074/1094048 adresine bakın.
pkuderov

Bir sarmalayamazsınız Neden @rodrigocl LTRIMaşağıdaki gibi dize etrafında: SELECT LEN(RTRIM(@string)) - LEN(REPLACE(RTRIM(@string), ',', ''))?
Alex Bello

67

Daha fazla karakter dizeleri için çalışan cmsjr cevabının hızlı uzantısı.

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

Kullanımı:

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

16
LEN () yerine DATALENGTH () / 2 kullanmak için küçük bir gelişme olacaktır. LEN, izleyen boşlukları yok sayar, bu nedenle dbo.CountOccurancesOfString( 'blah ,', ',')1 yerine 2 döndürür ve dbo.CountOccurancesOfString( 'hello world', ' ')sıfıra bölme başarısız olur.
Rory

5
Rory'nin yorumu yardımcı olur. Andrew'un işlevinde LEN'i DATALENGTH ile değiştirebileceğimi ve istenen sonucu elde edebileceğimi buldum. Matematiğin çalışma şekli ile 2'ye bölmek gerekli görünmüyor.
Garland Pope

@AndrewBarrett: Birkaç dize aynı uzunlukta olduğunda ne eklenir?
user2284570

2
DATALENGTH()/2Ayrıca, belirgin olmayan karakter boyutları nedeniyle de zor. Basit ve doğru bir yol için stackoverflow.com/a/11080074/1094048 adresine bakın .
pkuderov

26

Dizenin uzunluğunu virgüllerin kaldırıldığı bir dizeyle karşılaştırabilirsiniz:

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

8

@ Andrew'un çözümünü temel alarak, işlemsel olmayan tablo değerli bir işlev ve CROSS UYGULAMA kullanarak çok daha iyi performans elde edersiniz:

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

Aynı işlevi eski veritabanlarımın çoğunda kullanıyorum, eski ve yanlış tasarlanmış birçok veritabanında çok yardımcı oluyor. Çok zaman kazandırır ve büyük veri setlerinde bile çok hızlıdır.
Caimen

6

@Csmjr tarafından verilen yanıtın bazı durumlarda bir sorunu vardır.

Cevabı bunu yapmaktı:

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

Bu, çoğu senaryoda çalışır, ancak şunu çalıştırmayı deneyin:

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

Bazı nedenlerden dolayı, REPLACE son virgülden kurtulur, ancak AYRICA hemen önündeki boşluk (neden olduğundan emin değilsiniz). Bu, 4'ü beklediğinizde 5 değerini döndürür. Bu, bu özel senaryoda bile çalışacak başka bir yöntemdir:

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

Yıldız işareti kullanmanıza gerek olmadığını unutmayın. Herhangi iki karakterli değiştirme yapılır. Fikir, dizeyi saydığınız karakterin her bir örneği için bir karakter uzatmanız ve ardından orijinalin uzunluğunu çıkarmanızdır. Temel olarak, orijinal cevabın garip kırpma yan etkisi ile gelmeyen zıt yöntemidir.


5
"Bir sebepten dolayı, REPLACE son virgülden kurtulur ama AYRICA hemen önündeki boşluk (neden olduğundan emin değilsiniz)." REPLACE son virgül ve ondan önceki boşluktan kurtulmuyor, aslında bu boşluk nedeniyle dizenin sonunda oluşan beyaz boşluğu görmezden gelen LEN işlevidir.
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)

bu aslında gerçek
Entegratör

1

Kabul edilen cevap doğrudur ve alt dize içinde 2 veya daha fazla karakter kullanacak şekilde genişletilir:

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

1

LEN ve alan üzerinde bir sınırlama olduğunu bilersek, neden önce alanı değiştiremiyoruz? O zaman LEN'i karıştırmak için yer olmadığını biliyoruz.

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 Bence oldukça iyi bir cevabı var. Değiştir CHARINDEX()ile PATINDEX()ve bazı zayıf yapabilirsiniz regexde bir dize boyunca arama ...

Diyelim ki şunu kullanıyorsunuz @pattern:

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

Neden böyle çılgınca bir şey yapmak istesin?

Sınırlı metin dizelerini, verileri tutan alanın varchar (8000) veya nvarchar (max) gibi bir şey olduğu bir hazırlama tablosuna yüklediğinizi varsayalım ...

Bazen ETL (Extract-Transform-Load) yerine verilerle ELT (Extract-Load-Transform) yapmak daha kolay / hızlıdır ve bunu yapmanın bir yolu, sınırlandırılmış kayıtları olduğu gibi bir hazırlama tablosuna olduğu gibi yüklemek, özellikle de bir SSIS paketinin parçası olarak onlarla uğraşmak yerine istisnai kayıtları görmenin daha basit bir yolunu isteyebilirsiniz ... ama bu farklı bir iş parçacığı için kutsal bir savaş.


0

Aşağıdakiler, hem tek karakter hem de çoklu karakter aramaları için hile yapmalıdır:

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;

Bir sayı tablosu (dbo.Nums) kullanılarak işlev biraz basitleştirilebilir:

   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

Bu kodu kullanın, mükemmel çalışıyor. İki parametre kabul eden bir sql işlevi var, ilk param içine aramak istediğimiz uzun dizedir ve 1500 karaktere kadar dize uzunluğunu kabul edebilir (tabii ki genişletebilir veya hatta metin veri tipine değiştirebilirsiniz) ). Ve ikinci parametre, meydana gelme sayısını hesaplamak istediğimiz alt dizedir (uzunluğu 200 karaktere kadardır, elbette bunu ihtiyacınıza göre değiştirebilirsiniz). ve çıkış bir tamsayı, frekans sayısını temsil ..... zevk.


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

Son olarak, girdiye bir char öneki ve son ek ekleyerek tüm olası durumları kapsaması gereken bu işlevi yazıyorum. bu karakter, arama parametresinde bulunan karakterlerden farklı olarak değerlendirilir, dolayısıyla sonucu etkileyemez.

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 

kullanım

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

SQL 2017 veya daha yeni sürümlerde şunları kullanabilirsiniz:

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

0

bu T-SQL kodu cümlesindeki @ paterninin tüm örneklerini bulur ve yazdırır. daha sonra cümle üzerinde herhangi bir işlem yapabilirsiniz.

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

sonuç: 1 6 13 20


0

SQL Server 2017 için

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

-1

Değerleri almak için aşağıdaki saklı yordamı kullanabilirsiniz.

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

bu saklı yordamın 4. satırı @c1istediği cevaba ayarlanır . Kodun geri kalanı, table1çalışmak için önceden var olan bir tabloya ihtiyaç duyduğu , sabit kodlu bir sınırlayıcıya sahip olduğu ve iki ay önceki kabul edilen cevap gibi satır içi kullanılamayacağı düşünüldüğünde ne işe yarar ?
Nick.McDermaid

-1

Replace / Len testi sevimli, ama muhtemelen çok verimsiz (özellikle bellek açısından). Bir döngü ile basit bir işlev işi yapar.

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


İyi bir nokta. Dahili Len çağrısı, tanımlanmış bir fonksiyondan çok daha hızlı mı?
Darrel Lee

Büyük çaplı kayıtlarda, evet. Emin olsanız da, büyük dizeleri olan büyük bir kayıt kümesinde test yapmanız gerekir. Bundan kaçınabiliyorsanız SQL'de yordamsal bir şey asla yazmayın (yani döngüler)
Nick.McDermaid

-3

Belki de verileri bu şekilde depolamamalısınız. Bir alanda virgülle ayrılmış bir listeyi saklamak kötü bir uygulamadır. BT sorgulama için çok verimsiz. Bu ilgili bir tablo olmalıdır.


Bunu düşündüğünüz için +1. Birisi bir alanda virgülle ayrılmış veriler kullandığında genellikle bununla başlarım.
Guffa

6
Bu sorunun amacının bir kısmı, bu gibi mevcut verileri almak ve uygun şekilde ayırmaktı.
Orion Adrian

7
Bazılarımıza bunun yapıldığı eski veritabanları verilir ve bu konuda hiçbir şey yapamayız.
eddieroger

@Mulmoth, elbette bir cevap. Sorunu değil, sorunu çözersiniz. Sorun veritabanı tasarımı ile.
HLGEM

1
@HLGEM soru olabilir bir problem olduğuna işaret etmektedir, ancak daha genel anlaşılabilir. Soru çok iyi normalize edilmiş veritabanları için tamamen meşru.
Zeemee
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.