Bagaimana mengubah json array menjadi postgres array?


69

Saya memiliki kolom datayang menyimpan jsondokumen kira-kira seperti ini:

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

Saya ingin mengubah tagsarray bersarang menjadi string bersambung ( foo, bar). Itu akan dengan mudah dimungkinkan dengan array_to_string()fungsi dalam teori. Namun, fungsi ini tidak bekerja pada jsonarray. Jadi saya bertanya-tanya bagaimana cara mengubah jsonarray ini menjadi Postgres array?


Apakah json_extract_path_text(your_column, 'tags') yang Anda cari?
a_horse_with_no_name

1
@a_horse_with_no_name: Masalah yang tersisa: elemen array masih dikutip untuk format JSON. Teks tidak diekstraksi dengan benar ...
Erwin Brandstetter

Jawaban:


94

Postgres 9.4 atau lebih baru

Jelas terinspirasi oleh posting ini , Postgres 9.4 menambahkan fungsi yang hilang:
Terima kasih kepada Laurence Rowe untuk patch dan Andrew Dunstan untuk melakukan!

Untuk membatalkan array JSON. Kemudian gunakan array_agg()atau konstruktor ARRAY untuk membangun array Postgres darinya. Atau string_agg()untuk membangun text string .

Agregasikan elemen yang tidak diuji per baris dalam LATERALsubquery yang berkorelasi. Kemudian pesanan asli dipertahankan dan kami tidak perlu ORDER BY, GROUP BYatau bahkan kunci unik di kueri luar. Lihat:

Ganti 'json' dengan 'jsonb' untuk jsonbsemua kode SQL berikut.

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;

Sintaks pendek:

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;

Terkait:

Konstruktor ARRAY dalam subquery berkorelasi:

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

Terkait:

Perbedaan halus : nullelemen dipertahankan dalam array yang sebenarnya . Ini tidak mungkin dalam kueri di atas yang menghasilkan textstring, yang tidak bisa berisi nullnilai. The representasi sejati adalah array.

Fungsi pembungkus

Untuk penggunaan berulang, untuk membuatnya lebih sederhana, merangkum logika dalam suatu fungsi:

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

Jadikan sebagai fungsi SQL , sehingga bisa diuraikan dalam kueri yang lebih besar.
Buatlah IMMUTABLE(karena memang demikian) untuk menghindari evaluasi berulang dalam permintaan yang lebih besar dan memungkinkannya dalam ekspresi indeks.

Panggilan:

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

db <> biola di sini


Postgres 9.3 atau lebih tua

Gunakan fungsinya json_array_elements(). Tapi kami mendapatkan string yang dikutip ganda .

Kueri alternatif dengan agregasi dalam kueri luar. CROSS JOINmenghapus baris dengan array yang hilang atau kosong. Semoga bermanfaat untuk memproses elemen. Kami membutuhkan kunci unik untuk agregat:

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;

Konstruktor ARRAY, masih dengan string yang dikutip:

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

Catatan yang nulldikonversi ke nilai teks "null", tidak seperti di atas. Tidak benar, benar-benar berbicara, dan berpotensi ambigu.

Orang miskin tidak mengutip dengan 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;

Ambil satu baris dari 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;

String membentuk subquery yang dikorelasikan:

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

Konstruktor ARRAY:

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

Biola SQL Asli (kedaluwarsa) .
db <> biola di sini.

Terkait:

Catatan (kedaluwarsa sejak pg 9.4)

Kita akan membutuhkan json_array_elements_text(json), kembaran json_array_elements(json)untuk mengembalikan textnilai yang tepat dari array JSON. Tapi itu tampaknya hilang dari gudang fungsi JSON yang disediakan . Atau fungsi lain untuk mengekstraksi textnilai dari JSONnilai skalar . Sepertinya aku juga kehilangan yang itu.
Jadi saya berimprovisasi dengan trim(), tetapi itu akan gagal untuk kasus-kasus non-sepele ...


Posting yang bagus seperti biasa, tetapi dengan sepengetahuan Anda tentang bagian dalam, mengapa tidak ada pemeran dari array-> jsonb di sana. Saya bisa mengerti tidak mengimplementasikan pemeran lain karena sql-array diketik lebih kuat. Apakah itu hanya karena PostgreSQL menolak untuk membuat kode otomatis (int [], bigint [], teks []) dll.
Evan Carroll

3
@ Evan: Anda akan menggunakan to_jsonb()untuk array-> konversi jsonb.
Erwin Brandstetter

Apakah SELECT ARRAY(SELECT json_array_elements_text(_js))benar-benar menjamin bahwa pemesanan array dipertahankan? Tidakkah optimizer diizinkan mengubah urutan baris yang keluar dari json_array_elements_text secara teoritis?
Felix Geisendörfer

