Öğe numarası ile PostgreSQL unnest ()


90

Ayrı değerlere sahip bir sütunum olduğunda, şu unnest()işlevi kullanabilirim :

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

Eleman numaralarını nasıl ekleyebilirim? Yani:

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

Kaynak dizedeki her öğenin orijinal konumunu istiyorum . Ben pencere fonksiyonları (ile denedim row_number(), rank()vb) ama her zaman olsun 1. Belki de kaynak tablonun aynı satırında oldukları için?

Bunun kötü bir masa tasarımı olduğunu biliyorum. Benim değil, sadece düzeltmeye çalışıyorum.

Yanıtlar:


184

Postgres 9.4 veya üstü

WITH ORDINALITYKüme geri dönüş işlevleri için kullanın :

FROMCümlede bir işlevin sonuna eklendiğinde, çıktıya 1'den başlayan ve işlevin çıktısının her satırı için 1 artan WITH ORDINALITYbir bigintsütun eklenir. Bu, en çok gibi küme döndürme işlevlerinde yararlıdır unnest().

LATERAL9.3+ sayfasındaki özellik ile birlikte ve pgsql-hacker'lardaki bu konuya göre , yukarıdaki sorgu artık şu şekilde yazılabilir:

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 TRUESağdaki tablo ifadesi hiç satır döndürmese bile soldaki tablodaki tüm satırları korur. Bu endişe verici değilse, aksi takdirde eşdeğer olan, daha az ayrıntılı formu örtük bir şekilde kullanabilirsiniz CROSS JOIN LATERAL:

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

Veya gerçek bir diziye dayalıysa ( arrbir dizi sütunu ise) daha basittir :

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

Veya minimum sözdizimiyle:

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

aotomatik olarak tablo ve sütun takma adıdır. Eklenen sıra sütununun varsayılan adı ordinality. Ancak, açık sütun takma adları ve tabloya uygun sütunlar eklemek daha iyidir (daha güvenli, daha temiz).

Postgres 8.4 - 9.3

İle , dizedeki orijinal sıra konumununrow_number() OVER (PARTITION BY id ORDER BY elem) sıra sayısına göre değil, sıralama düzenine göre sayılar alırsınız .

Basitçe atlayabilirsiniz ORDER BY:

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

Bu normalde işe yarasa ve basit sorgularda başarısız olduğunu hiç görmedim, PostgreSQL satırların sırasına ilişkin hiçbir şey öne sürmez. ORDER BY . Bir uygulama detayı nedeniyle işe yarıyor.

Boşluklarla ayrılmış dizede sıra sayılarını garanti etmek için :

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;

Veya gerçek bir diziye dayalıysa daha basit :

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

Dba.SE ile ilgili cevap:

Postgres 8.1 - 8.4

Bu özelliklerin hiçbiri henüz kullanılabilir: RETURNS TABLE, generate_subscripts(), unnest(), array_length(). Ancak bu işe yarar:

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

Özellikle, dizi indeksinin öğelerin sıra konumlarından farklı olabileceğini unutmayın. Bu demoyu genişletilmiş bir işlevle düşünün :

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

Karşılaştırmak:


10
Bu cevap, PostgreSQL ile ilgili olarak SO'daki en kapsamlı cevaplardan biridir. Teşekkürler Erwin.
Alexandros

Aşağıdaki unnest2 işlevini yeni pg sürümlerinde gerçek bir tablo dönüşüne (sahte satırlara değil) uyarlayabilir miyiz ?
Peter Krauss

@ erwin-brandstetter, lütfen neden / WITH ORDINALITYtercih edilip edilmediğini açıklar generate_subscripts()mısınız? Dizideki generate_subscripts()gerçek eleman konumunu gösterdiği için bana göre daha iyi görünüyor . Bu yararlıdır, örneğin, diziyi güncellerken ... WITH ORDINALITYbunun yerine kullanmalı mıyım?
losthorse

1
@losthorse: Bunu şu şekilde özetleyeceğim: SQL sorgusunda herhangi bir set döndürme işlevi WITH ORDINALITYiçin satır numaralarını elde etmenin genel çözümü . Bu en hızlı ve güvenilir yoldur ve aynı zamanda 1 boyutlu, 1 tabanlı diziler için de mükemmel şekilde çalışır (Postgres dizileri için varsayılan, bunu düşünün ). Eğer siz diziler başka türlü (çoğu insan yok) ile çalışır ve aslında özgün alt simgeler ile / işi korumak gerekir, o zaman gitmek yoludur. Ama başlamak için her şeyi generate_subscripts()unnest()
düzeltir

1
@ z0r_ Kılavuz: 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

Deneyin:

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

6

Alt Simge Oluşturma İşlevlerini kullanın .
http://www.postgresql.org/docs/current/static/functions-srf.html#FUNCTIONS-SRF-SUBSCRIPTS

Örneğin:

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
;

Daha basit:

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
;

3

Öğenin sırası önemli değilse şunları yapabilirsiniz:

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

0

unnest2() egzersiz olarak

Pg v8.4'ten önceki eski sürümler kullanıcı tanımlı olmalıdır unnest(). Bu eski işlevi, indisli elemanlar döndürecek şekilde uyarlayabiliriz:

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;

2
Bu, pg v8.4'ten önce çalışmaz, çünkü RETURNS TABLEhenüz yoktur. Cevabıma bir çözümü tartışan bir bölüm ekledim.
Erwin Brandstetter

1
@ErwinBrandstetter, cevaplarınız çok öğretici ve 4 yıl önceki bir metni parlatıyorsunuz (!) ... SO metinlerinizi kullanarak bir PostgreSQL kitabı mı yazıyorsunuz? :-)
Peter Krauss

Merhabalar, bu bir Wiki, düzenleyebilirsiniz (!) ... Ama tamam, olarak düzelttim setof record.
Peter Krauss
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.