Bagaimana Anda menghitung jumlah kemunculan substring tertentu dalam SQL varchar?


150

Saya memiliki kolom yang memiliki nilai yang diformat seperti a, b, c, d. Apakah ada cara untuk menghitung jumlah koma dalam nilai itu di T-SQL?

Jawaban:


245

Cara pertama yang terlintas dalam pikiran adalah melakukannya secara tidak langsung dengan mengganti koma dengan string kosong dan membandingkan panjangnya

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

13
Itu menjawab pertanyaan seperti yang tertulis dalam teks, tetapi tidak seperti yang tertulis dalam judul. Untuk membuatnya berfungsi lebih dari satu karakter, cukup tambahkan a / len (searchterm) di sekelilingnya. Diposting jawaban seandainya bermanfaat bagi seseorang.
Andrew Barrett

Seseorang menunjukkan kepada saya bahwa ini tidak selalu berhasil seperti yang diharapkan. Pertimbangkan hal-hal berikut: SELECT LEN ('a, b, c, d,') - LEN (REPLACE ('a, b, c, d,', ',', ',' ')) Untuk alasan yang saya belum mengerti , ruang antara d dan kolom terakhir menyebabkan ini mengembalikan 5, bukan 4. Saya akan memposting jawaban lain yang memperbaiki ini, jika berguna bagi siapa pun.
bubbleking

5
Mungkin menggunakan DATALENGTH sebagai ganti LEN akan lebih baik, karena LEN mengembalikan ukuran string yang dipangkas.
rodrigocl

2
DATALENGTH () / 2 juga rumit karena ukuran arang yang tidak jelas. Lihatlah stackoverflow.com/a/11080074/1094048 untuk cara sederhana dan akurat untuk mendapatkan panjang string.
pkuderov

@rodrigocl Mengapa tidak membungkus sebuah LTRIMsekitar string sebagai berikut: SELECT LEN(RTRIM(@string)) - LEN(REPLACE(RTRIM(@string), ',', ''))?
Alex Bello

67

Perpanjangan cepat jawaban cmsjr yang berfungsi untuk string lebih dari lebih banyak karakter.

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

Pemakaian:

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

16
Sedikit perbaikan akan menggunakan DATALENGTH () / 2 bukannya LEN (). LEN akan mengabaikan spasi spasi tambahan sehingga dbo.CountOccurancesOfString( 'blah ,', ',')akan mengembalikan 2 bukannya 1 dan dbo.CountOccurancesOfString( 'hello world', ' ')akan gagal dengan membagi dengan nol.
Rory

5
Komentar Rory sangat membantu. Saya menemukan bahwa saya bisa mengganti LEN dengan DATALENGTH dalam fungsi Andrew dan mendapatkan hasil yang diinginkan. Tampaknya membaginya dengan 2 tidak perlu dengan cara matematika bekerja.
Garland Paus

@AndrewBarrett: Apa yang ditambahkan ketika beberapa string memiliki panjang yang sama?
user2284570

2
DATALENGTH()/2juga rumit karena ukuran char tidak jelas. Lihatlah stackoverflow.com/a/11080074/1094048 untuk cara yang sederhana dan akurat.
pkuderov

26

Anda dapat membandingkan panjang string dengan satu di mana koma dihapus:

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

8

Dengan menggunakan solusi @ Andrew, Anda akan mendapatkan kinerja yang jauh lebih baik menggunakan fungsi bernilai tabel non-prosedural dan CROSS BERLAKU:

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

Saya menggunakan fungsi yang sama ini di banyak basis data lama saya, sangat membantu dengan banyak basis data lama dan tidak dirancang dengan baik. Menghemat banyak waktu dan sangat cepat bahkan pada set data besar.
Caimen

6

Jawaban oleh @csmjr memiliki masalah dalam beberapa kasus.

Jawabannya adalah melakukan ini:

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

Ini berfungsi di sebagian besar skenario, namun, coba jalankan ini:

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

Untuk beberapa alasan, REPLACE menghilangkan koma terakhir tetapi JUGA ruang sebelum itu (tidak yakin mengapa). Ini menghasilkan nilai 5 yang dikembalikan saat Anda mengharapkan 4. Berikut adalah cara lain untuk melakukan ini yang akan berfungsi bahkan dalam skenario khusus ini:

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

Perhatikan bahwa Anda tidak perlu menggunakan tanda bintang. Penggantian dua karakter apa pun akan dilakukan. Idenya adalah Anda memperpanjang string dengan satu karakter untuk setiap instance karakter yang Anda hitung, lalu kurangi panjang aslinya. Ini pada dasarnya metode yang berlawanan dari jawaban asli yang tidak datang dengan efek samping pemangkasan yang aneh.


5
"Untuk beberapa alasan, REPLACE menghilangkan koma terakhir tetapi JUGA ruang sebelum itu (tidak yakin mengapa)." REPLACE tidak menghilangkan koma terakhir dan spasi sebelumnya, itu sebenarnya fungsi LEN yang mengabaikan ruang putih yang menghasilkan di akhir string karena ruang itu.
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)

ini benar-benar mengembalikan 1 kurang dari hitungan sebenarnya
The Integrator

1

Jawaban yang diterima benar, memperluasnya menggunakan 2 atau lebih karakter dalam substring:

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

1

Jika kita tahu ada batasan pada LEN dan ruang, mengapa kita tidak mengganti ruang terlebih dahulu? Maka kita tahu tidak ada ruang untuk membingungkan 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

Saya kira Darrel Lee punya jawaban yang cukup bagus. Ganti CHARINDEX()dengan PATINDEX(), dan Anda dapat melakukan regexpencarian yang lemah di sepanjang string, juga ...

Seperti, katakan Anda menggunakan ini untuk @pattern:

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

Mengapa Anda mungkin ingin melakukan sesuatu yang gila seperti ini?

Katakanlah Anda sedang memuat string teks terbatas ke tabel pementasan, di mana bidang yang menyimpan data adalah sesuatu seperti varchar (8000) atau nvarchar (maks) ...

Terkadang lebih mudah / lebih cepat untuk melakukan ELT (Extract-Load-Transform) dengan data daripada ETL (Extract-Transform-Load), dan salah satu cara untuk melakukan ini adalah dengan memuat catatan yang dibatasi seperti apa adanya ke dalam tabel pementasan, terutama jika Anda mungkin menginginkan cara yang lebih sederhana untuk melihat catatan yang luar biasa daripada berurusan dengan mereka sebagai bagian dari paket SSIS ... tapi itu perang suci untuk utas yang berbeda.


0

Hal-hal berikut harus melakukan trik untuk pencarian karakter tunggal dan banyak karakter:

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;

Fungsi ini dapat disederhanakan sedikit menggunakan tabel angka (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

Gunakan kode ini, ini berfungsi dengan baik. Saya telah membuat fungsi sql yang menerima dua parameter, param pertama adalah string panjang yang ingin kita cari di dalamnya, dan dapat menerima panjang string hingga 1500 karakter (tentu saja Anda dapat memperluas atau bahkan mengubahnya ke datatype teks ). Dan parameter kedua adalah substring yang ingin kita hitung jumlah kemunculannya (panjangnya hingga 200 karakter, tentu saja Anda dapat mengubahnya sesuai dengan kebutuhan Anda). dan output adalah bilangan bulat, mewakili jumlah frekuensi ..... nikmatilah.


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

Saya akhirnya menulis fungsi ini yang harus mencakup semua situasi yang mungkin, menambahkan awalan char dan akhiran ke input. arang ini dievaluasi berbeda dengan arang yang terdapat pada parameter pencarian, sehingga tidak dapat mempengaruhi hasilnya.

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 

pemakaian

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

Di SQL 2017 atau lebih tinggi, Anda dapat menggunakan ini:

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

0

kode T-SQL ini menemukan dan mencetak semua kemunculan pola @p dalam kalimat @s. Anda dapat melakukan pemrosesan pada kalimat setelahnya.

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

hasilnya adalah: 1 6 13 20


0

untuk SQL Server 2017

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

-1

Anda dapat menggunakan prosedur tersimpan berikut untuk mengambil, nilai.

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

baris 4 dari prosedur tersimpan ini mengatur @c1ke jawaban yang dia butuhkan. Apa gunanya sisa kode, mengingat perlu tabel yang sudah ada sebelumnya dipanggil table1untuk bekerja, memiliki delimeter kode keras, dan tidak dapat digunakan sebaris seperti jawaban yang diterima dari dua bulan sebelumnya?
Nick.McDermaid

-1

Tes Replace / Len lucu, tetapi mungkin sangat tidak efisien (terutama dalam hal memori). Fungsi sederhana dengan loop akan melakukan pekerjaan itu.

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

Melintasi tabel ukuran apa pun yang cukup besar, menggunakan fungsi prosedural jauh lebih tidak efisien
Nick.McDermaid

Poin yang bagus. Apakah panggilan Len yang dibangun jauh lebih cepat daripada fungsi yang didefinisikan?
Darrel Lee

Pada skala besar catatan, ya. Meskipun untuk memastikan Anda harus menguji pada recordset besar dengan string besar. Jangan pernah menulis apa pun prosedural dalam SQL jika Anda dapat menghindarinya (yaitu loop)
Nick.McDermaid

-3

Mungkin Anda sebaiknya tidak menyimpan data seperti itu. Merupakan praktik yang buruk untuk pernah menyimpan daftar yang dibatasi koma di bidang. TI sangat tidak efisien untuk query. Ini harus berupa tabel terkait.


+1 untuk memikirkannya. Itu yang biasanya saya mulai dengan ketika seseorang menggunakan data yang dipisahkan koma dalam bidang.
Guffa

6
Bagian dari tujuan pertanyaan ini adalah untuk mengambil data yang ada seperti itu dan membaginya dengan tepat.
Orion Adrian

7
Beberapa dari kita diberikan basis data warisan di mana itu dilakukan dan kita tidak bisa berbuat apa-apa.
eddieroger

@Mulmoth, tentu saja itu adalah jawaban. Anda memperbaiki masalahnya bukan gejalanya. Masalahnya adalah dengan desain database.
HLGEM

1
@HLGEM Pertanyaannya mungkin mengarah ke masalah, tetapi bisa dipahami secara lebih umum. Pertanyaannya benar-benar sah untuk database yang dinormalisasi dengan sangat baik.
Zeemee
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.