Cách tìm tất cả các vị trí của một chuỗi trong một chuỗi khác


11

Làm thế nào tôi có thể tìm thấy tất cả các vị trí với patindextrong một bảng hoặc biến?

declare @name nvarchar(max)
set @name ='ali reza dar yek shabe barani ba yek  '
  + 'dokhtare khoshkel be disco raft va ali baraye'
  + ' 1 saat anja bud va sepas... ali...'
select patindex('%ali%',@name) as pos 

Điều này trả về 1nhưng tôi muốn tất cả kết quả, ví dụ:

pos
===
  1
 74
113

Câu trả lời:


9
declare @name nvarchar(max)
set @name ='ali reza dar yek shabe barani ba yek  dokhtare khoshkel be disco raft va ali baraye 1 saat anja bud va sepas... ali...'

Declare @a table (pos int)
Declare @pos int
Declare @oldpos int
Select @oldpos=0
select @pos=patindex('%ali%',@name) 
while @pos > 0 and @oldpos<>@pos
 begin
   insert into @a Values (@pos)
   Select @oldpos=@pos
   select @pos=patindex('%ali%',Substring(@name,@pos + 1,len(@name))) + @pos
end

Select * from @a

Để làm cho nó có thể sử dụng lại, bạn có thể sử dụng nó trong một hàm bảng để gọi nó như sau:

Select * from  dbo.F_CountPats ('ali reza dar yek shabe barani ba yek  dokhtare khoshkel be disco raft va ali baraye 1 saat anja bud va sepas... ali...','%ali%')

Các chức năng có thể trông như thế này

Create FUNCTION [dbo].[F_CountPats] 
(
@txt varchar(max),
@Pat varchar(max)
)
RETURNS 
@tab TABLE 
(
 ID int
)
AS
BEGIN
Declare @pos int
Declare @oldpos int
Select @oldpos=0
select @pos=patindex(@pat,@txt) 
while @pos > 0 and @oldpos<>@pos
 begin
   insert into @tab Values (@pos)
   Select @oldpos=@pos
   select @pos=patindex(@pat,Substring(@txt,@pos + 1,len(@txt))) + @pos
end

RETURN 
END

GO

Tôi biết đây là câu hỏi cũ, nhưng tôi có câu hỏi về hiệu suất. Tôi đã xây dựng hai hàm tìm kiếm 1trong chuỗi chỉ chứa số không và hàm số. Tôi đã sử dụng giải pháp của bạn và @ aaron-bertrand, nhưng tôi có kết quả tương tự và hiệu suất tương tự. Giải pháp nào sẽ tốt hơn?
Misiu

2
@Misiu như mong đợi các giải pháp Aaron Bertrands không chỉ thanh lịch hơn mà còn nhanh hơn nhiều so với tôi và nên là giải pháp được chấp nhận. Bạn có thể kiểm tra điều này một cách dễ dàng với đầu vào lớn hơn, bằng cách sử dụng ví dụ của anh ấy, chỉ cần thêm SET @ name = Replicate (@ name, 5000) trước cuộc gọi CHỌN pos TỪ dbo.FindPotypeLocation (@name, 'ali'); và thử tương tự với thủ tục chậm chạp của tôi.
bummi

15

Tôi nghĩ rằng phương pháp này sẽ hiệu quả hơn một chút so với phương pháp lặp mà bạn đã chọn ( một số bằng chứng ở đây ) và chắc chắn hiệu quả hơn so với CTE đệ quy:

CREATE FUNCTION dbo.FindPatternLocation
(
    @string NVARCHAR(MAX),
    @term   NVARCHAR(255)
)
RETURNS TABLE
AS
    RETURN 
    (
      SELECT pos = Number - LEN(@term) 
      FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(@string, Number, 
      CHARINDEX(@term, @string + @term, Number) - Number)))
      FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
      FROM sys.all_objects) AS n(Number)
      WHERE Number > 1 AND Number <= CONVERT(INT, LEN(@string)+1)
      AND SUBSTRING(@term + @string, Number, LEN(@term)) = @term
    ) AS y);

Sử dụng mẫu:

DECLARE @name NVARCHAR(MAX);

