PostgreSQL unnest () với số phần tử


90

Khi tôi có một cột có các giá trị được phân tách, tôi có thể sử dụng unnest()hàm:

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

Làm cách nào tôi có thể bao gồm số phần tử? I E:

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

Tôi muốn vị trí ban đầu của mỗi phần tử trong chuỗi nguồn. Tôi đã thử với các chức năng cửa sổ ( row_number(), rank()v.v.) nhưng tôi luôn nhận được 1. Có lẽ vì chúng ở cùng một hàng của bảng nguồn?

Tôi biết đó là một thiết kế bàn tồi. Nó không phải của tôi, tôi chỉ đang cố gắng sửa nó.

Câu trả lời:


184

Postgres 9.4 trở lên

Sử dụng WITH ORDINALITYcho các hàm trả về thiết lập:

Khi một hàm trong FROMmệnh đề có hậu tố là WITH ORDINALITY, một bigintcột được nối vào đầu ra bắt đầu từ 1 và tăng lên 1 cho mỗi hàng của đầu ra của hàm. Điều này hữu ích nhất trong trường hợp đặt các hàm trả về chẳng hạn như unnest().

Kết hợp với LATERALtính năng trong pg 9.3+ và theo chủ đề này trên pgsql-hacker , truy vấn trên hiện có thể được viết là:

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 TRUEgiữ nguyên tất cả các hàng trong bảng bên trái, ngay cả khi biểu thức bảng bên phải trả về không có hàng nào. Nếu điều đó không có gì đáng lo ngại, bạn có thể sử dụng biểu mẫu tương đương, ít dài dòng hơn với hàm ý CROSS JOIN LATERAL:

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

Hoặc đơn giản hơn nếu dựa trên một mảng thực tế ( arrlà một cột mảng):

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

Hoặc thậm chí, với cú pháp tối thiểu:

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

atự động là bí danh của bảng cột. Tên mặc định của cột thứ tự đã thêm là ordinality. Nhưng tốt hơn (an toàn hơn, rõ ràng hơn) để thêm bí danh cột rõ ràng và các cột đủ tiêu chuẩn bảng.

Postgres 8,4 - 9,3

Với việc row_number() OVER (PARTITION BY id ORDER BY elem)bạn lấy số theo thứ tự sắp xếp, không phải số thứ tự của vị trí thứ tự ban đầu trong chuỗi.

Bạn chỉ cần bỏ qua ORDER BY:

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

Trong khi điều này bình thường hoạt động và tôi chưa bao giờ thấy nó không thành công trong các truy vấn đơn giản, PostgreSQL khẳng định không có gì liên quan đến thứ tự của các hàng mà không có ORDER BY. Nó xảy ra để làm việc do một chi tiết triển khai.

Để đảm bảo số thứ tự của các phần tử trong chuỗi được phân tách bằng khoảng trống :

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;

Hoặc đơn giản hơn nếu dựa trên một mảng thực tế :

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

Câu trả lời liên quan trên dba.SE:

Postgres 8.1 - 8.4

Không ai trong số những tính năng có sẵn, tuy nhiên: RETURNS TABLE, generate_subscripts(), unnest(), array_length(). Nhưng điều này hoạt động:

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

Đặc biệt lưu ý rằng chỉ số mảng có thể khác với vị trí thứ tự của các phần tử. Hãy xem xét bản trình diễn này với một chức năng mở rộng :

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

So sánh:


10
Câu trả lời này là một trong những câu trả lời toàn diện nhất trong SO, liên quan đến PostgreSQL. Cảm ơn Erwin.
Alexandros

Chúng ta có thể điều chỉnh hàm unnest2 bên dưới thành một bảng trả về thực (không phải hàng giả), trong các phiên bản pg mới không?
Peter Krauss

@ erwin-brandstetter, bạn vui lòng giải thích tại sao / nếu WITH ORDINALITYđược ưu tiên hơn generate_subscripts()? Đối với tôi, nó có vẻ generate_subscripts()tốt hơn vì nó hiển thị vị trí phần tử thực tế trong mảng. Điều này rất hữu ích, chẳng hạn như khi cập nhật mảng ... tôi có nên sử dụng WITH ORDINALITYthay thế không?
lạc ngựa vào

1
@losthorse: Tôi sẽ phác thảo nó như thế này: WITH ORDINALITYlà giải pháp chung để lấy số hàng cho bất kỳ hàm trả về tập hợp nào trong một truy vấn SQL. Đó là cách nhanh nhất, đáng tin cậy và nó cũng hoạt động hoàn hảo cho các mảng 1 chiều, 1 chiều (mặc định cho mảng Postgres, hãy xem xét điều này ). Nếu bạn làm việc với bất kỳ loại mảng nào khác (hầu hết mọi người thì không) và bạn thực sự cần bảo tồn / làm việc với các chỉ số dưới ban đầu, thì đây generate_subscripts()là cách để thực hiện. Nhưng unnest()flattens everytihng để bắt đầu với ...
Erwin Brandstetter

1
@ z0r_ Sách hướng dẫn: 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

Thử:

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

Sử dụng các chức năng tạo chỉ số .
http://www.postgresql.org/docs/current/static/functions-srf.html#FUNCTIONS-SRF-SUBSCRIPTS

Ví dụ:

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
;

Đơn giản hơn:

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

Nếu thứ tự của phần tử không quan trọng, bạn có thể

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() như bài tập

Các phiên bản cũ hơn trước pg v8.4 cần người dùng xác định unnest(). Chúng ta có thể điều chỉnh hàm cũ này để trả về các phần tử có chỉ mục:

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
Điều này sẽ không hoạt động trước pg v8.4, bởi vì RETURNS TABLEchưa có. Tôi đã thêm một chương vào câu trả lời của mình thảo luận về một giải pháp.
Erwin Brandstetter

1
@ErwinBrandstetter, câu trả lời của bạn rất khoa học, và bạn đang đánh bóng một văn bản của 4 năm trước (!) ... Bạn có đang viết một cuốn sách PostgreSQL bằng văn bản SO của mình không? :-)
Peter Krauss

Xin chào tất cả, nó là một Wiki, bạn có thể chỉnh sửa (!) ... Nhưng ok, tôi đã sửa thành setof record.
Peter Krauss
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.