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

quand j'ai une colonne avec des valeurs séparées, je peux utiliser la fonction unnest() :

myTable
id | elements
---+------------
1  |ab,cd,efg,hi
2  |jk,lm,no,pq
3  |rstuv,wxyz

select id, unnest(string_to_array(elements, ',')) AS elem
from myTable

id | elem
---+-----
1  | ab
1  | cd
1  | efg
1  | hi
2  | jk
...

Comment puis-je inclure des numéros d'éléments? C'est-à-dire:

id | elem | nr
---+------+---
1  | ab   | 1
1  | cd   | 2
1  | efg  | 3
1  | hi   | 4
2  | jk   | 1
...

je veux la position originale de chaque élément de la chaîne source. J'ai essayé avec les fonctions de fenêtre ( row_number() , rank() etc.) mais je reçois toujours 1 . Peut-être parce qu'ils sont dans la même ligne de la table source?

je sais que c'est un mauvais design de table. Ce n'est pas le mien, j'essaie juste de le réparer.

57
demandé sur Erwin Brandstetter 2012-01-06 19:45:44

5 réponses

Postgres 9,4 ou plus tard

Use WITH ORDINALITY pour les fonctions de retour de jeu:

Lorsqu'une fonction de la clause FROM est suffixée par WITH ORDINALITY , a La colonne bigint est ajoutée à la sortie qui commence par 1 et incrémente de 1 pour chaque ligne de la sortie de la fonction. Ce qui est le plus utile dans le cas du retour des fonctions telles que UNNEST() .

en combinaison avec le LATERAL caractéristique dans pg 9.3+ , et selon ce fil sur pgsql-hackers , la requête ci-dessus peut maintenant être écrit comme:

SELECT t.id, a.elem, a.nr
FROM   tbl AS t
LEFT   JOIN LATERAL unnest(string_to_array(t.elements, ','))
                    WITH ORDINALITY AS a(elem, nr) ON TRUE;

LEFT JOIN ... ON TRUE conserve toutes les lignes de la table de gauche, même si l'expression table de droite ne renvoie aucune ligne. Si cela ne vous concerne pas, vous pouvez utiliser ceci autrement. équivalent, moins verbeux forme avec un implicite CROSS JOIN LATERAL :

SELECT t.id, a.elem, a.nr
FROM   tbl t, unnest(string_to_array(t.elements, ',')) WITH ORDINALITY a(elem, nr);

ou plus simple si basé sur un tableau actuel ( arr étant une colonne tableau):

SELECT t.id, a.elem, a.nr
FROM   tbl t, unnest(t.arr) WITH ORDINALITY a(elem, nr);

ou même, avec une syntaxe minimale:

SELECT id, a, ordinality
FROM   tbl, unnest(arr) WITH ORDINALITY a;

a est automatiquement table et alias de colonne. Le nom par défaut de la colonne d'ordinalité ajoutée est ordinality . Mais il est préférable (plus sûr, plus propre) d'ajouter des alias de colonne explicites et des colonnes de qualification de table.

Postgresql 8.4. - 9.3

avec row_number() OVER (PARTITION BY id ORDER BY elem) vous obtenez des nombres selon l'ordre de tri, pas le nombre ordinal de la position initiale ordinale dans la chaîne.

vous pourriez tout simplement omettre le ORDER BY :

SELECT *, row_number() OVER (PARTITION by id) AS nr
FROM  (SELECT id, regexp_split_to_table(elements, ',') AS elem FROM tbl) t;

Alors que cela fonctionne normalement et j'ai jamais vu il casse dans les requêtes simples, PostgreSQL n'affirme rien concernant l'ordre des rangs sans ORDER BY . Il se trouve que cela fonctionne grâce à un détail de mise en œuvre.

à numéros ordinaux de garantie d'éléments dans le blanc-séparé chaîne :

SELECT id, arr[nr] AS elem, nr
FROM  (
   SELECT *, generate_subscripts(arr, 1) AS nr
   FROM  (SELECT id, string_to_array(elements, ' ') AS arr FROM tbl) t
   ) sub;

ou plus simple si basé sur un tableau actuel :

SELECT id, arr[nr] AS elem, nr
FROM  (SELECT *, generate_subscripts(arr, 1) AS nr FROM tbl) t;

