Frage gestellt
Testtabelle:
CREATE TABLE tbl (id int, str text);
INSERT INTO tbl VALUES
(1, 'a.b.c.d.e')
, (2, 'x1.yy2.zzz3') -- different number & length of elements for testing
, (3, '') -- empty string
, (4, NULL); -- NULL
Rekursiver CTE in einer LATERAL-Unterabfrage
SELECT *
FROM tbl, LATERAL (
WITH RECURSIVE cte AS (
SELECT str
UNION ALL
SELECT right(str, strpos(str, '.') * -1) -- trim leading name
FROM cte
WHERE str LIKE '%.%' -- stop after last dot removed
)
SELECT ARRAY(TABLE cte) AS result
) r;
Das CROSS JOIN LATERAL
( , LATERAL
kurz) ist sicher, da das Gesamtergebnis der Unterabfrage immer eine Zeile zurückgibt. Du erhältst ...
- ... ein Array mit einem leeren String-Element für
str = ''
in der Basistabelle
- ... ein Array mit einem NULL-Element für
str IS NULL
in der Basistabelle
Eingepackt mit einem billigen Array-Konstruktor in der Unterabfrage, also keine Aggregation in der äußeren Abfrage.
Ein Vorzeigeobjekt für SQL-Funktionen, aber der rCTE-Overhead kann die Spitzenleistung beeinträchtigen.
Brute Force für eine triviale Anzahl von Elementen
Für Ihren Fall mit einer trivial kleinen Anzahl von Elementen kann ein einfacher Ansatz ohne Unterabfrage schneller sein:
SELECT id, array_remove(ARRAY[substring(str, '(?:[^.]+\.){4}[^.]+$')
, substring(str, '(?:[^.]+\.){3}[^.]+$')
, substring(str, '(?:[^.]+\.){2}[^.]+$')
, substring(str, '[^.]+\.[^.]+$')
, substring(str, '[^.]+$')], NULL)
FROM tbl;
Angenommen, maximal 5 Elemente, wie Sie kommentiert haben. Sie können leicht für mehr erweitern.
Wenn eine bestimmte Domäne weniger Elemente enthält, geben überschüssige substring()
Ausdrücke NULL zurück und werden von entfernt array_remove()
.
Tatsächlich kann der right(str, strpos(str, '.')
mehrmals verschachtelte Ausdruck von oben ( ) schneller sein (obwohl er schwer zu lesen ist), da Funktionen für reguläre Ausdrücke teurer sind.
Eine Abzweigung von @ Dudus Abfrage
@ Dudus intelligente Abfrage könnte verbessert werden mit generate_subscripts()
:
SELECT id, array_agg(array_to_string(arr[i:], '.')) AS result
FROM (SELECT id, string_to_array(str,'.') AS arr FROM tbl) t
LEFT JOIN LATERAL generate_subscripts(arr, 1) i ON true
GROUP BY id;
Wird auch verwendet LEFT JOIN LATERAL ... ON true
, um mögliche Zeilen mit NULL-Werten beizubehalten.
PL / pgSQL-Funktion
Ähnliche Logik wie beim rCTE. Wesentlich einfacher und schneller als das, was Sie haben:
CREATE OR REPLACE FUNCTION string_part_seq(input text, OUT result text[]) AS
$func$
BEGIN
LOOP
result := result || input; -- text[] || text array concatenation
input := right(input, strpos(input, '.') * -1);
EXIT WHEN input = '';
END LOOP;
END
$func$ LANGUAGE plpgsql IMMUTABLE STRICT;
Der OUT
Parameter wird am Ende der Funktion automatisch zurückgegeben.
Eine Initialisierung ist nicht erforderlich result
, da NULL::text[] || text 'a' = '{a}'::text[]
.
Dies funktioniert nur mit 'a'
der richtigen Eingabe. NULL::text[] || 'a'
(String-Literal) würde einen Fehler auslösen, da Postgres den array || array
Operator auswählt .
strpos()
Gibt zurück, 0
wenn kein Punkt gefunden wurde. right()
Gibt also eine leere Zeichenfolge zurück und die Schleife endet.
Dies ist wahrscheinlich die schnellste aller Lösungen hier.
Alle funktionieren in Postgres 9.3+
(mit Ausnahme der kurzen Array-Slice-Notation arr[3:]
. Ich habe eine Obergrenze in die Geige eingefügt, damit sie in Seite 9.3 funktioniert : arr[3:999]
.)
SQL Fiddle.
Anderer Ansatz zur Optimierung der Suche
Ich bin bei @ jpmc26 (und bei Ihnen ): Ein völlig anderer Ansatz ist vorzuziehen. Ich mag die Kombination von jpmc26 reverse()
und a text_pattern_ops
.
Ein Trigrammindex wäre für Teil- oder Fuzzy-Übereinstimmungen überlegen. Da Sie jedoch nur an ganzen Wörtern interessiert sind , ist die Volltextsuche eine weitere Option. Ich erwarte eine wesentlich kleinere Indexgröße und damit eine bessere Performance.
pg_trgm sowie FTS unterstützen Abfragen, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird.
Hostnamen wie q.x.t.com
oder t.com
(Wörter mit Inline-Punkten) werden als Typ "Host" identifiziert und als ein Wort behandelt . Es gibt aber auch Präfix-Matching in FTS (was manchmal übersehen zu werden scheint). Das Handbuch:
Auch *
kann auf ein Lexem angebracht werden Präfix Anpassung anzugeben:
Mit der intelligenten Idee von @ jpmc26 reverse()
können wir Folgendes erreichen :
SELECT *
FROM tbl
WHERE to_tsvector('simple', reverse(str))
@@ to_tsquery ('simple', reverse('c.d.e') || ':*');
-- or with reversed prefix: reverse('*:c.d.e')
Welches wird von einem Index unterstützt:
CREATE INDEX tbl_host_idx ON tbl USING GIN (to_tsvector('simple', reverse(str)));
Beachten Sie die 'simple'
Konfiguration: Wir möchten nicht, dass der Stemming oder Thesaurus mit der Standardkonfiguration 'english'
verwendet wird.
Alternativ (mit einer größeren Anzahl möglicher Abfragen) könnten wir die neue Phrasensuchfunktion der Textsuche in Postgres 9.6 verwenden. Die Versionshinweise:
Eine Phrasensuchabfrage kann in tsquery input mit den neuen Operatoren <->
und angegeben werden . Ersteres bedeutet, dass die Lexeme davor und danach in dieser Reihenfolge nebeneinander erscheinen müssen. Letzteres bedeutet, dass sie genau Lexeme voneinander entfernt sein müssen.<
N
>
N
Abfrage:
SELECT *
FROM tbl
WHERE to_tsvector ('simple', replace(str, '.', ' '))
@@ phraseto_tsquery('simple', 'c d e');
Ersetzen Sie dot ( '.'
) durch space ( ' '
), um zu verhindern, dass der Parser 't.com' als Hostnamen klassifiziert, und verwenden Sie stattdessen jedes Wort als separates Lexem.
Und dazu ein passender Index:
CREATE INDEX tbl_phrase_idx ON tbl USING GIN (to_tsvector('simple', replace(str, '.', ' ')));