it-swarm.com.de

PostgreSQL unnest () mit Elementnummer

Wenn ich eine Spalte mit getrennten Werten habe, kann ich die Funktion unnest() verwenden:

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
...

Wie kann ich Elementnummern angeben? I.e .:

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

Ich möchte die ursprüngliche Position jedes Elements in der Quellzeichenfolge. Ich habe es mit Fensterfunktionen (row_number(), rank() etc.) versucht, aber ich bekomme immer 1. Vielleicht, weil sie sich in derselben Zeile der Quelltabelle befinden?

Ich weiß, dass es ein schlechtes Tischdesign ist. Es gehört nicht mir, ich versuche nur, es zu reparieren.

63
BartekR

Postgres 9.4 oder später

Verwenden Sie WITH ORDINALITY , um Funktionen zurückzusetzen, die das Set zurückgeben:

Wenn eine Funktion in der FROM-Klausel mit WITH ORDINALITY versehen ist, wird ein Die bigint-Spalte wird an die Ausgabe angehängt, die mit 1 und .__ beginnt. erhöht sich um 1 für jede Zeile der Funktionsausgabe. Dies ist am meisten nützlich, wenn zurückgegebene Funktionen wie UNNEST() zurückgegeben werden.

In Kombination mit dem LATERAL -Feature in S. 9.3+ und gemäß diesem Thread auf pgsql-hacker kann die obige Abfrage nun wie folgt geschrieben werden:

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 behält alle Zeilen in der linken Tabelle bei, selbst wenn der Tabellenausdruck auf der rechten Seite keine Zeilen zurückgibt. Wenn das kein Problem ist, können Sie diese ansonsten gleichwertige less verbose - Form mit einem impliziten CROSS JOIN LATERAL verwenden:

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

Oder einfacher, wenn auf einem actual-Array basiert (arr ist eine Array-Spalte):

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

Oder sogar mit minimaler Syntax:

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

a ist automatisch der Spaltenalias der Tabelle und. Der Standardname der hinzugefügten Ordinalitätsspalte lautet ordinality. Es ist jedoch besser (sicherer, sauberer), explizite Spaltenaliase und Tabellenqualifikationsspalten hinzuzufügen.

Postgres 8.4 - 9.3

Mit row_number() OVER (PARTITION BY id ORDER BY elem) erhalten Sie Nummern gemäß der Sortierreihenfolge, nicht die Ordnungszahl der ursprünglichen Ordinalposition im String.

Sie könnten einfach den ORDER BY weglassen:

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

Während dies normalerweise funktioniert und ich nie gesehen habe, wie es in einfachen Abfragen zerbrochen ist, behauptet PostgreSQL nichts über die Reihenfolge der Zeilen ohne ORDER BY. Es geschieht aufgrund eines Implementierungsdetails.

Zu Ordnungszahlen der Elemente im durch Leerzeichen getrennten string garantieren:

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;

Oder einfacher, wenn auf einem actual-Array basiert:

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

Verwandte Antwort zu dba.SE:

Postgres 8.1 - 8.4

Bisher sind keine dieser Funktionen verfügbar: RETURNS TABLE , generate_subscripts() , unnest() , array_length() .
Aber das funktioniert:

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

Beachten Sie insbesondere, dass sich der Array-Index von den Ordinalpositionen der Elemente unterscheiden kann. Betrachten Sie diese Demo mit einer erweiterten Funktion:

CREATE FUNCTION f_unnest_ord_idx(anyarray, OUT val anyelement, OUT ordinality int, OUT idx int)
  RETURNS SETOF record  LANGUAGE sql IMMUTABLE AS
'SELECT $1[i], i - array_lower($1,1) + 1, i
 FROM   generate_series(array_lower($1,1), array_upper($1,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

Vergleichen Sie:

136

Versuchen:

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
user359040

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

Zum Beispiel:

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
;

Einfacher:

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
YujiSoftware

Wenn die Reihenfolge der Elemente nicht wichtig ist, können Sie dies tun 

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
Florin Ghita

unnest2() als Übung

Ältere Versionen vor S. v8.4 benötigen eine benutzerdefinierte unnest(). Wir können diese alte Funktion anpassen, um Elemente mit einem Index zurückzugeben:

CREATE FUNCTION unnest2(anyarray)
  RETURNS TABLE(v anyelement, i integer) AS
$BODY$
  SELECT $1[i], i
  FROM   generate_series(array_lower($1,1),
                         array_upper($1,1)) i;
$BODY$ LANGUAGE sql IMMUTABLE;
0
Peter Krauss