Liés à la réponse sur le dba.SE:

Postgres 8.1 - 8.4

aucune de ces fonctionnalités ne sont disponibles, encore: RETURNS TABLE , generate_subscripts() , unnest() , array_length() .

Mais cela fonctionne:

CREATE FUNCTION f_unnest_ord(anyarray, OUT val anyelement, OUT ordinality integer)
  RETURNS SETOF record LANGUAGE sql IMMUTABLE AS
'SELECT [i], i - array_lower(,1) + 1
 FROM   generate_series(array_lower(,1), array_upper(,1)) i';

noter en particulier que l'index du tableau peut différer des positions ordinales des éléments. Considérez cette démo avec une fonction étendue :

CREATE FUNCTION f_unnest_ord_idx(anyarray, OUT val anyelement, OUT ordinality int, OUT idx int)
  RETURNS SETOF record  LANGUAGE sql IMMUTABLE AS
'SELECT [i], i - array_lower(,1) + 1, i
 FROM   generate_series(array_lower(,1), array_upper(,1)) i';

SELECT id, arr, (rec).*
FROM  (
   SELECT *, f_unnest_ord_idx(arr) AS rec
   FROM  (VALUES (1, '{a,b,c}'::text[])  --  short for: '[1:3]={a,b,c}'
               , (2, '[5:7]={a,b,c}')
               , (3, '[-9:-7]={a,b,c}')
      ) t(id, arr)
   ) sub;

 id |       arr       | val | ordinality | idx
----+-----------------+-----+------------+-----
  1 | {a,b,c}         | a   |          1 |   1
  1 | {a,b,c}         | b   |          2 |   2
  1 | {a,b,c}         | c   |          3 |   3
  2 | [5:7]={a,b,c}   | a   |          1 |   5
  2 | [5:7]={a,b,c}   | b   |          2 |   6
  2 | [5:7]={a,b,c}   | c   |          3 |   7
  3 | [-9:-7]={a,b,c} | a   |          1 |  -9
  3 | [-9:-7]={a,b,c} | b   |          2 |  -8
  3 | [-9:-7]={a,b,c} | c   |          3 |  -7

comparer:

126
répondu Erwin Brandstetter 2017-05-23 11:54:50

, Essayez:

select v.*, row_number() over (partition by id order by elem) rn from
(select
    id,
    unnest(string_to_array(elements, ',')) AS elem
 from myTable) v
7
répondu Josef K 2012-01-06 15:50:13

Utiliser Indice De Générer Des .

http://www.postgresql.org/docs/current/static/functions-srf.html#FUNCTIONS-SRF-SUBSCRIPTS

par exemple:

SELECT 
  id
  , elements[i] AS elem
  , i AS nr
FROM
  ( SELECT 
      id
      , elements
      , generate_subscripts(elements, 1) AS i
    FROM
      ( SELECT
          id
          , string_to_array(elements, ',') AS elements
        FROM
          myTable
      ) AS foo
  ) bar
;

plus simplement:

SELECT
  id
  , unnest(elements) AS elem
  , generate_subscripts(elements, 1) AS nr
FROM
  ( SELECT
      id
      , string_to_array(elements, ',') AS elements
    FROM
      myTable
  ) AS foo
;
6
répondu YujiSoftware 2012-12-28 16:58:40

si l'ordre de l'élément n'est pas important, vous pouvez ""

select 
  id, elem, row_number() over (partition by id) as nr
from (
  select
      id,
      unnest(string_to_array(elements, ',')) AS elem
  from myTable
) a
3
répondu Florin Ghita 2012-01-06 15:51:11

unnest2() comme exercice

versions plus anciennes avant pg v8.4 besoin d'un unnest() défini par l'utilisateur . Nous pouvons adapter cette ancienne fonction pour retourner des éléments ayant un indice:

CREATE FUNCTION unnest2(anyarray)
  RETURNS TABLE(v anyelement, i integer) AS
$BODY$
  SELECT [i], i
  FROM   generate_series(array_lower(,1),
                         array_upper(,1)) i;
$BODY$ LANGUAGE sql IMMUTABLE;
0
répondu Peter Krauss 2016-02-15 05:45:32