So finden Sie alle Positionen einer Zeichenfolge in einer anderen Zeichenfolge


11

Wie finde ich alle Positionen patindexin einer Tabelle oder Variablen?

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 

Dies kehrt zurück, 1aber ich möchte alle Ergebnisse, z.

pos
===
  1
 74
113

Antworten:


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

Um es wiederverwendbar zu machen, können Sie es in einer Tabellenfunktion verwenden, um es wie folgt aufzurufen:

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

Die Funktion könnte so aussehen

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

Ich weiß, dass dies eine alte Frage ist, aber ich habe Fragen zur Leistung. Ich habe zwei Funktionen erstellt, die 1in einer Zeichenfolge suchen , die nur Nullen und Einsen enthält. Ich habe Ihre Lösung und @ aaron-bertrand verwendet, aber ich habe die gleichen Ergebnisse und die gleiche Leistung erzielt. Welche Lösung wäre es besser?
Misiu

2
@Misiu wie erwartet Aaron Bertrands Lösungen sind nicht nur eleganter, sondern sogar viel schneller als meine und sollten die akzeptierte Lösung sein. Sie können dies einfach mit einer größeren Eingabe testen. Fügen Sie in seinem Beispiel einfach SET @ name = Replicate (@ name, 5000) hinzu, bevor Sie SELECT pos FROM dbo.FindPatternLocation (@name, 'ali') aufrufen. und versuche das gleiche mit meiner langsamen Prozedur.
Bummi

15

Ich denke, dies ist etwas effizienter als die von Ihnen gewählte Schleifenmethode ( einige Hinweise hier ) und definitiv effizienter als der rekursive CTE:

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

Beispielnutzung:

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

Ergebnisse:

pos
---
  1
 74
113

Wenn Ihre Zeichenfolgen länger als 2 KB sind, verwenden Sie sys.all_columns anstelle von sys.all_objects. Wenn länger als 8 KB, fügen Sie einen Cross-Join hinzu.


2

- Rekursiver CTE

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

Ich liebe Aaron Bertrands Antwort. Obwohl ich es nicht ganz verstehe, sieht es wirklich elegant aus.

In der Vergangenheit bin ich bei der Verwendung auf Probleme mit Berechtigungen gestoßen sys.objects. In Kombination mit der Notwendigkeit, den Code zu beheben, habe ich eine Variation von Aarons Code entwickelt und diese unten hinzugefügt.

Dies ist meine Vorgehensweise:

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

Der MAX(posid)Wert ist auch die Anzahl der gefundenen Übereinstimmungen.


Um pedantisch zu sein, sieht das nicht nach einer Variation meines Codes aus. Überhaupt. :-) Dies ist genau die Art von Brute-Force-Looping, gegen die ich mich ausspreche (und die sich als langsamer erwiesen hat ).
Aaron Bertrand

0

Dies ist ein einfacher Code, der auf Aarons Antwort basiert :

  • Nicht auf die Größe von sys.all_objects beschränkt
  • Verpassen Sie nicht das letzte 'X'

CODE:

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

ERGEBNIS

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

(8 row(s) affected)

Ich glaube, ich habe die Größe von angesprochen sys.all_columns(Sie können jede Quelle verwenden, solange sie die Länge Ihrer längsten Zeichenfolge abdeckt), und ich habe sie erneut getestet und sehe nicht, wo ich das letzte 'X' vermisse. .
Aaron Bertrand

0

Tut mir leid, Leute, die so spät vorbeischauen, aber ich möchte es den Leuten leichter machen, die dies erweitern möchten. Ich habe mir jede dieser Implementierungen angesehen, diejenige genommen, die mir am besten erschien (Aaron Bertrand), sie vereinfacht und los geht's, Sie haben die "Vorlage". Benutze es weise.

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

Nur als Referenz - Sie können daraus andere Verhaltensweisen ableiten, z. B. PATINDEX () erweitern:

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

Dies würde stark von einer konsistenten Einkerbung und Hülle profitieren. Ein paar Worte zur Erläuterung des Ansatzes und der Implementierung würden ebenfalls viel bewirken.
Michael Green
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.