PostgreSQL unnest () mit Elementnummer

89

Wenn ich eine Spalte mit getrennten Werten habe, kann ich die unnest()Funktion 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 einfügen? Dh:

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 ( usw.) versucht row_number(), rank()aber ich bekomme immer 1. Vielleicht, weil sie sich in derselben Zeile der Quelltabelle befinden?

Ich weiß, dass es ein schlechtes Tischdesign ist. Es ist nicht meins, ich versuche nur, es zu reparieren.

BartekR
quelle

Antworten:

181

Postgres 9.4 oder höher

Verwendung WITH ORDINALITYfür Set-Return-Funktionen:

Wenn eine Funktion in der FROMKlausel mit einem Suffix versehen wird WITH ORDINALITY, wird eine bigintSpalte an die Ausgabe angehängt, die bei 1 beginnt und für jede Zeile der Funktionsausgabe um 1 erhöht wird. Dies ist am nützlichsten bei gesetzten Rückgabefunktionen wie z unnest().

In Kombination mit der LATERALFunktion in Seite 9.3+ und gemäß diesem Thread zu pgsql-Hackern kann die obige Abfrage jetzt 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 TRUEbehält alle Zeilen in der linken Tabelle bei, auch wenn der Tabellenausdruck rechts keine Zeilen zurückgibt. Wenn dies kein Problem darstellt, können Sie diese ansonsten äquivalente, weniger ausführliche Form mit einem impliziten Wert verwenden CROSS JOIN LATERAL:

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

Oder einfacher, wenn es auf einem tatsächlichen Array basiert ( arreine 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;

aist automatisch Tabellen- und Spaltenalias. Der Standardname der hinzugefügten Ordinalitätsspalte lautet ordinality. Es ist jedoch besser (sicherer, sauberer), explizite Spaltenaliasnamen und Tabellenqualifizierungsspalten hinzuzufügen.

Postgres 8.4 - 9.3

Mit erhalten row_number() OVER (PARTITION BY id ORDER BY elem)Sie Zahlen gemäß der Sortierreihenfolge, nicht die Ordnungszahl der ursprünglichen Ordnungsposition in der Zeichenfolge.

Sie können einfach weglassen ORDER BY:

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 noch nie gesehen habe, dass es bei einfachen Abfragen fehlschlägt, behauptet PostgreSQL nichts bezüglich der Reihenfolge der Zeilen ohne ORDER BY. Es funktioniert aufgrund eines Implementierungsdetails.

So garantieren Sie die Ordnungszahl von Elementen in der durch Leerzeichen getrennten Zeichenfolge :

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 es auf einem tatsächlichen Array basiert :

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

Verwandte Antwort auf dba.SE:

Postgres 8.1 - 8.4

Keine dieser Funktionen stehen zur Verfügung, doch: 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 der Array-Index von den Ordnungspositionen der Elemente abweichen 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:

Erwin Brandstetter
quelle
10
Diese Antwort ist eine der umfassendsten Antworten in SO in Bezug auf PostgreSQL. Danke Erwin.
Alexandros
Können wir die unten stehende Funktion unnest2 in den neuen pg-Versionen an eine echte Tabellenrückgabe (keine gefälschten Zeilen) anpassen ?
Peter Krauss
@ erwin-brandstetter, würdest du bitte näher erläutern warum / ob WITH ORDINALITYbevorzugt wird generate_subscripts()? Es sieht für mich generate_subscripts()besser aus, da es die tatsächliche Elementposition im Array anzeigt. Dies ist zum Beispiel nützlich, wenn Sie das Array aktualisieren ... sollte ich WITH ORDINALITYstattdessen verwenden?
Losthorse
1
@losthorse: Ich würde es so umreißen: WITH ORDINALITYist die allgemeine Lösung, um Zeilennummern für jede festgelegte Rückgabefunktion in einer SQL-Abfrage abzurufen. Es ist der schnellste, zuverlässigste Weg , und es ist auch noch perfekt für 1-dimenstional, 1-basierte Arrays (Standard für Postgres - Arrays arbeiten betrachten dies ). Wenn Sie mit einer anderen Art von Arrays arbeiten (die meisten Leute tun dies nicht) und tatsächlich die ursprünglichen Indizes beibehalten / damit arbeiten müssen, generate_subscripts()ist dies der richtige Weg. Aber unnest()flacht zunächst alles ab ...
Erwin Brandstetter
1
@ z0r_ Das Handbuch: Table functions appearing in FROM can also be preceded by the key word LATERAL, but for functions the key word is optional; the function's arguments can contain references to columns provided by preceding FROM items in any case.
Erwin Brandstetter
9

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

quelle
6

Verwenden Sie Funktionen zum Generieren von Indizes .
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
;
YujiSoftware
quelle
3

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

select 
  id, elem, row_number() over (partition by id) as nr
from (
  select
      id,
      unnest(string_to_array(elements, ',')) AS elem
  from myTable
) a
Florin Ghita
quelle
0

unnest2() als Übung

Ältere Versionen vor pg 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 setof record  AS
$BODY$
  SELECT $1[i], i
  FROM   generate_series(array_lower($1,1),
                         array_upper($1,1)) i;
$BODY$ LANGUAGE sql IMMUTABLE;
Peter Krauss
quelle
2
Dies würde vor Seite 8.4 nicht funktionieren, da dies noch nicht der Fall ist RETURNS TABLE. Ich habe meiner Antwort ein Kapitel hinzugefügt, in dem eine Lösung besprochen wird.
Erwin Brandstetter
1
@ErwinBrandstetter, Ihre Antworten sind sehr didaktisch und Sie polieren einen Text von vor 4 Jahren (!) ... Schreiben Sie ein PostgreSQL-Buch mit Ihren SO-Texten? :-)
Peter Krauss
Hallo zusammen, es ist ein Wiki, das du bearbeiten kannst (!) ... Aber ok, ich habe es korrigiert setof record.
Peter Krauss