Comment conserver l'ordre d'origine des éléments dans un tableau non imbriqué?

19

Étant donné la chaîne:

«Je pense que PostgreSQL est astucieux»

Je voudrais opérer sur les mots individuels trouvés dans cette chaîne. Essentiellement, j'ai un autre à partir duquel je peux obtenir des détails sur les mots et je voudrais rejoindre un tableau non imbriqué de cette chaîne dans ce dictionnaire.

Jusqu'à présent, j'ai:

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;

Cela accomplit les principes fondamentaux de ce que j'espérais faire, mais cela ne préserve pas l'ordre des mots d'origine.

Question connexe:
PostgreSQL unnest () avec le numéro d'élément

swasheck
la source
Voulez-vous traiter une chaîne ou une table entière de chaînes ? Si oui, la table a-t-elle une clé primaire?
Erwin Brandstetter
@ErwinBrandstetter une chaîne dans une table (qui a une clé primaire)
swasheck

Réponses:

24

WITH ORDINALITY dans Postgres 9.4 ou version ultérieure

La nouvelle fonctionnalité simplifie cette classe de problèmes. La requête ci-dessus peut maintenant simplement être:

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

Ou, appliqué à une table:

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

Détails:

À propos de la LATERALjointure implicite :

Postgres 9.3 ou plus ancien - et explication plus générale

Pour une seule chaîne

Vous pouvez appliquer la fonction de fenêtre row_number()pour mémoriser l'ordre des éléments. Cependant, avec l'habituel, row_number() OVER (ORDER BY col)vous obtenez des nombres selon l' ordre de tri , pas la position d'origine dans la chaîne.

Vous pouvez simplement omettre ORDER BYd'obtenir le poste "tel quel":

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

Performance des regexp_split_to_table()dégradés avec des cordes longues. unnest(string_to_array(...))se balance mieux:

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

Cependant, bien que cela fonctionne normalement et que je ne l'ai jamais vu casser dans des requêtes simples, Postgres n'affirme rien quant à l'ordre des lignes sans explicite ORDER BY.

Pour garantir le nombre ordinal d'éléments dans la chaîne d'origine, utilisez generate_subscript()(amélioré avec le commentaire de @deszo):

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

Pour une table de cordes

Ajouter PARTITION BY idà la OVERclause ...

Table de démonstration:

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

J'utilise ctidcomme substitut ad hoc pour une clé primaire . Si vous en avez une (ou une colonne unique ), utilisez-la à la place.

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

Cela fonctionne sans aucun identifiant distinct:

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.

Réponse à la question

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
la source
1
Vous pouvez également exploiter le comportement excentrique SRF-in-liste_sélection de Pg: SELECT generate_series(1,array_length(word_array,1)), unnest(word_array) FROM ..... Les 9.3 LATERALpourraient fournir de meilleures solutions à ce problème.
Craig Ringer
2
Ne generate_subscripts(arr, 1)fonctionnerait pas à la place de generate_series(1, array_upper(arr, 1))? Je préfère le premier pour plus de clarté.
dezso
1
@Erwin avez-vous vu ce message AVEC ORDINALITÉ de depesz?
Jack Douglas
1
@JackDouglas: En l'occurrence, nous avons eu une discussion sur un sujet connexe vendredi , ce qui m'a conduit à une découverte similaire. J'ai ajouté un peu à la réponse.
Erwin Brandstetter du
1
Le lien pour les "détails" renvoie simplement à cette même page. C'est déroutant.
Wildcard