Làm thế nào để bạn đếm số lần xuất hiện của một chuỗi con nhất định trong một varchar SQL?


150

Tôi có một cột có các giá trị được định dạng như a, b, c, d. Có cách nào để đếm số dấu phẩy trong giá trị đó trong T-SQL không?

Câu trả lời:


245

Cách đầu tiên bạn nghĩ đến là làm điều đó một cách gián tiếp bằng cách thay thế dấu phẩy bằng một chuỗi rỗng và so sánh độ dài

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

13
Điều đó trả lời câu hỏi như được viết trong văn bản, nhưng không được viết trong tiêu đề. Để làm cho nó hoạt động cho nhiều hơn một ký tự, chỉ cần thêm một / len (searchterm) làm tròn điều. Đăng một câu trả lời trong trường hợp nó hữu ích cho ai đó.
Andrew Barrett

Ai đó đã chỉ ra cho tôi rằng điều này không phải lúc nào cũng hoạt động như mong đợi. Hãy xem xét những điều sau: CHỌN LEN ('a, b, c, d,') - LEN (REPLACE ('a, b, c, d,', ',', '')) Vì những lý do tôi chưa hiểu , khoảng trắng giữa cột d và cột cuối cùng khiến giá trị này trả về 5 thay vì 4. Tôi sẽ đăng một câu trả lời khác để khắc phục điều này, trong trường hợp nó hữu ích cho bất kỳ ai.
bong bóng

5
Có thể sử dụng DATALENGTH thay vì LEN sẽ tốt hơn, vì LEN trả về kích thước của chuỗi được cắt.
Rodrigocl

2
DATALENGTH () / 2 cũng khó khăn vì kích thước char không rõ ràng. Nhìn vào stackoverflow.com/a/11080074/1094048 để biết cách đơn giản và chính xác để có được độ dài chuỗi.
pkuderov

@rodrigocl Tại sao không quấn một LTRIMchuỗi xung quanh như sau : SELECT LEN(RTRIM(@string)) - LEN(REPLACE(RTRIM(@string), ',', ''))?
Alex Bello

67

Mở rộng nhanh câu trả lời của cmsjr hoạt động cho các chuỗi có nhiều ký tự hơn.

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

Sử dụng:

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

16
Một cải tiến nhỏ sẽ là sử dụng DATALENGTH () / 2 thay vì LEN (). LEN sẽ bỏ qua mọi khoảng trắng ở cuối, do đó dbo.CountOccurancesOfString( 'blah ,', ',')sẽ trả về 2 thay vì 1 và dbo.CountOccurancesOfString( 'hello world', ' ')sẽ thất bại với phép chia cho 0.
Rory

5
Nhận xét của Rory là hữu ích. Tôi thấy rằng tôi chỉ có thể thay thế LEN bằng DATALENGTH trong chức năng của Andrew và nhận được kết quả mong muốn. Có vẻ như việc chia cho 2 là không cần thiết với cách làm toán.
Vòng hoa của Giáo hoàng

@AndrewBarrett: Điều gì nối thêm khi một số chuỗi có cùng độ dài?
dùng2284570

2
DATALENGTH()/2cũng khó khăn vì kích thước char không rõ ràng. Nhìn vào stackoverflow.com/a/11080074/1094048 để biết cách đơn giản và chính xác.
pkuderov

26

Bạn có thể so sánh độ dài của chuỗi với một dấu phẩy được xóa:

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

8

Dựa trên giải pháp của @ Andrew, bạn sẽ có hiệu suất tốt hơn nhiều khi sử dụng hàm không có giá trị của bảng theo thủ tục và ỨNG DỤNG CROSS:

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

Tôi sử dụng chức năng tương tự này trong nhiều cơ sở dữ liệu kế thừa của mình, nó giúp ích rất nhiều cho rất nhiều cơ sở dữ liệu cũ và được thiết kế không đúng. Tiết kiệm rất nhiều thời gian và rất nhanh ngay cả trên các tập dữ liệu lớn.
Caimen

6

Câu trả lời của @csmjr có vấn đề trong một số trường hợp.

Câu trả lời của ông là làm điều này:

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

Điều này hoạt động trong hầu hết các kịch bản, tuy nhiên, hãy thử chạy này:

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

Vì một số lý do, REPLACE thoát khỏi dấu phẩy cuối cùng nhưng CSONG không gian ngay trước nó (không chắc tại sao). Điều này dẫn đến giá trị trả về là 5 khi bạn mong đợi 4. Đây là một cách khác để làm điều này sẽ hoạt động ngay cả trong kịch bản đặc biệt này:

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

Lưu ý rằng bạn không cần sử dụng dấu sao. Bất kỳ thay thế hai ký tự sẽ làm. Ý tưởng là bạn kéo dài chuỗi bằng một ký tự cho mỗi phiên bản của ký tự bạn đang đếm, sau đó trừ đi độ dài của bản gốc. Về cơ bản, đây là phương pháp ngược lại với câu trả lời ban đầu không đi kèm với hiệu ứng phụ cắt tỉa kỳ lạ.


5
"Vì một số lý do, REPLACE thoát khỏi dấu phẩy cuối cùng nhưng CSONG không gian ngay trước nó (không chắc tại sao)." THAY THẾ không thoát khỏi dấu phẩy cuối cùng và khoảng trắng trước nó, thực ra đó là hàm LEN bỏ qua khoảng trắng dẫn đến cuối chuỗi vì khoảng trống đó.
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)

điều này thực sự trả về 1 ít hơn số lượng thực tế
Nhà tích hợp

1

Câu trả lời được chấp nhận là chính xác, mở rộng nó để sử dụng 2 hoặc nhiều ký tự trong chuỗi con:

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

