2017-02-05 4 views
4

Ich speichere Zeichenfolge Präfixe in meiner SQL Server-Tabelle, ich möchte sehen, ob einer dieser Werte ein gültiges Präfix für einen bestimmten Parameterwert ist.Übereinstimmende Spaltenwerte als Präfixe

z.B. angenommen, ich habe eine Telefon Do-Not-Call-Liste und es enthält einen Eintrag zu verbieten alle Anrufe auf Nummern beginnend mit "1425123", anstatt 10000 Nummern einfügen (14251230000 zu 14251239999) es speichert das Präfix statt.

Wie so:

CREATE TABLE Prefixes (
    Value varchar(10) 
) 

CREATE INDEX IX_Value UNIQUE Prefixes (Value) 

wie so Ausgewertet:

DECLARE @value varchar(10) = 'foobar' 

SELECT 
    * 
FROM 
    Prefixes 
WHERE 
    @value LIKE (Value + '%'); 

Wenn ich dies in Azure SQL in SQL Server Management Studio sagt, es es einen Index Scan ist die Durchführung. Bei ungefähr 70.000 Einträgen in einer Azure SQL S1-Datenbank dauert die Abfrage zwischen 200 und 500 ms. Das Tool schlägt keine Verbesserung der Indizes für eine schnellere Leistung vor.

Zum Vergleich verwendet eine exakte Gleichheitsübereinstimmung (Value = @value) einen Index Seek und passiert fast sofort.

200-500ms ist zu langsam für meine Anwendung.

Eine Möglichkeit ist, den Nachschlag in meinen Anwendungscode unter Verwendung einen Trie für eine effiziente Präfixsuche (das führt Synchronisationsprobleme) zu bewegen, aber ein anderer Ansatz ist die Abfrage, so etwas zu ändern:

DECLARE @v1 varchar(1) = LEFT(@value, 1) 
DECLARE @v2 varchar(2) = LEFT(@value, 2) 
DECLARE @v3 varchar(3) = LEFT(@value, 3) 
DECLARE @v4 varchar(4) = LEFT(@value, 4) 
DECLARE @v5 varchar(5) = LEFT(@value, 5) 
DECLARE @v6 varchar(6) = LEFT(@value, 6) 
DECLARE @v7 varchar(7) = LEFT(@value, 7) 
DECLARE @v8 varchar(8) = LEFT(@value, 8) 
DECLARE @v9 varchar(9) = LEFT(@value, 9) 

SELECT 
    * 
FROM 
    Prefixes 
WHERE 
    Value = @v1 OR 
    Value = @v2 OR 
    Value = @v3 OR 
    Value = @v4 OR 
    Value = @v5 OR 
    Value = @v6 OR 
    Value = @v7 OR 
    Value = @v8 OR 
    Value = @v9 

Wenn ich das mache, ist es viel schneller (mit einem Index Seek), aber es fühlt sich an wie ein Hack, aber weil ich weiß, dass die Länge weniger als 10 Zeichen beträgt, bin ich damit einverstanden ... vorerst.

Gibt es einen besseren Weg? Gibt es eine Möglichkeit, wie SQL Server mein Präfix intern abgleichen kann (d. H. Die gleiche Logik in meinem letzten Beispiel verwenden, aber kein repetitives und sprödes SQL verwenden)?

+1

Ist 70.000 Einträge wirklich realistisch? Wie viele davon sind vollständige Nummern und wie viele sind Präfixe. Wie viele Einträge sind redundant? Z.B. Wenn Präfixe "1" enthalten, dann könnte es Hunderte oder sogar Tausende von Einträgen geben, die mit "1" beginnen, die ignoriert würden, weil "1" bereits eine Übereinstimmung für "14251230000" ist. –

Antwort

1

Der Grund, warum Ihre erste Option langsam ist, ist, dass es nicht sargable ist, weil Sie Prefixes.Value in Ihrer WHERE-Klausel ändern.

Als Ergebnis ist es unmöglich, den Index zu nutzen.

Ihre vorgeschlagene Lösung ist in Ordnung (obwohl Sie ein Präfix der Länge 10 fehlt).

