Làm thế nào để biến mảng json thành mảng postgres?


69

Tôi có một cột datachứa một jsontài liệu đại khái như thế này:

{
    "name": "foo",
    "tags": ["foo", "bar"]
}

Tôi muốn biến tagsmảng lồng nhau thành một chuỗi nối ( foo, bar). Điều đó sẽ dễ dàng có thể với array_to_string()chức năng trong lý thuyết. Tuy nhiên, chức năng này không hoạt động trên jsonmảng. Vì vậy, tôi tự hỏi làm thế nào để biến jsonmảng này thành một Postgres array?


json_extract_path_text(your_column, 'tags') những gì bạn đang tìm kiếm?
a_horse_with_no_name

1
@a_horse_with_no_name: Vấn đề còn lại: các phần tử mảng vẫn được trích dẫn cho định dạng JSON. Văn bản không được trích xuất chính xác ...
Erwin Brandstetter

Câu trả lời:


94

Postgres 9,4 hoặc mới hơn

Rõ ràng lấy cảm hứng từ bài đăng này , Postgres 9.4 đã thêm (các) chức năng còn thiếu:
Cảm ơn Laurence Rowe cho bản vá và Andrew Dunstan đã cam kết!

Để hủy bỏ mảng JSON. Sau đó sử dụng array_agg()hoặc một hàm tạo ARRAY để xây dựng một mảng Postgres từ nó. Hoặc string_agg()để xây dựng một text chuỗi .

Tổng hợp các phần tử không được kiểm tra trên mỗi hàng trong một LATERALtruy vấn con tương quan. Sau đó, thứ tự ban đầu được giữ nguyên và chúng tôi không cần ORDER BY, GROUP BYhoặc thậm chí là một khóa duy nhất trong truy vấn bên ngoài. Xem:

Thay thế 'json' bằng 'jsonb' jsonbtrong tất cả các mã SQL sau.

SELECT t.tbl_id, d.list
FROM   tbl t
CROSS  JOIN LATERAL (
   SELECT string_agg(d.elem::text, ', ') AS list
   FROM   json_array_elements_text(t.data->'tags') AS d(elem)
   ) d;

Cú pháp ngắn:

SELECT t.tbl_id, d.list
FROM   tbl t, LATERAL (
   SELECT string_agg(value::text, ', ') AS list
   FROM   json_array_elements_text(t.data->'tags')  -- col name default: "value"
   ) d;

Liên quan:

Trình xây dựng ARRAY trong truy vấn con tương quan:

SELECT tbl_id, ARRAY(SELECT json_array_elements_text(t.data->'tags')) AS txt_arr
FROM   tbl t;

Liên quan:

Sự khác biệt tinh tế : nullcác yếu tố được bảo tồn trong các mảng thực tế . Điều này là không thể trong các truy vấn trên tạo ra một textchuỗi, không thể chứa nullcác giá trị. Các đại diện thực sự là một mảng.

Chức năng bao bọc

Để sử dụng nhiều lần, để làm cho điều này thậm chí đơn giản hơn, hãy gói gọn logic trong một hàm:

CREATE OR REPLACE FUNCTION json_arr2text_arr(_js json)
  RETURNS text[] LANGUAGE sql IMMUTABLE AS
'SELECT ARRAY(SELECT json_array_elements_text(_js))';

Làm cho nó trở thành một hàm SQL , để nó có thể được nội tuyến trong các truy vấn lớn hơn.
Làm cho nó IMMUTABLE(bởi vì nó là) để tránh đánh giá lặp đi lặp lại trong các truy vấn lớn hơn và cho phép nó trong các biểu thức chỉ mục.

Gọi:

SELECT tbl_id, json_arr2text_arr(data->'tags')
FROM   tbl;

db <> fiddle ở đây


Postgres 9.3 trở lên

Sử dụng chức năng json_array_elements(). Nhưng chúng tôi nhận được chuỗi trích dẫn gấp đôi từ nó.

Truy vấn thay thế với tổng hợp trong truy vấn bên ngoài. CROSS JOINloại bỏ các hàng có mảng bị thiếu hoặc trống. Cũng có thể hữu ích cho việc xử lý các yếu tố. Chúng tôi cần một khóa duy nhất để tổng hợp:

SELECT t.tbl_id, string_agg(d.elem::text, ', ') AS list
FROM   tbl t
CROSS  JOIN LATERAL json_array_elements(t.data->'tags') AS d(elem)
GROUP  BY t.tbl_id;

Trình xây dựng ARRAY, vẫn có các chuỗi được trích dẫn:

SELECT tbl_id, ARRAY(SELECT json_array_elements(t.data->'tags')) AS quoted_txt_arr
FROM   tbl t;

Lưu ý rằng nullđược chuyển đổi thành giá trị văn bản "null", không giống như ở trên. Không chính xác, nói đúng, và có khả năng mơ hồ.

Người đàn ông nghèo không có quyền với trim():