SET @name = N'ali reza dar yek shabe barani ba yek'
    + '  dokhtare khoshkel be disco raft va ali baraye '
    + '1 saat anja bud va sepas... ali...';

SELECT pos FROM dbo.FindPatternLocation(@name, 'ali');

Các kết quả:

pos
---
  1
 74
113

Nếu chuỗi của bạn sẽ dài hơn 2K thì hãy sử dụng sys.all_columns thay vì sys.all_objects. Nếu dài hơn 8K thì thêm một liên kết chéo.


2

- CTERecursive

with cte as
(select 'ali reza dar yek shabe barani ba yek  dokhtare khoshkel be disco raft va ali baraye 1 saat anja bud va sepas... ali...' as name
), 
pos as
(select patindex('%ali%',name) pos, name from cte
union all
select pos+patindex('%ali%',substring(name, pos+1, len(name))) pos, name from pos
where patindex('%ali%',substring(name, pos+1, len(name)))>0
)
select pos from pos

0

Tôi yêu câu trả lời của Aaron Bertrand. Mặc dù tôi không hiểu nó hoàn toàn, nhưng nó trông rất thanh lịch.

Trước đây tôi từng gặp vấn đề với quyền khi sử dụng sys.objects. Kết hợp với nhu cầu khắc phục sự cố mã, tôi đã đưa ra một biến thể về mã của Aaron và thêm nó vào bên dưới.

Đây là thủ tục của tôi:

CREATE PROCEDURE dbo.FindPatternLocations
-- Params
@TextToSearch nvarchar (max),
@TextToFind nvarchar (255)

AS
BEGIN

    declare @Length int
    set @Length = (Select LEN(@TextToSearch))

    declare @LengthSearchString int
    set @LengthSearchString = (select LEN (@TextToFind))

    declare @Index int
    set @Index=1

    create table #Positions (
    [POSID] [int] IDENTITY(0,1) NOT FOR REPLICATION NOT NULL,
    POS int
    )

    insert into #Positions (POS) select 0 -- to return a row even if no findings occur

        set @Index = (select charindex(@TextToFind, @TextToSearch, @Index))
                    if @Index = 0 goto Ende -- TextToFind is not in TextToSearch

        insert into #Positions (POS) select @Index


        set @Index = @Index + @LengthSearchString

while @Index <= @Length - @LengthSearchString   
    Begin
            set @Index = (select charindex(@TextToFind, @TextToSearch, @Index) )
            if @Index = 0 goto Ende -- no findings anymore
            insert into #Positions (POS) select @Index
            set @Index = @Index + @LengthSearchString
    end
Ende:
if (select MAX(posid) from #Positions) > 0 delete from #Positions where POSID = 0 -- row is not needed if TextToFind occurs
select * from #Positions
END
GO

Các MAX(posid)giá trị cũng là số trận đấu được tìm thấy.


Để được mô phạm, điều đó không giống bất kỳ biến thể nào trong mã của tôi. Ở tất cả. :-) Đây chính xác là kiểu vòng lặp vũ phu mà tôi chủ trương chống lại (và đã được chứng minh là chậm hơn ).
Aaron Bertrand

0

Đây là một mã đơn giản dựa trên câu trả lời của Aaron rằng:

  • Không giới hạn kích thước của sys.all_objects
  • Đừng bỏ lỡ 'X' cuối cùng

MÃ:

DECLARE @termToFind CHAR(1) = 'X'
DECLARE @string VARCHAR(40) = 'XX XXX  X   XX'

SET @string += '.' --Add any data here (different from the one searched) to get the position of the last character

DECLARE @stringLength BIGINT = len(@string)

SELECT pos = Number - LEN(@termToFind)
FROM (
    SELECT Number
        , Item = LTRIM(RTRIM(SUBSTRING(@string, Number, CHARINDEX(@termToFind, @string + @termToFind, Number) - Number)))
    FROM (
        --All numbers between 1 and the lengh of @string. Better than use sys.all_objects
        SELECT TOP (@stringLength) row_number() OVER (
                ORDER BY t1.number
                ) AS N
        FROM master..spt_values t1
        CROSS JOIN master..spt_values t2
        ) AS n(Number)
    WHERE Number > 1
        AND Number <= CONVERT(INT, LEN(@string))
        AND SUBSTRING(@termToFind + @string, Number, LEN(@termToFind)) = @termToFind
    ) AS y