@ Feliks: tidak ada jaminan formal dalam standar SQL. (sekali lagi, setel fungsi pengembalian bahkan tidak diperbolehkan dalam daftar SELECT di SQL standar untuk memulai.) tetapi ada pernyataan informal dalam manual Postgres. lihat: dba.stackexchange.com/a/185862/3684 Secara eksplisit - dengan biaya penalti kinerja minor - lihat: dba.stackexchange.com/a/27287/3684 . Secara pribadi, saya 100% yakin ungkapan khusus ini berfungsi seperti yang diharapkan di setiap versi Postgres sekarang dan mendatang sejak 9.4.
Erwin Brandstetter

@ ErwinBrandstetter terima kasih banyak telah mengonfirmasi ini! Saat ini saya sedang melakukan riset untuk sebuah artikel yang merangkum jaminan formal dan informal memesan jaminan yang diberikan oleh PostgreSQL dan jawaban Anda sangat membantu! Jika Anda tertarik untuk mengulas artikel tersebut, beri tahu saya, tetapi jangan khawatir jika tidak. Saya sangat berterima kasih atas kontribusi StackOverflow Anda dan belajar banyak dari Anda selama bertahun-tahun!
Felix Geisendörfer

16

PG 9.4+

Jawaban yang diterima pasti apa yang Anda butuhkan, tetapi demi kesederhanaan di sini adalah penolong yang saya gunakan untuk ini:

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;

Maka lakukan saja:

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

Saya menambahkan beberapa ekspresi lebih cepat ke jawaban saya dan fungsi yang lebih sederhana. Ini bisa jauh lebih murah.
Erwin Brandstetter

4
Fungsi ini harus berupa SQL murni sehingga pengoptimal dapat mengintip ke dalamnya. Tidak perlu menggunakan pgplsql di sini.
Bagilah

8

Pertanyaan ini ditanyakan pada milis PostgreSQL dan saya menemukan cara ini untuk mengkonversi teks JSON menjadi tipe teks PostgreSQL melalui operator ekstraksi bidang 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
 

Pada dasarnya itu mengubah nilai menjadi array elemen tunggal dan kemudian meminta elemen pertama.

Pendekatan lain adalah menggunakan operator ini untuk mengekstraksi semua bidang satu per satu. Tetapi untuk array besar ini kemungkinan lebih lambat, karena perlu mengurai seluruh string JSON untuk setiap elemen array, yang mengarah ke kompleksitas 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

Saya sudah menguji beberapa opsi. Ini permintaan favorit saya. Misalkan kita memiliki tabel yang berisi bidang id dan json. Bidang json berisi array, yang ingin kita ubah menjadi array pg.

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

Ini bekerja di mana saja dan lebih cepat daripada yang lain, tetapi terlihat kasar)

Pertama array json dilemparkan sebagai teks, dan kemudian kita hanya mengubah tanda kurung siku ke tanda kurung. Akhirnya teks tersebut dicasting sebagai larik tipe yang diperlukan.

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

dan jika Anda lebih suka array teks []

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"}}"Saya pikir Anda harus menambahkan beberapa penjelasan tentang bagaimana ini seharusnya bekerja.
dezso

Pertanyaannya adalah bagaimana mengubah array JSON (!) Menjadi array pg. Misalkan saya memiliki tabel yang berisi kolom id dan jsonb. Kolom JSONb berisi json array. Kemudian
FiscalCliff

TRANSLATE (jsonb :: jsonb :: text, '[]', '{}') :: INT [] mengubah json array menjadi pg array.
FiscalCliff

SELECT translate('["foo", "bar"]'::jsonb::text, '[]','{}')::INT[]; ERROR: invalid input syntax for integer: "foo"Ini tidak begitu tahan bom ...
dezso

Pertimbangkan untuk menggunakan teks [] untuk array ini
FiscalCliff

0

Beberapa fungsi ini, diambil dari jawaban untuk pertanyaan ini , adalah apa yang saya gunakan dan mereka bekerja dengan baik

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;

Di masing-masing dari mereka, dengan menggabungkan dengan array kosong, mereka menangani kasus yang membuat saya memeras otak saya sedikit, dalam bahwa jika Anda mencoba dan melemparkan array kosong dari json/ jsonbtanpa itu Anda tidak akan mendapatkan apa-apa kembali, bukan sebuah kosongkan array ( {}) seperti yang Anda harapkan. Saya yakin ada beberapa optimasi untuk mereka, tetapi mereka dibiarkan apa adanya untuk kesederhanaan dalam menjelaskan konsep.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.