Domanda posta
Tabella di prova:
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
CTE ricorsivo in una sottoquery LATERALE
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;
Il CROSS JOIN LATERAL
( , LATERAL
in breve) è sicuro, perché il risultato aggregato della sottoquery restituisce sempre una riga. Ottieni ...
- ... un array con un elemento stringa vuoto per
str = ''
nella tabella di base
- ... un array con un elemento NULL
str IS NULL
nella tabella di base
Confezionato con un costruttore di array economico nella sottoquery, quindi nessuna aggregazione nella query esterna.
Un punto di forza delle funzionalità SQL, ma il sovraccarico di rCTE potrebbe impedire le massime prestazioni.
Forza bruta per numero banale di elementi
Per il tuo caso con un numero banalmente piccolo di elementi , un approccio semplice senza subquery potrebbe essere più veloce:
SELECT id, array_remove(ARRAY[substring(str, '(?:[^.]+\.){4}[^.]+$')
, substring(str, '(?:[^.]+\.){3}[^.]+$')
, substring(str, '(?:[^.]+\.){2}[^.]+$')
, substring(str, '[^.]+\.[^.]+$')
, substring(str, '[^.]+$')], NULL)
FROM tbl;
Supponendo un massimo di 5 elementi come hai commentato. Puoi espanderti facilmente per ulteriori informazioni.
Se un determinato dominio ha meno elementi, le substring()
espressioni in eccesso restituiscono NULL e vengono rimosse da array_remove()
.
In realtà, l'espressione dall'alto ( right(str, strpos(str, '.')
), nidificata più volte potrebbe essere più veloce (anche se scomoda da leggere) poiché le funzioni delle espressioni regolari sono più costose.
Un fork della domanda di @ Dudu
La query intelligente di @ Dudu potrebbe essere migliorata con 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;
Usando anche LEFT JOIN LATERAL ... ON true
per preservare possibili righe con valori NULL.
Funzione PL / pgSQL
Logica simile alla rCTE. Sostanzialmente più semplice e veloce di quello che hai:
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;
Il OUT
parametro viene restituito automaticamente alla fine della funzione.
Non è necessario inizializzare result
, perché NULL::text[] || text 'a' = '{a}'::text[]
.
Questo funziona solo con la 'a'
corretta digitazione. NULL::text[] || 'a'
(string letteral) genererebbe un errore perché Postgres seleziona l' array || array
operatore.
strpos()
ritorna 0
se non viene trovato alcun punto, quindi right()
restituisce una stringa vuota e il ciclo termina.
Questa è probabilmente la più veloce di tutte le soluzioni qui.
Funzionano tutti in Postgres 9.3+
(ad eccezione della breve notazione di slice dell'array arr[3:]
. Ho aggiunto un limite superiore nel violino per farlo funzionare in pag 9.3:. arr[3:999]
)
SQL Fiddle.
Approccio diverso per ottimizzare la ricerca
Sono con @ jpmc26 (e te stesso): sarà preferibile un approccio completamente diverso. Mi piace la combinazione di jpmc26 di reverse()
e a text_pattern_ops
.
Un indice di trigramma sarebbe superiore per le partite parziali o sfocate. Ma poiché sei interessato solo a parole intere , la ricerca full-text è un'altra opzione. Mi aspetto una dimensione dell'indice sostanzialmente inferiore e quindi prestazioni migliori.
pg_trgm e query insensibili al caso di supporto FTS , tra l'altro.
I nomi host come q.x.t.com
o t.com
(parole con punti incorporati) sono identificati come tipo "host" e trattati come una parola. Ma c'è anche la corrispondenza del prefisso in FTS (che a volte sembra essere trascurato). Il manuale:
Inoltre, *
può essere collegato a un lessico per specificare la corrispondenza del prefisso:
Usando l'idea intelligente di @ jpmc26 con reverse()
, possiamo far funzionare questo:
SELECT *
FROM tbl
WHERE to_tsvector('simple', reverse(str))
@@ to_tsquery ('simple', reverse('c.d.e') || ':*');
-- or with reversed prefix: reverse('*:c.d.e')
Quale è supportato da un indice:
CREATE INDEX tbl_host_idx ON tbl USING GIN (to_tsvector('simple', reverse(str)));
Nota la 'simple'
configurazione: non vogliamo che il stemming o il thesaurus vengano usati con la 'english'
configurazione predefinita .
In alternativa (con una maggiore varietà di possibili query) potremmo usare la nuova funzionalità di ricerca di frasi della ricerca di testo in Postgres 9.6. Le note di rilascio:
È possibile specificare una query di ricerca di frasi nell'input tsquery utilizzando i nuovi operatori <->
e . Il primo significa che i lessemi prima e dopo devono apparire adiacenti l'uno all'altro in quell'ordine. Quest'ultimo significa che devono essere esattamente distanti lexemi.<
N
>
N
Query:
SELECT *
FROM tbl
WHERE to_tsvector ('simple', replace(str, '.', ' '))
@@ phraseto_tsquery('simple', 'c d e');
Sostituisci punto ( '.'
) con spazio ( ' '
) per impedire al parser di classificare "t.com" come nome host e utilizzare invece ogni parola come lessema separato.
E un indice corrispondente da abbinare:
CREATE INDEX tbl_phrase_idx ON tbl USING GIN (to_tsvector('simple', replace(str, '.', ' ')));