Pergunta feita
Tabela de teste:
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 recursiva em uma 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;
O CROSS JOIN LATERAL
( , LATERAL
abreviado) é seguro, porque o resultado agregado da subconsulta sempre retorna uma linha. Você consegue ...
- ... uma matriz com um elemento de sequência vazio para
str = ''
na tabela base
- ... uma matriz com um elemento NULL para
str IS NULL
na tabela base
Embrulhado com um construtor de matriz barato na subconsulta, portanto, sem agregação na consulta externa.
Uma demonstração dos recursos SQL, mas a sobrecarga do rCTE pode impedir o desempenho superior.
Força bruta para número trivial de elementos
Para o seu caso com um número trivialmente pequeno de elementos , uma abordagem simples sem subconsulta pode ser mais rápida:
SELECT id, array_remove(ARRAY[substring(str, '(?:[^.]+\.){4}[^.]+$')
, substring(str, '(?:[^.]+\.){3}[^.]+$')
, substring(str, '(?:[^.]+\.){2}[^.]+$')
, substring(str, '[^.]+\.[^.]+$')
, substring(str, '[^.]+$')], NULL)
FROM tbl;
Supondo um máximo de 5 elementos como você comentou. Você pode facilmente expandir para mais.
Se um determinado domínio tiver menos elementos, substring()
expressões em excesso retornarão NULL e serão removidas por array_remove()
.
Na verdade, a expressão acima ( right(str, strpos(str, '.')
), aninhada várias vezes, pode ser mais rápida (embora de difícil leitura), pois as funções de expressão regular são mais caras.
Uma bifurcação da consulta de @ Dudu
A consulta inteligente do @ Dudu pode ser melhorada com 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;
Também usando LEFT JOIN LATERAL ... ON true
para preservar possíveis linhas com valores NULL.
Função PL / pgSQL
Lógica semelhante à do rCTE. Substancialmente mais simples e mais rápido do que você tem:
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;
O OUT
parâmetro é retornado no final da função automaticamente.
Não há necessidade de inicializar result
, porque NULL::text[] || text 'a' = '{a}'::text[]
.
Isso funciona apenas com a 'a'
digitação correta. NULL::text[] || 'a'
(string literal) geraria um erro porque o Postgres escolhe o array || array
operador.
strpos()
retorna 0
se nenhum ponto for encontrado, então right()
retorna uma string vazia e o loop termina.
Esta é provavelmente a mais rápida de todas as soluções aqui.
Todos eles funcionam no Postgres 9.3+ (exceto pela notação de fatia de matriz curta . Adicionei um limite superior no violino para fazê-lo funcionar na página 9.3:. )
arr[3:]
arr[3:999]
SQL Fiddle.
Abordagem diferente para otimizar a pesquisa
Estou com @ jpmc26 (e com você): uma abordagem completamente diferente será preferível. Eu gosto da combinação de jpmc26 de reverse()
e a text_pattern_ops
.
Um índice de trigrama seria superior para correspondências parciais ou difusas. Mas como você só está interessado em palavras inteiras , a Pesquisa de texto completo é outra opção. Espero um tamanho de índice substancialmente menor e, portanto, melhor desempenho.
O pg_trgm e o FTS oferecem suporte a consultas sem distinção entre maiúsculas e minúsculas , btw.
Nomes de host como q.x.t.com
ou t.com
(palavras com pontos embutidos) são identificados como tipo "host" e tratados como uma palavra. Mas também há correspondência de prefixo no STF (que às vezes parece ser esquecido). O manual:
Além disso, *
pode ser anexado a um lexeme para especificar a correspondência de prefixo:
Usando a idéia inteligente do @ jpmc26 reverse()
, podemos fazer este trabalho:
SELECT *
FROM tbl
WHERE to_tsvector('simple', reverse(str))
@@ to_tsquery ('simple', reverse('c.d.e') || ':*');
-- or with reversed prefix: reverse('*:c.d.e')
O que é suportado por um índice:
CREATE INDEX tbl_host_idx ON tbl USING GIN (to_tsvector('simple', reverse(str)));
Observe a 'simple'
configuração: não queremos que o stemming ou o thesaurus sejam usados com a 'english'
configuração padrão .
Como alternativa (com uma variedade maior de consultas possíveis), poderíamos usar o novo recurso de pesquisa de frase da pesquisa de texto no Postgres 9.6. As notas de versão:
Uma consulta de pesquisa de frase pode ser especificada na entrada tsquery usando os novos operadores <->
e . O primeiro significa que os lexemas antes e depois devem aparecer adjacentes um ao outro nessa ordem. O último significa que eles devem estar exatamente com lexemes separados.<
N
>
N
Inquerir:
SELECT *
FROM tbl
WHERE to_tsvector ('simple', replace(str, '.', ' '))
@@ phraseto_tsquery('simple', 'c d e');
Substitua dot ( '.'
) por space ( ' '
) para impedir que o analisador classifique 't.com' como nome do host e, em vez disso, use cada palavra como léxico máximo.
E um índice correspondente para acompanhar:
CREATE INDEX tbl_phrase_idx ON tbl USING GIN (to_tsvector('simple', replace(str, '.', ' ')));