Pregunta formulada
Tabla de prueba:
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 recursivo en una subconsulta LATERAL
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;
El CROSS JOIN LATERAL
( , LATERAL
para abreviar) es seguro, porque el resultado agregado de la subconsulta siempre devuelve una fila. Usted obtiene ...
- ... una matriz con un elemento de cadena vacío para
str = ''
en la tabla base
- ... una matriz con un elemento NULL
str IS NULL
en la tabla base
Terminado con un constructor de matriz barato en la subconsulta, por lo que no hay agregación en la consulta externa.
Una obra maestra de las características de SQL, pero la sobrecarga de rCTE puede evitar el máximo rendimiento.
Fuerza bruta para un número trivial de elementos.
Para su caso con un número trivialmente pequeño de elementos , un enfoque simple sin subconsulta puede ser más rápido:
SELECT id, array_remove(ARRAY[substring(str, '(?:[^.]+\.){4}[^.]+$')
, substring(str, '(?:[^.]+\.){3}[^.]+$')
, substring(str, '(?:[^.]+\.){2}[^.]+$')
, substring(str, '[^.]+\.[^.]+$')
, substring(str, '[^.]+$')], NULL)
FROM tbl;
Suponiendo un máximo de 5 elementos como usted comentó. Puede expandirse fácilmente para obtener más.
Si un dominio dado tiene menos elementos, las substring()
expresiones en exceso devuelven NULL y son eliminadas por array_remove()
.
En realidad, la expresión de arriba ( right(str, strpos(str, '.')
), anidada varias veces puede ser más rápida (aunque difícil de leer) ya que las funciones de expresión regular son más caras.
Una bifurcación de la consulta de @ Dudu
La consulta inteligente de @ Dudu podría mejorarse 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;
También se usa LEFT JOIN LATERAL ... ON true
para preservar posibles filas con valores NULL.
Función PL / pgSQL
Lógica similar a la rCTE. Sustancialmente más simple y más rápido que lo que tienes:
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;
El OUT
parámetro se devuelve al final de la función automáticamente.
No hay necesidad de inicializar result
, porque NULL::text[] || text 'a' = '{a}'::text[]
.
Esto solo funciona si 'a'
se escribe correctamente. NULL::text[] || 'a'
(literal de cadena) generaría un error porque Postgres elige el array || array
operador.
strpos()
devuelve 0
si no se encuentra ningún punto, por lo que right()
devuelve una cadena vacía y el ciclo termina
Esta es probablemente la más rápida de todas las soluciones aquí.
Todos ellos funcionan en Postgres 9.3+ (a excepción de la notación de corte de matriz corta . Agregué un límite superior en el violín para que funcione en la página 9.3:. )
arr[3:]
arr[3:999]
SQL Fiddle.
Enfoque diferente para optimizar la búsqueda
Estoy con @ jpmc26 (y usted mismo): un enfoque completamente diferente será preferible. Me gusta la combinación de jpmc26 reverse()
y a text_pattern_ops
.
Un índice de trigrama sería superior para coincidencias parciales o difusas. Pero como solo le interesan las palabras completas , la búsqueda de texto completo es otra opción. Espero un tamaño de índice sustancialmente menor y, por lo tanto, un mejor rendimiento.
pg_trgm y FTS admiten consultas que no distinguen entre mayúsculas y minúsculas , por cierto.
Los nombres de host como q.x.t.com
o t.com
(palabras con puntos en línea) se identifican como tipo "host" y se tratan como una sola palabra. Pero también hay coincidencia de prefijos en FTS (que a veces parece pasarse por alto). El manual:
Además, *
se puede adjuntar a un lexema para especificar la coincidencia de prefijos:
Usando la idea inteligente de @ jpmc26 con reverse()
, podemos hacer que esto funcione:
SELECT *
FROM tbl
WHERE to_tsvector('simple', reverse(str))
@@ to_tsquery ('simple', reverse('c.d.e') || ':*');
-- or with reversed prefix: reverse('*:c.d.e')
Que es compatible con un índice:
CREATE INDEX tbl_host_idx ON tbl USING GIN (to_tsvector('simple', reverse(str)));
Tenga en cuenta la 'simple'
configuración: no queremos que se utilicen las derivaciones o tesauros con la 'english'
configuración predeterminada .
Alternativamente (con una mayor variedad de consultas posibles) podríamos usar la nueva capacidad de búsqueda de frases de búsqueda de texto en Postgres 9.6. Las notas de lanzamiento:
Se puede especificar una consulta de búsqueda de frase en la entrada tsquery utilizando los nuevos operadores <->
y . El primero significa que los lexemas anteriores y posteriores deben aparecer adyacentes entre sí en ese orden. Esto último significa que deben estar exactamente separados por lexemas.<
N
>
N
Consulta:
SELECT *
FROM tbl
WHERE to_tsvector ('simple', replace(str, '.', ' '))
@@ phraseto_tsquery('simple', 'c d e');
Reemplace dot ( '.'
) con espacio ( ' '
) para evitar que el analizador clasifique 't.com' como nombre de host y, en su lugar, use cada palabra como lexema separado.
Y un índice coincidente para acompañarlo:
CREATE INDEX tbl_phrase_idx ON tbl USING GIN (to_tsvector('simple', replace(str, '.', ' ')));