PostgreSQL unest () dengan nomor elemen

90

Ketika saya memiliki kolom dengan nilai terpisah, saya dapat menggunakan unnest()fungsi:

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

Bagaimana cara memasukkan nomor elemen? Yaitu:

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

Saya ingin posisi asli setiap elemen dalam string sumber. Saya sudah mencoba dengan fungsi jendela ( row_number(), rank()dll.) Tetapi saya selalu mendapatkannya 1. Mungkin karena mereka berada di baris yang sama dari tabel sumber?

Saya tahu itu desain meja yang buruk. Ini bukan milikku, aku hanya mencoba memperbaikinya.

BartekR
sumber

Jawaban:

184

Postgres 9.4 atau lebih baru

Gunakan WITH ORDINALITYuntuk fungsi set-return:

Jika fungsi dalam FROMklausa diakhiri dengan WITH ORDINALITY, bigintkolom ditambahkan ke keluaran yang dimulai dari 1 dan bertambah 1 untuk setiap baris keluaran fungsi. Ini paling berguna dalam kasus set mengembalikan fungsi seperti unnest().

Dalam kombinasi dengan LATERALfitur di pg 9.3+ , dan menurut utas ini di pgsql-hackers , kueri di atas sekarang dapat ditulis sebagai:

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 TRUEmempertahankan semua baris di tabel kiri, meskipun ekspresi tabel di kanan tidak menghasilkan baris. Jika itu bukan urusan Anda, Anda dapat menggunakan bentuk yang setara ini, lebih sedikit verbose dengan implisit CROSS JOIN LATERAL:

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

Atau lebih sederhana jika didasarkan pada larik sebenarnya ( arrberupa kolom larik):

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

Atau bahkan, dengan sintaks minimal:

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

asecara otomatis menjadi alias tabel dan kolom. Nama default dari kolom ordinalitas yang ditambahkan adalah ordinality. Tapi lebih baik (lebih aman, lebih bersih) menambahkan alias kolom eksplisit dan kolom kualifikasi tabel.

Postgres 8.4 - 9.3

Dengan row_number() OVER (PARTITION BY id ORDER BY elem)Anda mendapatkan nomor sesuai dengan urutan urutan, bukan nomor urut dari posisi urutan asli dalam string.

Anda cukup menghilangkan ORDER BY:

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

Meskipun ini biasanya berfungsi dan saya tidak pernah melihatnya gagal dalam kueri sederhana, PostgreSQL tidak menegaskan apa pun tentang urutan baris tanpa ORDER BY. Itu terjadi untuk bekerja karena detail implementasi.

Untuk menjamin nomor urut elemen dalam string yang dipisahkan kosong :

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;

Atau lebih sederhana jika didasarkan pada larik sebenarnya :

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

Jawaban terkait di dba.SE:

Postgres 8.1 - 8.4

Tak satu pun dari fitur ini tersedia, namun: RETURNS TABLE, generate_subscripts(), unnest(), array_length(). Tapi ini berhasil:

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';

Perhatikan secara khusus, bahwa indeks larik dapat berbeda dari posisi ordinal elemen. Pertimbangkan demo ini dengan fungsi tambahan :

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

Membandingkan:

Erwin Brandstetter
sumber
10
Jawaban ini adalah salah satu jawaban terlengkap di SO, terkait PostgreSQL. Terima kasih Erwin.
Alexandros
Bisakah kita mengadaptasi fungsi unest2 di bawah ini menjadi pengembalian tabel nyata (bukan baris palsu), di versi pg baru?
Peter Krauss
@ erwin-brandstetter, bisakah Anda menjelaskan mengapa / jika WITH ORDINALITYlebih disukai generate_subscripts()? Sepertinya saya generate_subscripts()lebih baik karena ini menunjukkan lokasi elemen sebenarnya dalam array. Ini berguna, misalnya, saat memperbarui larik ... haruskah saya menggunakan WITH ORDINALITY?
kuda hilang
1
@ Hantu kuda: Saya akan menjelaskannya seperti ini: WITH ORDINALITYadalah solusi umum untuk mendapatkan nomor baris untuk setiap fungsi yang mengembalikan set dalam kueri SQL. Ini adalah cara tercepat, andal, dan juga bekerja dengan sempurna untuk array 1-dimensi, berbasis 1 (default untuk array Postgres, pertimbangkan ini ). Jika Anda bekerja dengan jenis array lain (kebanyakan orang tidak), dan Anda benar-benar perlu mempertahankan / bekerja dengan subskrip asli, maka generate_subscripts()itulah cara yang harus dilakukan. Tapi unnest()meratakan segalanya untuk memulai dengan ...
Erwin Brandstetter
1
@ z0r_ Manual: 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

Mencoba:

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

sumber
6

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

Sebagai contoh:

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
;

Lebih sederhananya:

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
sumber
3

Jika urutan elemen tidak penting, Anda bisa

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
sumber
0

unnest2() sebagai latihan

Versi lama sebelum pg v8.4 membutuhkan definisi pengguna unnest(). Kita dapat mengadaptasi fungsi lama ini untuk mengembalikan elemen dengan indeks:

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
sumber
2
Ini tidak akan berfungsi sebelum pg v8.4, karena RETURNS TABLEbelum ada. Saya menambahkan satu bab ke jawaban saya yang membahas solusi.
Erwin Brandstetter
1
@ErwinBrandstetter, jawaban Anda sangat didaktik, dan Anda memoles teks 4 tahun yang lalu (!) ... Apakah Anda sedang menulis buku PostgreSQL menggunakan teks SO Anda? :-)
Peter Krauss
Halo semuanya, ini Wiki, Anda dapat mengedit (!) ... Tapi oke, saya mengoreksi ke setof record.
Peter Krauss