SELECT t.tbl_id, string_agg(trim(d.elem::text, '"'), ', ') AS list
FROM   tbl t, json_array_elements(t.data->'tags') d(elem)
GROUP  BY 1;

Lấy một hàng từ tbl:

SELECT string_agg(trim(d.elem::text, '"'), ', ') AS list
FROM   tbl t, json_array_elements(t.data->'tags') d(elem)
WHERE  t.tbl_id = 1;

Chuỗi hình thành truy vấn con tương quan:

SELECT tbl_id, (SELECT string_agg(trim(value::text, '"'), ', ')
                FROM   json_array_elements(t.data->'tags')) AS list
FROM   tbl t;

Nhà xây dựng ARRAY:

SELECT tbl_id, ARRAY(SELECT trim(value::text, '"')
                     FROM   json_array_elements(t.data->'tags')) AS txt_arr
FROM   tbl t;

Fiddle SQL gốc (lỗi thời) .
db <> fiddle ở đây.

Liên quan:

Ghi chú (lỗi thời kể từ trang 9,4)

Chúng ta sẽ cần một json_array_elements_text(json)cặp sinh đôi json_array_elements(json)để trả về các textgiá trị phù hợp từ một mảng JSON. Nhưng điều đó dường như bị thiếu trong kho vũ khí được cung cấp của các hàm JSON . Hoặc một số hàm khác để trích xuất một textgiá trị từ JSONgiá trị vô hướng . Tôi dường như đang thiếu cái đó
Vì vậy, tôi đã ứng biến trim(), nhưng điều đó sẽ thất bại đối với những trường hợp không tầm thường ...


Bài đăng tốt như mọi khi, nhưng với kiến ​​thức của bạn về nội bộ tại sao không phải là diễn viên từ mảng-> jsonb ở đó. Tôi có thể hiểu không thực hiện các diễn viên khác bởi vì mảng sql được gõ mạnh hơn. Có phải chỉ vì PostgreSQL không thích tự động tạo mã để truyền (int [], bigint [], text []), v.v.
Evan Carroll

3
@Evan: Bạn sẽ sử dụng to_jsonb()cho mảng-> chuyển đổi jsonb.
Erwin Brandstetter

SELECT ARRAY(SELECT json_array_elements_text(_js))thực sự đảm bảo rằng thứ tự của mảng được bảo tồn? Không phải trình tối ưu hóa được phép thay đổi về mặt lý thuyết thứ tự của các hàng đi ra từ json_array_elements bản sao?
Felix Geisendorfer

@Felix: không có bảo đảm chính thức trong tiêu chuẩn SQL. (sau đó, một lần nữa, thiết lập các hàm trả về thậm chí không được phép trong danh sách CHỌN trong SQL chuẩn để bắt đầu.) nhưng có một xác nhận không chính thức trong hướng dẫn Postgres. xem: dba.stackexchange.com/a/185862/3684 Để được rõ ràng - với chi phí của một hình phạt nước hoa nhỏ - xem: dba.stackexchange.com/a/27287/3684 . Cá nhân, tôi chắc chắn 100% biểu thức cụ thể này hoạt động như mong đợi trong mọi phiên bản hiện tại và tương lai của Postgres kể từ ngày 9.4.
Erwin Brandstetter

@ErwinBrandstetter cảm ơn bạn rất nhiều vì đã xác nhận điều này! Tôi hiện đang thực hiện một số nghiên cứu cho một bài viết tóm tắt các đảm bảo đặt hàng chính thức và không chính thức được cung cấp bởi PostgreQuery và câu trả lời của bạn rất hữu ích! Nếu bạn muốn xem lại bài viết, hãy cho tôi biết, nhưng đừng lo lắng nếu không. Tôi vô cùng biết ơn những đóng góp của StackOverflow của bạn và đã học được rất nhiều từ bạn trong những năm qua!
Felix Geisendorfer

16

PG 9,4 trở lên

Câu trả lời được chấp nhận chắc chắn là những gì bạn cần, nhưng vì mục đích đơn giản, đây là một trợ giúp tôi sử dụng cho việc này:

CREATE OR REPLACE FUNCTION jsonb_array_to_text_array(
  p_input jsonb
) RETURNS TEXT[] AS $BODY$

DECLARE v_output text[];

BEGIN

  SELECT array_agg(ary)::text[]
  INTO v_output
  FROM jsonb_array_elements_text(p_input) AS ary;

  RETURN v_output;

END;

$BODY$
LANGUAGE plpgsql VOLATILE;

Sau đó, chỉ cần làm:

SELECT jsonb_array_to_text_array('["a", "b", "c"]'::jsonb);

Tôi đã thêm một số biểu thức nhanh hơn vào câu trả lời của tôi và một chức năng đơn giản hơn. Điều này có thể rẻ hơn đáng kể.
Erwin Brandstetter

4
Hàm này phải là SQL thuần để trình tối ưu hóa có thể nhìn vào nó. Không cần sử dụng pgplsql ở đây.
Chia