Das einzige, was ich darauf hinweisen würde ist, dass Sie sicherlich lieber eine EXISTS Abfrage verwenden? Sobald Sie ein einziges Spiel gefunden haben, sind Sie fertig. keine Notwendigkeit, mehr zu finden. Auch IN ist mehr Succint.

I.e.

IF EXISTS (
    SELECT * 
    FROM Prefixes 
    WHERE Value IN (@v1, @v2, ...) 
) 
    RETURN 1 
ELSE 
    RETURN 0 

PS Wenn es wirklich wichtig ist, könnten Sie in aussehen Full Text Indexing verwenden. (Leider habe ich es nie selbst benutzt, also kann ich nicht weiter helfen.) Ich weiß, es ist mehr Arbeit, aber vielleicht auch gerechtfertigt. Früher musste ein zusätzlicher Dienst ausgeführt werden. und ich weiß nicht, ob das immer noch so ist.


EDIT

Borrowing von Dudu Markovitz's idea, die immer noch, wenn beispielsweise ineffizient wäre:

  • @Value = '9999999999' und passen keine Präfixe.
  • Der Grund ist, dass alle Prefixes.Value < '9999999999'.
  • Aber keiner passt zum Filter @value like value + '%'.
  • Also müsste die Abfrage immer noch alle Zeilen scannen.

aber ich dies tun denken kann (mit einiger zwickt), wenn diese Streichhölzer @value like value + '%' speziell von immer bekommen die erstevalue < @value und dann Überprüfung effizienter gemacht werden. Der Haken ist, dass Sie die erste Garantie benötigen Prefixes enthält keine "redundanten" Werte (oder zumindest, dass redundante Werte können einfach mit einem Flag ausgefiltert werden).

Durch redundante meine ich jede Value das würde sich nicht gültig sein, weil es mit einem bestehenden kürzeren Präfix beginnt.

Sie könnten dann die folgende Abfrage verwenden.

SELECT * 
FROM (
     SELECT TOP 1 Value as PossiblePrefix 
     FROM Prefixes 
       /* WHERE can leverage index; 
        but requires NO redundant Prefixes.Value rows 
        so that it returns only ONE possible prefix that 
        has a chance of matching @Value.*/ 
     WHERE Value <= @Value 
     ORDER BY Value DESC 
     ) pp 
WHERE @Value LIKE pp.PosisblePrefix + '%' 

Wenn der Optimierer den richtigen Index wählen ausfällt, dies ist eines der seltenen Situationen würde ich einen Indexhinweis befürworten würde verwenden.

3

Dies ist etwas, eine Hilfsnummer Tabelle kann mit helfen.

Da Sie nur 1-10 benötigen, habe ich eine Inline in der Abfrage gemacht, anstatt davon auszugehen, dass eine existiert.

Sie können den Code verkürzen, indem Sie die abgeleitete Tabelle V durch eine Referenz auf eine Tabelle mit festen Nummern ersetzen, wenn Sie eine haben oder eine erstellen können.

SELECT IIF(EXISTS (SELECT * 
        FROM (VALUES(1),(2),(3), 
           (4),(5),(6), 
           (7),(8),(9),(10) 
         ) V(number) 
          JOIN Prefixes P WITH(FORCESEEK) 
          ON P.Value = LEFT(@value, number) 
        WHERE number <= LEN(@value)), 1, 0) AS PrefixExists 

enter image description here

|--Compute Scalar(DEFINE:([Expr1014]=CASE WHEN [Expr1015] THEN (1) ELSE (0) END)) 
     |--Nested Loops(Left Semi Join, DEFINE:([Expr1015] = [PROBE VALUE])) 
      |--Constant Scan 
      |--Nested Loops(Inner Join, OUTER REFERENCES:([Union1010])) 
       |--Filter(WHERE:([Union1010]<=len([@value]))) 
       | |--Constant Scan(VALUES:(((1)),((2)),((3)),((4)),((5)),((6)),((7)),((8)),((9)),((10)))) 
       |--Index Seek(OBJECT:([tempdb].[dbo].[Prefixes].[IX_Value] AS [P]), SEEK:([P].[Value]=substring([@value],(1),[Union1010])) ORDERED FORWARD) 
+0

Schön! Darf ich vorschlagen, 'set showplan_text on' für einen nützlicheren Abfrageplan als das Bild zu verwenden? –

Verwandte Themen