Question posée
Table de test:
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 récursif dans une sous-requête LATÉRALE
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;
Le CROSS JOIN LATERAL
( , LATERAL
pour faire court) est sûr, car le résultat agrégé de la sous-requête renvoie toujours une ligne. Vous obtenez ...
- ... un tableau avec un élément de chaîne vide pour
str = ''
dans la table de base
- ... un tableau avec un élément NULL pour
str IS NULL
dans la table de base
Enveloppé avec un constructeur de tableau bon marché dans la sous-requête, donc pas d'agrégation dans la requête externe.
Un exemple des fonctionnalités SQL, mais la surcharge rCTE peut empêcher des performances optimales.
Force brute pour un nombre trivial d'éléments
Pour votre cas avec un petit nombre d'éléments , une approche simple sans sous-requête peut être plus rapide:
SELECT id, array_remove(ARRAY[substring(str, '(?:[^.]+\.){4}[^.]+$')
, substring(str, '(?:[^.]+\.){3}[^.]+$')
, substring(str, '(?:[^.]+\.){2}[^.]+$')
, substring(str, '[^.]+\.[^.]+$')
, substring(str, '[^.]+$')], NULL)
FROM tbl;
En supposant un maximum de 5 éléments comme vous l'avez commenté. Vous pouvez facilement vous développer pour plus.
Si un domaine donné a moins d'éléments, les substring()
expressions en excès renvoient NULL et sont supprimées par array_remove()
.
En fait, l'expression ci-dessus ( right(str, strpos(str, '.')
), imbriquée plusieurs fois peut être plus rapide (bien que difficile à lire) car les fonctions d'expression régulière sont plus chères.
Un fork de la requête de @ Dudu
La requête intelligente de @ Dudu pourrait être améliorée avec 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;
Également utilisé LEFT JOIN LATERAL ... ON true
pour conserver les lignes possibles avec des valeurs NULL.
Fonction PL / pgSQL
Logique similaire à celle du rCTE. Sensiblement plus simple et plus rapide que ce que vous avez:
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;
Le OUT
paramètre est renvoyé automatiquement à la fin de la fonction.
Il n'est pas nécessaire de l'initialiser result
, car NULL::text[] || text 'a' = '{a}'::text[]
.
Cela ne fonctionne qu'avec 'a'
une saisie correcte. NULL::text[] || 'a'
(chaîne littérale) déclencherait une erreur car Postgres choisit l' array || array
opérateur.
strpos()
renvoie 0
si aucun point n'est trouvé, right()
renvoie donc une chaîne vide et la boucle se termine.
C'est probablement la plus rapide de toutes les solutions ici.
Tous fonctionnent dans Postgres 9.3+
(à l'exception de la notation de tranche de tableau court arr[3:]
. J'ai ajouté une limite supérieure dans le violon pour le faire fonctionner dans pg 9.3:. arr[3:999]
)
SQL Fiddle.
Approche différente pour optimiser la recherche
Je suis avec @ jpmc26 (et vous-même): une approche complètement différente sera préférable. J'aime la combinaison de jpmc26 reverse()
et de a text_pattern_ops
.
Un index de trigrammes serait supérieur pour les correspondances partielles ou floues. Mais comme vous n'êtes intéressé que par des mots entiers , la recherche en texte intégral est une autre option. Je m'attends à une taille d'index beaucoup plus petite et donc à de meilleures performances.
pg_trgm ainsi que les requêtes insensibles à la casse FTS , btw.
Les noms d'hôte comme q.x.t.com
ou t.com
(mots avec des points en ligne) sont identifiés comme de type "hôte" et traités comme un seul mot. Mais il y a aussi la correspondance des préfixes dans FTS (qui semble parfois être négligée). Le manuel:
En outre, *
peut être attaché à un lexème pour indiquer le préfixe correspondant:
En utilisant l'idée intelligente de @ jpmc26 avec reverse()
, nous pouvons faire en sorte que cela fonctionne:
SELECT *
FROM tbl
WHERE to_tsvector('simple', reverse(str))
@@ to_tsquery ('simple', reverse('c.d.e') || ':*');
-- or with reversed prefix: reverse('*:c.d.e')
Qui est pris en charge par un index:
CREATE INDEX tbl_host_idx ON tbl USING GIN (to_tsvector('simple', reverse(str)));
Notez la 'simple'
configuration: nous ne voulons pas que le radical ou le dictionnaire des synonymes soit utilisé avec la 'english'
configuration par défaut .
Alternativement (avec une plus grande variété de requêtes possibles), nous pourrions utiliser la nouvelle capacité de recherche d'expression de la recherche de texte dans Postgres 9.6. Les notes de version:
Une requête de recherche de phrase peut être spécifiée dans l'entrée tsquery à l'aide des nouveaux opérateurs <->
et . Le premier signifie que les lexèmes avant et après doivent apparaître côte à côte dans cet ordre. Ce dernier signifie qu'ils doivent être exactement séparés les lexèmes.<
N
>
N
Requete:
SELECT *
FROM tbl
WHERE to_tsvector ('simple', replace(str, '.', ' '))
@@ phraseto_tsquery('simple', 'c d e');
Remplacez dot ( '.'
) par space ( ' '
) pour empêcher l'analyseur de classer «t.com» comme nom d'hôte et utilisez plutôt chaque mot comme lexème distinct.
Et un index correspondant pour l'accompagner:
CREATE INDEX tbl_phrase_idx ON tbl USING GIN (to_tsvector('simple', replace(str, '.', ' ')));