Cara menemukan semua posisi string dalam string lain

11

Bagaimana saya bisa menemukan semua posisi dengan patindexdalam tabel atau variabel?

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 

Ini mengembalikan 1tetapi saya ingin semua hasil, misalnya:

pos
===
  1
 74
113
coditori
sumber

Jawaban:

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

Untuk membuatnya dapat digunakan kembali, Anda dapat menggunakannya dalam fungsi tabel untuk memanggilnya seperti:

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

Fungsinya bisa terlihat seperti ini

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
bummi
sumber
Saya tahu ini adalah pertanyaan lama, tetapi saya memiliki pertanyaan tentang kinerja. Saya telah membangun dua fungsi yang dicari 1dalam string yang hanya berisi nol dan satu. Saya telah menggunakan solusi dan @ aaron-bertrand Anda, tetapi saya mendapatkan hasil dan kinerja yang sama. Solusi mana yang lebih baik?
Misiu
2
@Misiu seperti yang diharapkan Solusi Aaron Bertrands tidak hanya lebih elegan tetapi bahkan lebih cepat daripada milikku dan harus menjadi solusi yang diterima. Anda dapat menguji ini dengan mudah dengan input yang lebih besar, menggunakan contohnya cukup tambahkan SET @ name = Replicate (@ name, 5000) sebelum panggilan SELECT pos FROM dbo.FindPatternLocation (@name, 'ali'); dan coba hal yang sama dengan prosedur lambat saya.
bummi
15

Saya pikir ini akan sedikit lebih efisien daripada metode perulangan yang Anda pilih ( beberapa bukti di sini ), dan jelas lebih efisien daripada CTE rekursif:

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

Penggunaan sampel:

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

Hasil:

pos
---
  1
 74
113

Jika string Anda lebih panjang dari 2K maka gunakan sys.all_columns sebagai ganti sys.all_objects. Jika lebih dari 8K maka tambahkan tanda silang.

Aaron Bertrand
sumber
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
msi77
sumber
0

Saya suka jawaban Aaron Bertrand. Meskipun saya tidak mengerti sepenuhnya, itu terlihat sangat elegan.

Di masa lalu saya mengalami masalah dengan izin saat menggunakan sys.objects. Dikombinasikan dengan kebutuhan saya untuk memecahkan masalah kode, saya telah membuat variasi pada kode Aaron, dan menambahkannya di bawah.

Ini adalah prosedur saya:

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

The MAX(posid)nilai juga jumlah pertandingan ditemukan.

Nils
sumber
Menjadi bertele-tele, itu tidak terlihat seperti variasi pada kode saya. Sama sekali. :-) Ini persis seperti looping brute force yang saya anjurkan (dan terbukti lebih lambat ).
Aaron Bertrand
0

Ini adalah kode sederhana berdasarkan jawaban Harun yang:

  • Tidak terbatas pada ukuran sys.all_objects
  • Jangan lewatkan 'X' terakhir

KODE:

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

HASIL

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

(8 row(s) affected)
JotaPardo
sumber
Saya percaya saya membahas ukuran sys.all_columns(Anda dapat menggunakan sumber apa pun asalkan mencakup panjang string terpanjang Anda), dan saya juga telah menguji ulang dan saya tidak melihat di mana saya melewatkan 'X' terakhir .. .
Aaron Bertrand
0

Maaf teman-teman terlambat datang, tapi saya ingin mempermudah orang-orang yang ingin memperluas ini. Saya melihat masing-masing implementasi ini, mengambil salah satu yang tampak terbaik bagi saya (Aaron Bertrand), menyederhanakannya dan di sini Anda pergi, Anda memiliki "template". Gunakan dengan bijak.

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

Sama seperti referensi - Anda dapat memperoleh perilaku lain dari ini, seperti memperluas di 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')
OzrenTkalcecKrznaric
sumber
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
Raghu
sumber
Ini akan sangat diuntungkan dari indentasi & casing yang konsisten. Beberapa kata untuk menjelaskan pendekatan dan penerapannya akan sangat membantu.
Michael Green