Frage

Ich musste eine einfache Frage schreiben, bei der ich nach den Namen der Leute suche, die mit einem B oder einem D beginnen:

SELECT s.name 
FROM spelers s 
WHERE s.name LIKE 'B%' OR s.name LIKE 'D%'
ORDER BY 1

Ich habe mich gefragt, ob es eine Möglichkeit gibt, dies neu zu schreiben, um leistungsfähiger zu werden. So kann ich vermeiden or und / oder like?

War es hilfreich?

Lösung

Ihre Abfrage ist so ziemlich das Optimum. Syntax wird nicht viel kürzer, die Abfrage wird nicht viel schneller:

SELECT name
FROM   spelers
WHERE  name LIKE 'B%' OR name LIKE 'D%'
ORDER  BY 1;

Wenn Sie wirklich wollen Verkürzen Sie die Syntax, Verwenden Sie einen regulären Ausdruck mit Geäst:

...
WHERE  name ~ '^(B|D).*'

Oder etwas schneller mit a Charakterklasse:

...
WHERE  name ~ '^[BD].*'

Ein kurzer Test ohne Index liefert schnellere Ergebnisse als für SIMILAR TO in beiden Fällen für mich.
Mit einem geeigneten B-Tree-Index, an Ort und Stelle, LIKE gewinnt diese Rasse um Größenordnungen.

Lesen Sie die Grundlagen über Musteranpassung im Handbuch.

Index für überlegene Leistung

Wenn Sie sich mit der Leistung befassen, erstellen Sie einen Index wie diesen für größere Tabellen:

CREATE INDEX spelers_name_special_idx ON spelers (name text_pattern_ops);

Macht diese Art von Abfrage durch Größenordnungen schneller. Besondere Überlegungen gelten für die ortsschemasspezifische Sortierreihenfolge. Lesen Sie mehr über Bedienungsklassen im Handbuch. Wenn Sie das Standard "C" -Donstätsschema verwenden (die meisten Personen nicht), wird ein einfacher Index (mit Standard -Operator -Klasse) dies tun.

Ein solcher Index ist nur gut für links verankerte Muster (abgestimmt vom Beginn der Zeichenfolge).

SIMILAR TO oder regelmäßige Ausdrücke mit grundlegenden links verankerten Ausdrücken können diesen Index auch verwenden. Aber nicht mit Zweigen (B|D) oder Charakterklassen [BD] (Zumindest in meinen Tests auf PostgreSQL 9.0).

Trigram übereinstimmt oder Textsuche verwendet spezielle Gin- oder GIST -Indizes.

Überblick über die Musteranpassungsoperatoren

  • LIKE (~~) ist einfach und schnell, aber in seinen Fähigkeiten begrenzt.
    ILIKE (~~*) die Fallunempfindlichkeitsvariante.
    PG_TRGM erweitert die Indexunterstützung für beide.

  • ~ (reguläre Ausdrucksübereinstimmung) ist leistungsstark, aber komplexer und kann für alles mehr als grundlegende Ausdrücke sein.

  • SIMILAR TO ist nur zwecklos. Ein eigenartiges Halbbreed von LIKE und regelmäßige Ausdrücke. Ich benutze es nie. Siehe unten.

  • % ist der "Ähnlichkeits" -Operator, der vom zusätzlichen Modul bereitgestellt wird pg_trgm. Siehe unten.

  • @@ ist der Textsuchbetreiber. Siehe unten.

PG_TRGM - Trigramm Matching

Mit ... anfangen PostgreSQL 9.1 Sie können die Erweiterung erleichtern pg_trgm zur Bereitstellung einer Indexunterstützung für irgendein LIKE / ILIKE Muster (und einfache Regexp -Muster mit ~) Verwenden eines Gin- oder Gistindex.

Details, Beispiel und Links:

pg_trgm stellt ebenfalls zur Verfügung diese Operatoren:

  • % - Der "Ähnlichkeits" -Operator
  • <% (Kommutator: %>) - Der Operator "Word_similarity" in Postgres 9.6 oder höher
  • <<% (Kommutator: %>>) - Der Operator "strict_word_similarity" in Postgres 11 oder später

Textsuche