KẾT QUẢ

pos
--------------------
1
2
4
5
6
9
13
14

(8 row(s) affected)

Tôi tin rằng tôi đã giải quyết kích thước của sys.all_columns(bạn có thể sử dụng bất kỳ nguồn nào miễn là nó bao gồm độ dài của chuỗi dài nhất của bạn) và tôi cũng đã kiểm tra lại và tôi không thấy nơi tôi bỏ lỡ 'X' cuối cùng .. .
Aaron Bertrand

0

Xin lỗi các bạn đã đến quá muộn, nhưng tôi muốn làm mọi thứ dễ dàng hơn cho những người muốn mở rộng về điều này. Tôi đã xem xét từng triển khai này, lấy cái có vẻ tốt nhất với tôi (Aaron Bertrand), đơn giản hóa nó và ở đây bạn đi, bạn có "khuôn mẫu". Sử dụng nó một cách rộng rãi.

CREATE FUNCTION dbo.CHARINDICES (
    @search_expression NVARCHAR(4000),
    @expression_to_be_searched NVARCHAR(MAX)
) RETURNS TABLE AS RETURN (
    WITH tally AS (
        SELECT Number = ROW_NUMBER() OVER (ORDER BY [object_id])
        FROM sys.all_objects)
    SELECT DISTINCT n = subIdx -- (4) if we don't perform distinct we'll get result for each searched substring, and we don't want that
    FROM 
        tally 
        CROSS APPLY (SELECT subIdx = CHARINDEX(@search_expression, @expression_to_be_searched, Number)) x -- (2) subIdx is found in the rest of the substring 
    WHERE 
        Number BETWEEN 1 AND LEN(@expression_to_be_searched) -- (1) run for each substring once
        AND SubIdx != 0  -- (3) we care only about the indexes we've found, 0 stands for "not found"
)

SELECT CHARINDEX('C', 'BACBABCBABBCBACBBABC')
SELECT * FROM dbo.CHARINDICES('C', 'BACBABCBABBCBACBBABC')

Cũng như một tài liệu tham khảo - bạn có thể rút ra các hành vi khác từ điều này, như mở rộng trên PATINDEX ():

CREATE FUNCTION dbo.PATINDICES (
    @search_expression NVARCHAR(4000) = '%[cS]%',
    @expression_to_be_searched NVARCHAR(MAX) = 'W3Schools.com'
) RETURNS TABLE AS RETURN (
    WITH tally AS (
        SELECT num = ROW_NUMBER() OVER (ORDER BY [object_id])
        FROM sys.all_objects)
    SELECT DISTINCT n = subIdx + num - 1
    FROM 
        tally 
        CROSS APPLY (SELECT numRev = LEN(@expression_to_be_searched) - num + 1) x
        CROSS APPLY (SELECT subExp = RIGHT(@expression_to_be_searched, numRev)) y
        CROSS APPLY (SELECT subIdx = PATINDEX(@search_expression, subExp)) z
    WHERE 
        num BETWEEN 1 AND LEN(@expression_to_be_searched)
        AND SubIdx != 0
)

SELECT PATINDEX('%[cS]%', 'W3Schools.com')
SELECT * FROM dbo.PATINDICES('%[cS]%', 'W3Schools.com')

0
Declare @search varchar(5)
    sET @search='a'
    Declare @name varchar(40)
    Set @name='AmitabhBachan'
    Declare @init int
    Set @init=1
    Declare @hold int
    Declare @table table (POSITION Int)
    While( @init<= LEn(@name))
    Begin
   Set @hold=(Select CHARINDEX(@search,@name,@init))
   If (@hold!=0)
   BEgin 
   --Print @hold
   Insert into @table
   Select @hold
   Set @init=@hold+1
   End 
   Else
   If (@hold=0)
   BEgin
   Break
   End
  End
  Select * from @table

Điều này sẽ được hưởng lợi rất nhiều từ thụt & vỏ phù hợp. Một vài từ để giải thích cách tiếp cận và thực hiện cũng sẽ đi một chặng đường dài.
Michael Green
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.