Ich habe zwei Tische.
Das erste ist eine Tabelle mit Präfixen
code name price
343 ek1 10
3435 nt 4
3432 ek2 2
An zweiter Stelle stehen Anruflisten mit Telefonnummern
number time
834353212 10
834321242 20
834312345 30
Ich muss ein Skript schreiben, das das längste Präfix aus den Präfixen für jeden Datensatz findet, und all diese Daten wie folgt in die dritte Tabelle schreiben:
number code ....
834353212 3435
834321242 3432
834312345 343
Für die Nummer 834353212 müssen wir '8' kürzen und dann den längsten Code aus der Präfixtabelle finden, nämlich 3435.
Wir müssen immer zuerst '8' löschen und das Präfix muss am Anfang stehen.
Ich habe diese Aufgabe vor langer Zeit auf sehr schlechte Weise gelöst. Es war ein schreckliches Perl-Skript, das viele Abfragen für jeden Datensatz ausführt. Dieses Skript:
Nehmen Sie eine Nummer aus der Aufruftabelle und führen Sie eine Teilzeichenfolge von Länge (Nummer) bis 1 => $ Präfix in der Schleife aus
Führen Sie die Abfrage aus: Wählen Sie count (*) aus Präfixen aus, wobei Code wie '$ prefix'
- Wenn count> 0 ist, nehmen Sie die ersten Präfixe und schreiben Sie in die Tabelle
Das erste Problem ist die Anzahl der Abfragen call_records * length(number)
. Das zweite Problem sind LIKE
Ausdrücke. Ich fürchte, die sind langsam.
Ich habe versucht, das zweite Problem zu lösen, indem ich:
CREATE EXTENSION pg_trgm;
CREATE INDEX prefix_idx ON prefix USING gist (code gist_trgm_ops);
Das beschleunigt jede Abfrage, hat aber das Problem im Allgemeinen nicht gelöst.
Ich habe 20k Präfixe und 170k Zahlen jetzt, und meine alte Lösung ist schlecht. Sieht so aus, als ob ich eine neue Lösung ohne Schleifen brauche.
Nur eine Abfrage für jeden Anrufdatensatz oder ähnliches.
quelle
code
in der ersten Tabelle das gleiche Präfix wie später steht. Könnten Sie es bitte klarstellen? Eine Korrektur der Beispieldaten und der gewünschten Ausgabe (damit Sie Ihrem Problem leichter folgen können) ist ebenfalls willkommen.Antworten:
Ich gehe von einem Datentyp
text
für die relevanten Spalten aus."Einfache" Lösung
Schlüsselelemente:
DISTINCT ON
ist eine Postgres-Erweiterung des SQL-StandardsDISTINCT
. Eine ausführliche Erklärung für die verwendete Abfragetechnik finden Sie in dieser Antwort auf SO .ORDER BY p.code DESC
wählt die längste Übereinstimmung aus, da'1234'
nach'123'
(in aufsteigender Reihenfolge) sortiert wird .Einfache SQL-Geige .
Ohne Index würde die Abfrage sehr lange ausgeführt (ich habe nicht darauf gewartet, dass sie beendet wird). Um dies schnell zu machen, benötigen Sie Indexunterstützung. Die von Ihnen erwähnten Trigrammindizes, die vom Zusatzmodul bereitgestellt werden,
pg_trgm
sind ein guter Kandidat. Sie müssen zwischen GIN und GiST Index wählen. Das erste Zeichen der Zahlen ist nur Rauschen und kann aus dem Index ausgeschlossen werden, wodurch es zusätzlich zu einem Funktionsindex wird.In meinen Tests hat ein funktionaler Trigramm-GIN-Index das Rennen über einen Trigramm-GiST-Index gewonnen (wie erwartet):
Fortgeschrittene dbfiddle hier .
Alle Testergebnisse stammen aus einer lokalen Postgres 9.1-Testinstallation mit einem reduzierten Setup: 17.000 Nummern und 2.000 Codes:
Noch viel schneller
Fehlgeschlagener Versuch mit
text_pattern_ops
Sobald wir das ablenkende erste Rauschzeichen ignorieren, kommt es auf die grundlegende links verankerte Musterübereinstimmung an. Deshalb habe ich einen funktionalen B-Tree-Index mit der Operator-Klasse
text_pattern_ops
versucht (unter der Annahme eines Spaltentypstext
).Dies funktioniert hervorragend bei direkten Abfragen mit einem einzelnen Suchbegriff und lässt den Trigrammindex im Vergleich schlecht aussehen:
Der Abfrageplaner berücksichtigt diesen Index jedoch nicht für die Verknüpfung zweier Tabellen. Ich habe diese Einschränkung schon einmal gesehen. Ich habe noch keine aussagekräftige Erklärung dafür.
Partielle / funktionale B-Baum-Indizes
Die Alternative ist die Verwendung von Gleichheitsprüfungen für Teilzeichenfolgen mit Teilindizes. Dies kann in einem verwendet werden
JOIN
.Da wir normalerweise nur eine begrenzte Anzahl von
different lengths
Präfixen haben, können wir eine ähnliche Lösung wie die hier vorgestellte mit Teilindizes erstellen.Angenommen, wir haben Präfixe im Bereich von 1 und 5 Zeichen. Erstellen Sie eine Reihe von Teilfunktionsindizes, einen für jede bestimmte Präfixlänge:
Da diese Teilindizes, sind sie alle zusammen kaum größer als ein einziger vollständiger Index.
Fügen Sie übereinstimmende Indizes für Zahlen hinzu (unter Berücksichtigung des führenden Rauschzeichens):
Während diese Indizes jeweils nur einen Teilstring enthalten und teilweise sind, deckt jeder den größten Teil oder die gesamte Tabelle ab. Sie sind also zusammen viel größer als ein einzelner Gesamtindex - mit Ausnahme langer Zahlen. Und sie erfordern mehr Arbeit für Schreibvorgänge. Das ist das Kosten für erstaunliche Geschwindigkeit.
Wenn diese Kosten für Sie zu hoch sind (Schreibleistung ist wichtig / zu viele Schreibvorgänge / Speicherplatz ein Problem), können Sie diese Indizes überspringen. Der Rest ist noch schneller, wenn auch nicht ganz so schnell wie es sein könnte ...
Wenn Zahlen niemals kürzer als
n
Zeichen sind, löschen Sie redundanteWHERE
Klauseln von einigen oder allen und löschen Sie auch die entsprechendenWHERE
Klausel aus allen folgenden Abfragen.Rekursiver CTE
Bei all dem Setup hoffte ich auf eine sehr elegante Lösung mit einem rekursiven CTE :
Obwohl diese Abfrage nicht schlecht ist - sie funktioniert ungefähr so gut wie die einfache Version mit einem Trigramm-GIN-Index - liefert sie nicht das, was ich mir vorgenommen habe. Der rekursive Term ist nur einmal geplant, sodass nicht die besten Indizes verwendet werden können. Nur der nicht rekursive Term kann.
UNION ALL
Da es sich um eine kleine Anzahl von Rekursionen handelt, können wir sie einfach iterativ formulieren. Dies ermöglicht optimierte Pläne für jeden von ihnen. (Wir verlieren jedoch den rekursiven Ausschluss bereits erfolgreicher Zahlen. Es gibt also noch Verbesserungspotenzial, insbesondere für einen größeren Bereich von Präfixlängen.)
Endlich ein Durchbruch!
SQL-Funktion
Wenn Sie dies in eine SQL-Funktion packen, entfällt der Aufwand für die Abfrageplanung für die wiederholte Verwendung:
Anruf:
PL / pgSQL-Funktion mit dynamischem SQL
Diese plpgsql-Funktion ähnelt dem obigen rekursiven CTE, aber das dynamische SQL
EXECUTE
erzwingt, dass die Abfrage für jede Iteration neu geplant wird. Jetzt werden alle maßgeschneiderten Indizes verwendet.Zusätzlich funktioniert dies für jeden Bereich von Präfixlängen. Die Funktion verwendet zwei Parameter für den Bereich, aber ich habe sie mit
DEFAULT
Werten vorbereitet , sodass sie auch ohne explizite Parameter funktioniert:Der letzte Schritt kann nicht einfach in die eine Funktion eingebunden werden. Entweder rufen Sie es wie folgt aus :
Oder verwenden Sie eine andere SQL-Funktion als Wrapper:
Anruf:
Etwas langsamer aufgrund des erforderlichen Planungsaufwands. Aber vielseitiger als SQL und kürzer für längere Präfixe.
quelle
Eine Zeichenfolge S ist ein Präfix einer Zeichenfolge T, wenn T zwischen S und SZ liegt, wobei Z lexikografisch größer ist als jede andere Zeichenfolge (z. B. 99999999 mit genügend 9en, um die längste mögliche Telefonnummer im Datensatz zu überschreiten, oder manchmal funktioniert 0xFF).
Das längste gemeinsame Präfix für ein gegebenes T ist auch lexikographisch maximal, so dass eine einfache Gruppe von und max es findet.
Wenn dies langsam ist, liegt dies wahrscheinlich an den berechneten Ausdrücken. Sie können also auch versuchen, p.code || '999999' in einer Spalte in der Codetabelle mit einem eigenen Index usw. zu materialisieren.
quelle