8

Câu hỏi này đã được hỏi trong danh sách gửi thư của PostgreSQL và tôi đã nghĩ ra cách chuyển đổi văn bản JSON này thành loại văn bản PostgreQuery thông qua toán tử trích xuất trường JSON:

CREATE FUNCTION json_text(json) RETURNS text IMMUTABLE LANGUAGE sql
AS $$ SELECT ('['||$1||']')::json->>0 $$;

db=# select json_text(json_array_elements('["hello",1.3,"\u2603"]'));
 json_text 
-----------
 hello
 1.3
 

Về cơ bản, nó chuyển đổi giá trị thành một mảng phần tử đơn và sau đó yêu cầu phần tử đầu tiên.

Một cách tiếp cận khác là sử dụng toán tử này để trích xuất từng trường một. Nhưng đối với các mảng lớn thì điều này có thể chậm hơn, vì nó cần phân tích toàn bộ chuỗi JSON cho từng thành phần mảng, dẫn đến độ phức tạp O (n ^ 2).

CREATE FUNCTION json_array_elements_text(json) RETURNS SETOF text IMMUTABLE LANGUAGE sql
AS $$ SELECT $1->>i FROM generate_series(0, json_array_length($1)-1) AS i $$;

db=# select json_array_elements_text('["hello",1.3,"\u2603"]');
 json_array_elements_text 
--------------------------
 hello
 1.3
 

1

Tôi đã thử nghiệm một vài lựa chọn. Đây là truy vấn yêu thích của tôi. Giả sử chúng ta có một bảng chứa trường id và json. Trường json chứa mảng, mà chúng ta muốn biến thành mảng pg.

SELECT * 
FROM   test 
WHERE  TRANSLATE(jsonb::jsonb::text, '[]','{}')::INT[] 
       && ARRAY[1,2,3];

Nó hoạt động ở bất cứ đâu và nhanh hơn những nơi khác, nhưng trông có vẻ khó khăn)

Đầu tiên mảng json được truyền dưới dạng văn bản, và sau đó chúng ta chỉ cần thay đổi dấu ngoặc vuông thành dấu ngoặc đơn. Cuối cùng, văn bản đang được chọn là mảng của loại yêu cầu.

SELECT TRANSLATE('[1]'::jsonb::text, '[]','{}')::INT[];

và nếu bạn thích mảng văn bản []

SELECT TRANSLATE('[1]'::jsonb::text, '[]','{}')::TEXT[];

2
SELECT TRANSLATE('{"name": "foo", "tags": ["foo", "bar"]}'::jsonb::text, '[]','{}')::INT[]; ERROR: malformed array literal: "{"name": "foo", "tags": {"foo", "bar"}}"Tôi nghĩ rằng bạn phải thêm một số lời giải thích về cách thức này được cho là hoạt động.
dezso

Câu hỏi là làm thế nào để biến mảng JSON (!) Thành mảng pg. Giả sử tôi có bảng chứa cột id và jsonb. Cột JSONb chứa mảng json. Sau đó
FryptCliff

TRANSLATE (jsonb :: jsonb :: text, '[]', '{}') :: INT [] chuyển đổi mảng json thành mảng pg.
FryptCliff

SELECT translate('["foo", "bar"]'::jsonb::text, '[]','{}')::INT[]; ERROR: invalid input syntax for integer: "foo"Nó không phải là chống bom ...
dezso 6/12/2016

Cân nhắc sử dụng văn bản [] cho các mảng này
FryptCliff

0

Một vài chức năng, được lấy từ câu trả lời cho câu hỏi này , là những gì tôi đang sử dụng và chúng đang hoạt động rất tốt

CREATE OR REPLACE FUNCTION json_array_casttext(json) RETURNS text[] AS $f$
    SELECT array_agg(x) || ARRAY[]::text[] FROM json_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE FUNCTION jsonb_array_casttext(jsonb) RETURNS text[] AS $f$
    SELECT array_agg(x) || ARRAY[]::text[] FROM jsonb_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE FUNCTION json_array_castint(json) RETURNS int[] AS $f$
    SELECT array_agg(x)::int[] || ARRAY[]::int[] FROM json_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE FUNCTION jsonb_array_castint(jsonb) RETURNS int[] AS $f$
    SELECT array_agg(x)::int[] || ARRAY[]::int[] FROM jsonb_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

Trong mỗi người trong số họ, bằng cách ghép nối với một mảng trống, họ xử lý một trường hợp khiến tôi phải suy nghĩ một chút, trong đó nếu bạn cố gắng và tạo ra một mảng trống từ json/ jsonbkhông có nó, bạn sẽ không nhận được gì, thay vì một mảng trống ( {}) như bạn mong đợi. Tôi chắc chắn có một số tối ưu hóa cho họ, nhưng họ để lại vì đơn giản trong việc giải thích khái niệm này.

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.