Ich musste eine einfache Abfrage schreiben, in der ich nach Namen von Personen 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 performanter zu werden. Also kann ich das vermeiden or
und / oder like
?
postgresql
performance
index
regular-expression
pattern-matching
Lucas Kauffman
quelle
quelle
s.name
indiziert?name
Wenn Sie ohne führende Platzhalter suchen und keine zusätzlichen Spalten auswählen, kann ein Index hier hilfreich sein, wenn Sie Wert auf Leistung legen.Antworten:
Ihre Anfrage ist so ziemlich das Optimum. Die Syntax wird nicht viel kürzer, die Abfrage wird nicht viel schneller:
Wenn Sie die Syntax wirklich verkürzen möchten , verwenden Sie einen regulären Ausdruck mit Verzweigungen :
Oder etwas schneller mit einer Charakterklasse :
Ein schneller Test ohne Index liefert
SIMILAR TO
für mich in beiden Fällen schnellere Ergebnisse als für .Mit einem geeigneten B-Tree-Index
LIKE
gewinnt dieses Rennen um Größenordnungen.Lesen Sie die Grundlagen zum Pattern Matching im Handbuch .
Index für überlegene Leistung
Wenn Sie sich mit der Leistung befassen, erstellen Sie einen Index für größere Tabellen:
Beschleunigt diese Art der Abfrage um Größenordnungen. Besondere Überlegungen gelten für die länderspezifische Sortierreihenfolge. Weitere Informationen zu Operator-Klassen finden Sie im Handbuch . Wenn Sie das Standardgebietsschema "C" verwenden (die meisten Benutzer tun dies nicht), reicht ein einfacher Index (mit Standardoperatorklasse) aus.
Ein solcher Index eignet sich nur für links verankerte Muster (Abgleich ab dem Anfang der Zeichenfolge).
SIMILAR TO
Auch reguläre Ausdrücke mit einfachen links verankerten Ausdrücken können diesen Index 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.
Übersicht der Mustervergleichsoperatoren
LIKE
(~~
) ist einfach und schnell, aber in seinen Fähigkeiten begrenzt.ILIKE
(~~*
) die case insensitive Variante.pg_trgm erweitert die Indexunterstützung für beide.
~
(Übereinstimmung mit regulären Ausdrücken) ist leistungsfähig, aber komplexer und kann für mehr als nur grundlegende Ausdrücke langsam sein.SIMILAR TO
ist einfach sinnlos . Eine eigenartige Mischform vonLIKE
und regulären Ausdrücken. Ich benutze es nie. Siehe unten.% ist der "Ähnlichkeits" -Operator, der vom Zusatzmodul bereitgestellt wird
pg_trgm
. Siehe unten.@@
ist der Textsuchoperator. Siehe unten.pg_trgm - Trigrammabgleich
Ab PostgreSQL 9.1 können Sie die Erweiterung vereinfachen
pg_trgm
, um Indexunterstützung für anyLIKE
/ILIKE
pattern (und einfache Regexp-Muster mit~
) mithilfe eines GIN- oder GiST-Index bereitzustellen.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öherTextsuche
Ist eine spezielle Art des Mustervergleichs 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.
Der Präfixabgleich wird ebenfalls unterstützt:
Sowie die Phrasensuche seit Postgres 9.6:
Beachten Sie die Einführung im Handbuch und die Übersicht der Bediener und Funktionen .
Zusätzliche Tools für den Fuzzy-String-Abgleich
Das Zusatzmodul fuzzystrmatch bietet einige weitere Optionen, die Leistung ist jedoch generell schlechter als die oben genannten.
Insbesondere können verschiedene Implementierungen der
levenshtein()
Funktion hilfreich sein.Warum sind reguläre Ausdrücke (
~
) immer schneller alsSIMILAR TO
?Die Antwort ist einfach.
SIMILAR TO
Ausdrücke werden intern in reguläre Ausdrücke umgeschrieben. Daher gibt es für jedenSIMILAR TO
Ausdruck mindestens einen schnelleren regulären Ausdruck (der den Aufwand für das Neuschreiben des Ausdrucks erspart). Es gibt keinen Leistungsgewinn bei der VerwendungSIMILAR TO
jemals .Und einfache Ausdrücke, die mit
LIKE
(~~
) gemacht werden können, sindLIKE
sowieso schneller .SIMILAR TO
wird nur in PostgreSQL unterstützt, weil es zu frühen Entwürfen des SQL-Standards geführt hat. Sie haben es immer noch nicht losgeworden. Aber es gibt Pläne, es zu entfernen und stattdessen reguläre Ausdrücke einzuschließen - oder wie ich gehört habe.EXPLAIN ANALYZE
offenbart es. Probieren Sie es einfach mit einem Tisch aus!Enthüllt:
SIMILAR TO
wurde mit einem regulären Ausdruck (~
) umgeschrieben .Ultimative Leistung für diesen speziellen Fall
Aber
EXPLAIN ANALYZE
verrät mehr. Versuchen Sie es mit dem oben genannten Index:Enthüllt:
Intern mit einem Index, der nicht locale-aware (wird
text_pattern_ops
oder mit localeC
) einfach links verankerte Ausdrücke sind mit diesen Textmustern Operatoren neu geschrieben:~>=~
,~<=~
,~>~
,~<~
. Dies ist der Fall für~
,~~
oderSIMILAR TO
gleichermaßen.Gleiches gilt für Indizes zu
varchar
Typen mitvarchar_pattern_ops
oderchar
mitbpchar_pattern_ops
.Auf die ursprüngliche Frage angewendet ist dies der schnellste Weg :
Sollten Sie einmal nach benachbarten Initialen suchen , können Sie dies natürlich weiter vereinfachen:
Der Gewinn gegenüber dem normalen Gebrauch von
~
oder~~
ist winzig. Wenn Leistung nicht Ihre oberste Anforderung ist, sollten Sie sich einfach an die Standardoperatoren halten und zu dem gelangen, was Sie bereits in der Frage haben.quelle
similar
einen Scan umfassen würde, wenn dies der Fall wäre ?EXPLAIN ANALYZE
2 Bitmap-Index-Scans. Mehrere Bitmap-Index-Scans können relativ schnell kombiniert werden.OR
mitUNION ALL
oder ersetztname LIKE 'B%'
mitname >= 'B' AND name <'C'
in Postgres?UNION
werde es nicht tun , aber wenn Sie die Bereiche in einerWHERE
Klausel kombinieren , wird die Abfrage beschleunigt. Ich habe meiner Antwort mehr hinzugefügt. Natürlich müssen Sie Ihr Gebietsschema berücksichtigen. Die Suche nach Gebietsschemas ist immer langsamer.Wie wäre es mit dem Hinzufügen einer Spalte zur Tabelle? Abhängig von Ihren tatsächlichen Anforderungen:
PostgreSQL unterstützt keine berechneten Spalten in Basistabellen nach SQL Server , die neue Spalte kann jedoch über einen Trigger verwaltet werden. Offensichtlich würde diese neue Spalte indiziert werden.
Alternativ würde ein Index für einen Ausdruck dasselbe ergeben, das billiger ist. Z.B:
Abfragen, die dem Ausdruck in ihren Bedingungen entsprechen, können diesen Index verwenden.
Auf diese Weise wird der Leistungstreffer beim Erstellen oder Ändern der Daten erfasst, sodass er möglicherweise nur für Umgebungen mit geringer Aktivität geeignet ist (dh viel weniger Schreibvorgänge als Lesevorgänge).
quelle
Du könntest es versuchen
Ich habe keine Ahnung, ob die oben genannten oder Ihre ursprünglichen Ausdrücke in Postgres wiedergegeben werden können.
Wenn Sie den vorgeschlagenen Index erstellen, wären Sie auch interessiert zu erfahren, wie sich dieser mit den anderen Optionen vergleicht.
quelle
Was ich in der Vergangenheit getan habe, ist angesichts eines ähnlichen Leistungsproblems, das ASCII-Zeichen des letzten Buchstabens zu erhöhen und ein ZWISCHEN auszuführen. Sie erhalten dann die beste Leistung für eine Teilmenge der LIKE-Funktionalität. Natürlich funktioniert es nur in bestimmten Situationen, aber bei extrem großen Datenmengen, in denen Sie beispielsweise nach einem Namen suchen, wird die Leistung von miserabel auf akzeptabel erhöht.
quelle
Sehr alte Frage, aber ich habe eine andere schnelle Lösung für dieses Problem gefunden:
Da die Funktion ascii () nur das erste Zeichen der Zeichenkette betrachtet.
quelle
(name)
?Zur Überprüfung der Initialen verwende ich oft das Casting to
"char"
(mit den doppelten Anführungszeichen). Es ist nicht tragbar, aber sehr schnell. Intern wird der Text einfach entfremdet und das erste Zeichen zurückgegeben, und "char" -Vergleichsoperationen sind sehr schnell, da der Typ eine feste Länge von 1 Byte hat:Beachten Sie, dass das Casting in
"char"
schneller ist als dieascii()
Slution von @ Sole021, aber nicht UTF8-kompatibel ist (oder eine andere Codierung). Es wird lediglich das erste Byte zurückgegeben. Daher sollte es nur in Fällen verwendet werden, in denen der Vergleich mit der einfachen alten 7 erfolgt -Bit ASCII-Zeichen.quelle
Es gibt zwei Methoden, die noch nicht erwähnt wurden, um mit solchen Fällen umzugehen:
partieller (oder partitionierter - wenn für den gesamten Bereich manuell erstellt) Index - am nützlichsten, wenn nur eine Teilmenge der Daten erforderlich ist (z. B. während einer Wartung oder vorübergehend für einige Berichte):
Partitionierung 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) erwägenswert.
Wenn die Daten in einer Tabelle sortiert sind, kann der BRIN-Index (über dem ersten Zeichen) verwendet werden.
quelle
Wahrscheinlich schneller, um einen Einzelzeichenvergleich durchzuführen:
quelle
column LIKE 'B%'
Dies ist effizienter als die Verwendung der Teilzeichenfolgenfunktion für die Spalte.