Ist eine spezielle Art von Musteranpassung mit separaten Infrastruktur- und Indextypen. Es verwendet Wörterbücher und Stamm und ist ein großartiges Werkzeug, um Wörter in Dokumenten zu finden, insbesondere für natürliche Sprachen.

Präfix -Matching wird auch unterstützt:

Ebenso gut wie Phrasensuche Seit Postgres 9.6:

Bedenke die Einführung in das Handbuch und die Überblick über Operatoren und Funktionen.

Zusätzliche Tools für die Fuzzy -String -Matching

Das zusätzliche Modul FuzzyStrMatch Bietet einige weitere Optionen, aber die Leistung ist all dem oben genannten im Allgemeinen unterlegen.

Insbesondere verschiedene Implementierungen der levenshtein() Funktion kann instrumental sein.

Warum sind reguläre Ausdrücke (~) immer schneller als SIMILAR TO?

Die Antwort ist einfach. SIMILAR TO Ausdrücke werden intern in regelmäßige Ausdrücke umwandelt. Also für jeden SIMILAR TO Ausdruck gibt es wenigstens Ein schnellerer regulärer Ausdruck (der den Overhead der Umschreibung des Ausdrucks rettet). Es gibt keinen Leistungsgewinn bei der Verwendung SIMILAR TO je.

Und einfache Ausdrücke, die mit LIKE (~~) sind schneller mit LIKE ohnehin.

SIMILAR TO wird nur in PostgreSQL unterstützt, da es in frühen Entwürfen des SQL -Standards gelandet ist. Sie haben es immer noch nicht losgeworden. Aber es gibt Pläne, es zu entfernen und stattdessen Regexp -Übereinstimmungen einzuschließen - oder so habe ich gehört.

EXPLAIN ANALYZE enthüllt es. Versuchen Sie es einfach selbst mit jedem Tisch!

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name SIMILAR TO 'B%';

Enthüllt:

...  
Seq Scan on spelers  (cost= ...  
  Filter: (name ~ '^(?:B.*)$'::text)

SIMILAR TO wurde mit einem regelmäßigen Ausdruck umgeschrieben (~).

Endgültige Leistung für diesen speziellen Fall

Aber EXPLAIN ANALYZE enthüllt mehr. Versuchen Sie es mit dem oben erwähnten Index:

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name ~ '^B.*;

Enthüllt:

...
 ->  Bitmap Heap Scan on spelers  (cost= ...
       Filter: (name ~ '^B.*'::text)
        ->  Bitmap Index Scan on spelers_name_text_pattern_ops_idx (cost= ...
              Index Cond: ((prod ~>=~ 'B'::text) AND (prod ~<~ 'C'::text))

Intern, mit einem Index, der sich nicht für das Gebietsschema kennt (text_pattern_ops oder Gebietsschema C) Einfache links verankerte Ausdrücke werden mit diesen Textmusteroperatoren umgeschrieben: ~>=~, ~<=~, ~>~, ~<~. Dies ist der Fall für ~, ~~ oder SIMILAR TO wie.

Gleiches gilt für Indizes auf varchar Typen mit varchar_pattern_ops oder char mit bpchar_pattern_ops.

Also, auf die ursprüngliche Frage angewendet, ist dies die schnellstmöglicher Weg:

SELECT name
FROM   spelers  
WHERE  name ~>=~ 'B' AND name ~<~ 'C'
    OR name ~>=~ 'D' AND name ~<~ 'E'
ORDER  BY 1;

Natürlich, wenn Sie nach suchen sollten benachbarte Initialen, Sie können weiter vereinfachen:

WHERE  name ~>=~ 'B' AND name ~<~ 'D'   -- strings starting with B or C

Der Gewinn über einfachen Gebrauch von ~ oder ~~ ist winzig. Wenn die Leistung nicht Ihre größte Anforderung ist, sollten Sie sich nur bei den Standardbetreibern halten - Sie kommen zu dem, was Sie bereits in der Frage haben.

Andere Tipps

Wie wäre es mit einer Spalte zur Tabelle. Abhängig von Ihren tatsächlichen Anforderungen:

person_name_start_with_B_or_D (Boolean)

person_name_start_with_char CHAR(1)

person_name_start_with VARCHAR(30)

PostgreSQL unterstützt nicht Berechnete Spalten in Basistabellen a la SQL Server Die neue Spalte kann jedoch über Trigger aufrechterhalten werden. Offensichtlich würde diese neue Spalte indiziert.

Alternativ an Index für einen Ausdruck Würde dir das gleiche geben, billiger. Z.B:

CREATE INDEX spelers_name_initial_idx ON spelers (left(name, 1)); 

Abfragen, die mit dem Ausdruck unter ihren Bedingungen übereinstimmen, können diesen Index verwenden.

Auf diese Weise wird der Leistungsschlag aufgenommen, wenn die Daten erstellt oder geändert werden. Daher ist möglicherweise nur für eine Umgebung mit niedriger Aktivität geeignet (dh viel weniger Schreibvorgänge als Lesevorgänge).

Sie könnten Versuchen

SELECT s.name
FROM   spelers s
WHERE  s.name SIMILAR TO '(B|D)%' 
ORDER  BY s.name

Ich habe keine Ahnung, ob das obige oder Ihr ursprünglicher Ausdruck in Postgres sargierbar ist oder nicht.

Wenn Sie den vorgeschlagenen Index erstellen, wären Sie auch interessiert zu hören, wie sich dies mit den anderen Optionen vergleicht.

SELECT name
FROM   spelers
WHERE  name >= 'B' AND name < 'C'
UNION ALL
SELECT name
FROM   spelers
WHERE  name >= 'D' AND name < 'E'
ORDER  BY name

Was ich in der Vergangenheit getan habe, hat sich mit einem ähnlichen Leistungsproblem konfrontiert, ist, den ASCII -Charakter des letzten Buchstabens zu erhöhen und einen dazwischen zu tun. Sie erhalten dann die beste Leistung für eine Teilmenge der gleichen Funktionen. Natürlich funktioniert es nur in bestimmten Situationen, aber für ultra-große Datensätze, in denen Sie beispielsweise nach einem Namen suchen, macht es die Leistung von abgründiger zu akzeptabel.

Sehr alte Frage, aber ich fand eine weitere schnelle Lösung für dieses Problem:

SELECT s.name 
FROM spelers s 
WHERE ascii(s.name) in (ascii('B'),ascii('D'))
ORDER BY 1

Da function ascii () nur beim ersten Zeichen der Zeichenfolge schaut.

Zur Überprüfung der Initialen benutze ich oft Casting zu Casting "char" (mit den Doppelzitaten). Es ist nicht tragbar, aber sehr schnell. Innen wird einfach der Text genommen und gibt den ersten Charakter zurück, und "char" -Vergleichvorgänge sind sehr schnell, da der Typ 1-Byte-Länge ist:

SELECT s.name 
FROM spelers s 
WHERE s.name::"char" =ANY( ARRAY[ "char" 'B', 'D' ] )
ORDER BY 1

Beachten Sie, dass das Casting auf "char" ist schneller als die ascii() Slution von @sole021, aber es ist nicht UTF8-kompatibel (oder eine andere Codierung in dieser Angelegenheit), die einfach das erste Byte zurückgibt. Daher sollte nur in Fällen verwendet werden, in denen der Vergleich gegen einfache alte 7-Bit-ASCII-Zeichen vorliegt.

Für den Umgang mit solchen Fällen werden noch zwei Methoden erwähnt:

  1. Teilweise (oder partitioniert - falls manuell für den vollständigen Bereich erstellt) Index - am nützlichsten, wenn nur eine Teilmenge von Daten erforderlich ist (z. B. während einiger Wartung oder vorübergehend für einige Berichterstattung):

    CREATE INDEX ON spelers WHERE name LIKE 'B%'
    
  2. Partitionierung der Tabelle selbst (mit dem ersten Charakter als Partitionierungsschlüssel) - diese Technik ist besonders in Bezug auf PostgreSQL 10+ (weniger schmerzhafte Partitionierung) und 11+ (Partitions -Beschneidung während der Abfrageausführung) berücksichtigt.

Wenn die Daten in einer Tabelle sortiert sind, kann man außerdem von der Verwendung profitieren Brin -Index (über den ersten Charakter).

Wahrscheinlich schneller, um einen einzelnen Charaktervergleich durchzuführen:

SUBSTR(s.name,1,1)='B' OR SUBSTR(s.name,1,1)='D'
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit dba.stackexchange
scroll top