Ich musste eine einfache Abfrage schreiben, in der ich nach dem Namen der Leute suchte, 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 umzuschreiben, um performanter zu werden. Also kann ich or
und/oder like
vermeiden?
Ihre Anfrage ist so ziemlich das Optimum. Die 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 die Syntax verkürzen möchten, verwenden Sie einen regulären Ausdruck mit Zweigen:
...
WHERE name ~ '^(B|D).*'
Oder etwas schneller mit einer Zeichenklasse:
...
WHERE name ~ '^[BD].*'
Ein schneller Test ohne Index liefert für mich in beiden Fällen schnellere Ergebnisse als für SIMILAR TO
.
Mit einem geeigneten B-Tree-Index gewinnt LIKE
dieses Rennen um Größenordnungen.
Lesen Sie die Grundlagen zu Mustervergleich im Handbuch .
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);
Beschleunigt diese Art der Abfrage um Größenordnungen. Besondere Überlegungen gelten für die länderspezifische Sortierreihenfolge. Lesen Sie mehr über Operatorklassen im Handbuch . Wenn Sie das Standardgebietsschema "C" verwenden (die meisten Leute tun dies nicht), reicht ein einfacher Index (mit Standardoperatorklasse) aus.
Ein solcher Index ist nur für links verankerte Muster geeignet (Übereinstimmung vom Anfang der Zeichenfolge an).
SIMILAR TO
Oder reguläre Ausdrücke mit einfachen linksverankerten Ausdrücken können diesen Index ebenfalls verwenden. Aber nicht mit Zweigen (B|D)
Oder Zeichenklassen [BD]
(Zumindest in meinen Tests unter PostgreSQL 9.0).
Trigrammübereinstimmungen oder Textsuche verwenden spezielle GIN- oder Gist-Indizes.
LIKE
(~~
) ist einfach und schnell, aber in seinen Fähigkeiten begrenzt.ILIKE
(~~*
) die Variante ohne Berücksichtigung der Groß- und Kleinschreibung.
pg_trgm erweitert die Indexunterstützung für beide.
~
(Übereinstimmung mit regulären Ausdrücken) ist mächtig, aber komplexer und kann für alles andere als einfach langsam sein Ausdrücke.
SIMILAR TO
ist nur sinnlos. Eine eigenartige Mischling aus LIKE
und regulären Ausdrücken. Ich benutze es nie. Siehe unten.
% ist der "Ähnlichkeits" -Operator, der vom zusätzlichen Modul pg_trgm
bereitgestellt wird. Siehe unten.
@@
ist der Textsuchoperator. Siehe unten.
Beginnend mit PostgreSQL 9.1 können Sie die Erweiterung pg_trgm
vereinfachen, um Indexunterstützung für any bereitzustellen LIKE
/ILIKE
Muster (und einfache Regexp-Muster mit ~
) unter Verwendung eines GIN- oder Gist-Index.
Details, Beispiel und Links:
pg_trgm
Bietet auch diese Operatoren :
%
- der Operator "Ähnlichkeit"<%
(Kommutator: %>
) - der Operator "Word_similarity" in Postgres 9.6 oder höher<<%
(Kommutator: %>>
) - der Operator "strict_Word_similarity" in Postgres 11 oder höherIst eine spezielle Art der Musterübereinstimmung mit separaten Infrastruktur- und Indextypen. Es verwendet Wörterbücher und Stemming und ist ein großartiges Werkzeug, um Wörter in Dokumenten zu finden, insbesondere für natürliche Sprachen.
Präfixabgleich wird ebenfalls unterstützt:
Sowie Phrasensuche seit Postgres 9.6:
Betrachten Sie das Einführung im Handbuch und das Übersicht über Operatoren und Funktionen .
Das zusätzliche Modul fuzzystrmatch bietet einige weitere Optionen, aber die Leistung ist im Allgemeinen allen oben genannten unterlegen.
Insbesondere können verschiedene Implementierungen der Funktion levenshtein()
instrumentell sein.
~
) Immer schneller als SIMILAR TO
?Die Antwort ist einfach. SIMILAR TO
Ausdrücke werden intern in reguläre Ausdrücke umgeschrieben. Für jeden SIMILAR TO
- Ausdruck gibt es also mindestens einen schnelleren regulären Ausdruck (wodurch der Aufwand für das Umschreiben des Ausdrucks gespart wird). Es gibt keinen Leistungsgewinn bei der Verwendung von SIMILAR TO
jemals .
Und einfache Ausdrücke, die mit LIKE
(~~
) Ausgeführt werden können, sind mit LIKE
sowieso schneller.
SIMILAR TO
Wird nur in PostgreSQL unterstützt, da es in frühen Entwürfen des SQL-Standards landete. Sie haben es immer noch nicht losgeworden. Aber es gibt Pläne, es zu entfernen und stattdessen Regexp-Übereinstimmungen einzuschließen - so hörte ich.
EXPLAIN ANALYZE
Zeigt es. Versuchen Sie es einfach mit jedem Tisch selbst!
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 regulären Ausdruck (~
) Umgeschrieben.
Aber EXPLAIN ANALYZE
Enthüllt mehr. Versuchen Sie es mit dem oben genannten 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 werden mit einem Index, der das Gebietsschema nicht kennt (text_pattern_ops
Oder das Gebietsschema C
verwendet), einfache linksverankerte Ausdrücke mit den folgenden Textmusteroperatoren neu geschrieben: ~>=~
, ~<=~
, ~>~
, ~<~
. Dies gilt gleichermaßen für ~
, ~~
Oder SIMILAR TO
.
Gleiches gilt für Indizes für varchar
Typen mit varchar_pattern_ops
Oder char
mit bpchar_pattern_ops
.
Auf die ursprüngliche Frage angewendet ist dies der schnellstmögliche Weg :
SELECT name
FROM spelers
WHERE name ~>=~ 'B' AND name ~<~ 'C'
OR name ~>=~ 'D' AND name ~<~ 'E'
ORDER BY 1;
Wenn Sie zufällig nach benachbarten Initialen suchen, können Sie dies natürlich weiter vereinfachen:
WHERE name ~>=~ 'B' AND name ~<~ 'D' -- strings starting with B or C
Der Gewinn gegenüber der einfachen Verwendung von ~
Oder ~~
Ist gering. Wenn Leistung nicht Ihre vorrangige Anforderung ist, sollten Sie sich nur an die Standardoperatoren halten und zu dem gelangen, was Sie bereits in der Frage haben.
Wie wäre es, wenn Sie der Tabelle eine Spalte hinzufügen? 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 , aber die neue Spalte kann über den Trigger verwaltet werden. Offensichtlich würde diese neue Spalte indiziert.
Alternativ würde ein Index für einen Ausdruck Ihnen das gleiche geben, billiger. Z.B.:
CREATE INDEX spelers_name_initial_idx ON spelers (left(name, 1));
Abfragen, die dem Ausdruck in ihren Bedingungen entsprechen, können diesen Index verwenden.
Auf diese Weise wird der Leistungseinbruch erzielt, wenn die Daten erstellt oder geändert werden. Dies ist möglicherweise nur für eine Umgebung mit geringer Aktivität geeignet (d. H. 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 der obige oder Ihr ursprünglicher Ausdruck in Postgres sarkierbar sind oder nicht.
Wenn Sie den vorgeschlagenen Index erstellen, würde es Sie auch interessieren, wie dieser mit den anderen Optionen verglichen wird.
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
Zur Überprüfung der Initialen verwende ich häufig das Casting in "char"
(Mit doppelten Anführungszeichen). Es ist nicht tragbar, aber sehr schnell. Intern wird der Text einfach entgast und das erste Zeichen zurückgegeben. Die Vergleichsoperationen "char" sind sehr schnell, da der Typ eine feste Länge von 1 Byte hat:
SELECT s.name
FROM spelers s
WHERE s.name::"char" =ANY( ARRAY[ "char" 'B', 'D' ] )
ORDER BY 1
Beachten Sie, dass das Casting in "char"
Schneller ist als die ascii()
- Slution von @ Sole021, aber nicht UTF8-kompatibel ist (oder eine andere Codierung für diese Angelegenheit) und einfach das erste Byte zurückgibt Nur in Fällen verwenden, in denen der Vergleich mit einfachen alten 7-Bit-Zeichen ASCII Zeichen) erfolgt.
Sehr alte Frage, aber ich habe eine andere schnelle Lösung für dieses Problem gefunden:
SELECT s.name
FROM spelers s
WHERE ascii(s.name) in (ascii('B'),ascii('D'))
ORDER BY 1
Da die Funktion ascii () nur das erste Zeichen der Zeichenfolge betrachtet.
Was ich in der Vergangenheit angesichts eines ähnlichen Leistungsproblems getan habe, ist, das Zeichen ASCII des letzten Buchstabens) zu erhöhen und ZWISCHEN zu machen. Sie erhalten dann die beste Leistung für eine Teilmenge Natürlich funktioniert es nur in bestimmten Situationen, aber bei extrem großen Datenmengen, bei denen Sie beispielsweise nach einem Namen suchen, wird die Leistung von miserabel zu akzeptabel.
Es gibt zwei Methoden, die für solche Fälle noch nicht erwähnt wurden:
teilindex (oder partitioniert - wenn manuell für den gesamten Bereich erstellt) - am nützlichsten, wenn nur eine Teilmenge der Daten erforderlich ist (z. B. während einer Wartung oder vorübergehend für eine Berichterstellung):
CREATE INDEX ON spelers WHERE name LIKE 'B%'
partitionieren der Tabelle selbst (unter Verwendung des ersten Zeichens als Partitionierungsschlüssel) - Diese Technik ist besonders in PostgreSQL 10+ (weniger schmerzhafte Partitionierung) und 11+ (Partitionsbereinigung während der Abfrageausführung) zu berücksichtigen.
Wenn die Daten in einer Tabelle sortiert sind, kann außerdem die Verwendung von BRIN-Index (über dem ersten Zeichen) genutzt werden.