1

Nếu chúng ta biết có giới hạn về LEN và không gian, tại sao chúng ta không thể thay thế không gian trước? Sau đó, chúng tôi biết không có không gian để nhầm lẫn 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 tôi nghĩ rằng có một câu trả lời khá tốt. Thay thế CHARINDEX()bằng PATINDEX()và bạn cũng có thể thực hiện một số regextìm kiếm yếu dọc theo chuỗi, ...

Giống như, nói rằng bạn sử dụng điều này cho @pattern:

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

Tại sao bạn có thể muốn làm một cái gì đó điên rồ như thế này?

Giả sử bạn đang tải các chuỗi văn bản được phân tách vào một bảng phân tầng, trong đó trường chứa dữ liệu giống như một varchar (8000) hoặc nvarchar (max) ...

Đôi khi, thực hiện ELT (Extract-Load-Transform) với dữ liệu thay vì ETL (Extract-Transform-Load) dễ dàng hơn và nhanh hơn, và một cách để làm điều này là tải các bản ghi được phân tách như trong bảng phân tầng, đặc biệt là bạn có thể muốn một cách đơn giản hơn để xem các bản ghi đặc biệt thay vì xử lý chúng như một phần của gói SSIS ... nhưng đó là một cuộc chiến thần thánh cho một chủ đề khác.


0

Sau đây nên thực hiện thủ thuật cho cả tìm kiếm một ký tự và nhiều ký tự:

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;

Hàm có thể được đơn giản hóa một chút bằng cách sử dụng bảng số (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

Sử dụng mã này, nó đang hoạt động hoàn hảo. Tôi đã tạo một hàm sql chấp nhận hai tham số, tham số đầu tiên là chuỗi dài mà chúng ta muốn tìm kiếm và nó có thể chấp nhận độ dài chuỗi lên tới 1500 ký tự (tất nhiên bạn có thể mở rộng nó hoặc thậm chí thay đổi nó thành kiểu dữ liệu văn bản ). Và tham số thứ hai là chuỗi con mà chúng tôi muốn tính số lần xuất hiện của nó (độ dài của nó lên tới 200 ký tự, tất nhiên bạn có thể thay đổi nó thành những gì bạn cần). và đầu ra là một số nguyên, biểu thị số tần số ..... thưởng thức nó.


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

Cuối cùng tôi đã viết hàm này sẽ bao gồm tất cả các tình huống có thể, thêm tiền tố char và hậu tố vào đầu vào. char này được đánh giá là khác với bất kỳ char nào được xác định trong tham số tìm kiếm, vì vậy nó không thể ảnh hưởng đến kết quả.

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 

sử dụng

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

Trong SQL 2017 trở lên, bạn có thể sử dụng điều này:

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

0

mã T-SQL này tìm và in tất cả các lần xuất hiện của mẫu @p trong câu @s. bạn có thể thực hiện bất kỳ xử lý trên câu sau đó.

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

kết quả là: 1 6 13 20


0

cho SQL Server 2017

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

-1

Bạn có thể sử dụng các thủ tục được lưu trữ sau đây để tìm nạp, các giá trị.

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

dòng 4 của thủ tục được lưu trữ này đặt @c1ra câu trả lời mà anh ta yêu cầu. Việc sử dụng là phần còn lại của mã, xem xét rằng nó cần một bảng có sẵn được gọi table1để làm việc, có một phân tách mã hóa cứng và không thể được sử dụng nội tuyến như câu trả lời được chấp nhận từ hai tháng trước?
Nick.McDilyn

-1

Bài kiểm tra Thay thế / Len rất dễ thương, nhưng có lẽ rất kém hiệu quả (đặc biệt là về bộ nhớ). Một chức năng đơn giản với một vòng lặp sẽ thực hiện công việc.

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

Qua bất kỳ bảng kích thước đáng kể, sử dụng một hàm thủ tục là xa hiệu quả hơn
Nick.McDermaid

Điểm tốt. Là cuộc gọi Len được xây dựng nhanh hơn nhiều sau đó là một chức năng được xác định sử dụng?
Darrel Lee

Ở quy mô lớn của hồ sơ, có. Mặc dù chắc chắn bạn sẽ phải kiểm tra trên một tập bản ghi lớn với các chuỗi lớn. Không bao giờ viết bất cứ điều gì theo thủ tục trong SQL nếu bạn có thể tránh nó (ví dụ như các vòng lặp)
Nick.McDilyn

-3

Có lẽ bạn không nên lưu trữ dữ liệu theo cách đó. Đó là một thực tế xấu khi lưu trữ một danh sách được phân cách bằng dấu phẩy trong một trường. CNTT rất không hiệu quả để truy vấn. Đây phải là một bảng liên quan.


+1 vì nghĩ về điều đó. Đó là những gì tôi thường bắt đầu khi ai đó sử dụng dữ liệu được phân tách bằng dấu phẩy trong một trường.
Guffa

6
Một phần của mục đích của câu hỏi này là lấy dữ liệu hiện có như thế và phân tách nó một cách thích hợp.
Orion Adrian

7
Một số người trong chúng ta được cung cấp cơ sở dữ liệu kế thừa nơi đã được thực hiện và chúng ta không thể làm gì về nó.
eddieroger

@Mulmoth, tất nhiên đó là một câu trả lời. bạn khắc phục vấn đề không phải là triệu chứng. Vấn đề là với thiết kế cơ sở dữ liệu.
HLGEM

1
@HLGEM Câu hỏi có thể chỉ ra một vấn đề, nhưng nó có thể được hiểu tổng quát hơn. Câu hỏi là hoàn toàn hợp pháp cho các cơ sở dữ liệu chuẩn hóa rất tốt.
Zeemee
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.