Como preservar a ordem original dos elementos em uma matriz não aninhada?

17

Dada a sequência:

'Eu acho que o PostgreSQL é bacana'

Eu gostaria de operar com as palavras individuais encontradas nessa sequência. Essencialmente, eu tenho uma parte separada da qual posso obter detalhes de palavras e gostaria de ingressar em uma matriz não aninhada dessa sequência neste dicionário.

Até agora eu tenho:

select word, meaning, partofspeech
from unnest(string_to_array('I think that PostgreSQL is nifty',' ')) as word
from table t
join dictionary d
on t.word = d.wordname;

Isso realiza os fundamentos do que eu esperava fazer, mas não preserva a ordem das palavras original.

Pergunta relacionada:
PostgreSQL unnest () com número do elemento

swasheck
fonte
Deseja processar uma string ou uma tabela inteira de strings ? Se sim, a tabela possui uma chave primária?
Erwin Brandstetter
@ErwinBrandstetter uma corda em uma tabela (que tem uma chave primária)
swasheck

Respostas:

22

WITH ORDINALITY no Postgres 9.4 ou posterior

O novo recurso simplifica essa classe de problemas. A consulta acima agora pode ser simplesmente:

SELECT *
FROM   regexp_split_to_table('I think postgres is nifty', ' ') WITH ORDINALITY x(word, rn);

Ou, aplicado a uma tabela:

SELECT *
FROM   tbl t, regexp_split_to_table(t.my_column, ' ') WITH ORDINALITY x(word, rn);

Detalhes:

Sobre a LATERALjunção implícita :

Postgres 9.3 ou mais antigo - e explicação mais geral

Para uma única sequência

Você pode aplicar a função de janela row_number()para lembrar a ordem dos elementos. No entanto, com o habitual, row_number() OVER (ORDER BY col)você obtém números de acordo com a ordem de classificação , não a posição original na string.

Você pode tentar e simplesmente omitir o ORDER BYpara obter a posição "como está":

SELECT *, row_number() OVER () AS rn
FROM  (
   SELECT regexp_split_to_table('I think postgres is nifty', ' ') AS word
   ) x;

Desempenho de regexp_split_to_table()degradas com cordas longas. unnest(string_to_array(...))escala melhor:

SELECT *, row_number() OVER () AS rn
FROM  (
   SELECT unnest(string_to_array('I think postgres is nifty', ' ')) AS word
   ) x;

No entanto, enquanto isso normalmente funciona e eu nunca o vi quebrar em consultas simples, o PostgreSQL não afirma nada sobre a ordem das linhas sem um explícito ORDER BY.

Para garantir números ordinais de elementos na string original, use generate_subscript()(aprimorado com o comentário de @deszo):

SELECT arr[rn] AS word, rn
FROM   (
   SELECT *, generate_subscripts(arr, 1) AS rn
   FROM  (
      SELECT string_to_array('I think postgres is nifty', ' ') AS arr
      ) x
   ) y;

Para uma tabela de cordas

Adicione PARTITION BY idà OVERcláusula ...

Tabela de demonstração:

CREATE TEMP TABLE strings(string text);
INSERT INTO strings VALUES
  ('I think postgres is nifty')
 ,('And it keeps getting better');

Eu uso ctidcomo substituto ad-hoc para uma chave primária . Se você tiver uma (ou qualquer coluna exclusiva ), use-a.

SELECT *, row_number() OVER (PARTITION BY ctid) AS rn
FROM  (
   SELECT ctid, unnest(string_to_array(string, ' ')) AS word
   FROM   strings
   ) x;

Isso funciona sem nenhum ID distinto:

SELECT arr[rn] AS word, rn
FROM  (
   SELECT *, generate_subscripts(arr, 1) AS rn
   FROM  (
      SELECT string_to_array(string, ' ') AS arr
      FROM   strings
      ) x
   ) y;

SQL Fiddle.

Resposta à pergunta

SELECT z.arr, z.rn, z.word, d.meaning   -- , partofspeech -- ?
FROM  (
   SELECT *, arr[rn] AS word
   FROM  (
      SELECT *, generate_subscripts(arr, 1) AS rn
      FROM  (
         SELECT string_to_array(string, ' ') AS arr
         FROM   strings
         ) x
      ) y
   ) z
JOIN   dictionary d ON d.wordname = z.word
ORDER  BY z.arr, z.rn;
Erwin Brandstetter
fonte
1
Você também pode explorar o comportamento peculiar SRF-in-SELECT-lista do Pg: SELECT generate_series(1,array_length(word_array,1)), unnest(word_array) FROM ..... As 9.3 LATERALpodem fornecer soluções mais agradáveis ​​para esse problema.
Craig Ringer
2
Não generate_subscripts(arr, 1)funcionaria em vez de generate_series(1, array_upper(arr, 1))? Eu preferiria o primeiro por clareza.
dezso '
@dezso: Bom ponto. Simplifica a consulta um pouco mais. Eu alterei a resposta em conformidade.
Erwin Brandstetter
1
@ Erwin, você já viu isso com ORDINALIDADE post de depesz?
Jack Douglas
1
@JackDouglas: Por acaso, tivemos uma discussão sobre um tópico relacionado na sexta-feira , o que me levou a uma descoberta semelhante. Eu adicionei um pouco à resposta.
Erwin Brandstetter