Cara melakukan operasi pembaruan pada kolom tipe JSONB di Postgres 9.4

132

Melihat melalui dokumentasi untuk datatype JSONB Postgres 9.4, tidak segera jelas bagi saya bagaimana melakukan pembaruan pada kolom JSONB.

Dokumentasi untuk jenis dan fungsi JSONB:

http://www.postgresql.org/docs/9.4/static/functions-json.html http://www.postgresql.org/docs/9.4/static/datatype-json.html

Sebagai contoh, saya memiliki struktur tabel dasar ini:

CREATE TABLE test(id serial, data jsonb);

Memasukkannya mudah, seperti pada:

INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');

Sekarang, bagaimana saya memperbarui kolom 'data'? Ini adalah sintaks yang tidak valid:

UPDATE test SET data->'name' = 'my-other-name' WHERE id = 1;

Apakah ini didokumentasikan di suatu tempat yang jelas saya lewatkan? Terima kasih.

jvous
sumber

Jawaban:

31

Idealnya, Anda tidak menggunakan dokumen JSON untuk data reguler terstruktur yang ingin Anda manipulasi di dalam basis data relasional. Gunakan desain relasional yang dinormalisasi sebagai gantinya.

JSON terutama dimaksudkan untuk menyimpan seluruh dokumen yang tidak perlu dimanipulasi di dalam RDBMS. Terkait:

Memperbarui baris di Postgres selalu menulis versi baru dari keseluruhan baris. Itulah prinsip dasar model MVCC Postgres . Dari perspektif kinerja, hampir tidak masalah apakah Anda mengubah satu bagian data di dalam objek JSON atau semuanya: versi baru dari baris harus ditulis.

Demikian saran dalam manual :

Data JSON tunduk pada pertimbangan kontrol-konkurensi yang sama seperti tipe data lainnya saat disimpan dalam sebuah tabel. Meskipun menyimpan dokumen besar dapat dilakukan, perlu diingat bahwa setiap pembaruan memperoleh kunci tingkat baris di seluruh baris. Pertimbangkan membatasi dokumen JSON ke ukuran yang dapat dikelola untuk mengurangi pertikaian kunci di antara memperbarui transaksi. Idealnya, dokumen JSON masing-masing harus mewakili datum atom yang mendikte aturan bisnis tidak dapat dibagi lagi menjadi datum yang lebih kecil yang dapat dimodifikasi secara independen.

Inti dari itu: untuk memodifikasi apa pun di dalam objek JSON, Anda harus menetapkan objek yang dimodifikasi ke kolom. Postgres memasok sarana terbatas untuk membuat dan memanipulasi jsondata selain kemampuan penyimpanannya. Gudang peralatan telah tumbuh secara substansial dengan setiap rilis baru sejak versi 9.2. Tetapi prinsipnya tetap: Anda selalu harus menetapkan objek yang dimodifikasi lengkap ke kolom dan Postgres selalu menulis versi baris baru untuk setiap pembaruan.

Beberapa teknik cara bekerja dengan alat Postgres 9.3 atau yang lebih baru:

Jawaban ini telah menarik downvotes sebanyak semua jawaban saya yang lain pada SO bersama-sama . Orang-orang tampaknya tidak menyukai gagasan itu: desain yang dinormalisasi lebih unggul untuk data yang tidak dinamis. Posting blog yang luar biasa ini oleh Craig Ringer menjelaskan lebih terinci:

Erwin Brandstetter
sumber
6
Jawaban ini hanya menyangkut jenis JSON dan mengabaikan JSONB.
fiatjaf
7
@fiatjaf: Jawaban ini sepenuhnya berlaku untuk tipe data jsondan jsonbsama. Keduanya menyimpan data JSON, jsonbapakah itu dalam bentuk biner yang dinormalisasi yang memiliki beberapa kelebihan (dan sedikit kerugian). stackoverflow.com/a/10560761/939860 Tidak ada tipe data yang baik untuk memanipulasi banyak di dalam database. Tidak ada tipe dokumen. Yah, tidak masalah untuk dokumen JSON yang kecil dan sulit terstruktur. Tapi dokumen bersarang yang besar akan menjadi kebodohan seperti itu.
Erwin Brandstetter
7
"Petunjuk cara bekerja dengan alat-alat Postgres 9.3" benar-benar harus menjadi yang pertama dalam jawaban Anda karena menjawab pertanyaan yang diajukan .. kadang-kadang masuk akal untuk memperbarui json untuk pemeliharaan / perubahan skema dll dan alasan untuk tidak melakukan pembaruan json don benar-benar berlaku
Michael Wasser
22
Jawaban ini tidak membantu, maaf. @ jous, tidakkah Anda ingin menerima jawaban Jimothy, karena itu benar-benar menjawab pertanyaan Anda?
Bastian Voigt
10
Jawab pertanyaan terlebih dahulu sebelum menambahkan komentar / pendapat / diskusi Anda sendiri.
Ppp
330

Jika Anda dapat memutakhirkan ke Postgresql 9.5, jsonb_setperintah itu tersedia, seperti yang disebutkan orang lain.

Dalam setiap pernyataan SQL berikut, saya telah menghilangkan whereklausa untuk singkatnya; jelas, Anda ingin menambahkan itu kembali.

Perbarui nama:

UPDATE test SET data = jsonb_set(data, '{name}', '"my-other-name"');

Ganti tag (sebagai lawan menambahkan atau menghapus tag):

UPDATE test SET data = jsonb_set(data, '{tags}', '["tag3", "tag4"]');

Mengganti tag kedua (0-diindeks):

UPDATE test SET data = jsonb_set(data, '{tags,1}', '"tag5"');

Tambahkan tag ( ini akan berfungsi selama ada kurang dari 999 tag; mengubah argumen 999 ke 1000 atau lebih menghasilkan kesalahan . Ini tidak lagi menjadi kasus di Postgres 9.5.3; indeks yang jauh lebih besar dapat digunakan) :

UPDATE test SET data = jsonb_set(data, '{tags,999999999}', '"tag6"', true);

Hapus tag terakhir:

UPDATE test SET data = data #- '{tags,-1}'

Pembaruan kompleks (hapus tag terakhir, masukkan tag baru, dan ubah nama):

UPDATE test SET data = jsonb_set(
    jsonb_set(data #- '{tags,-1}', '{tags,999999999}', '"tag3"', true), 
    '{name}', '"my-other-name"');

Penting untuk dicatat bahwa dalam setiap contoh ini, Anda tidak benar-benar memperbarui satu bidang data JSON. Alih-alih, Anda membuat versi data sementara yang dimodifikasi, dan menetapkan versi yang diubah itu kembali ke kolom. Dalam praktiknya, hasilnya harus sama, tetapi mengingat hal ini harus membuat pembaruan yang kompleks, seperti contoh terakhir, lebih dapat dimengerti.

Dalam contoh kompleks, ada tiga transformasi dan tiga versi sementara: Pertama, tag terakhir dihapus. Kemudian, versi itu diubah dengan menambahkan tag baru. Selanjutnya, versi kedua ditransformasikan dengan mengubah namebidang. Nilai dalam datakolom diganti dengan versi final.

Jimothy
sumber
42
Anda mendapatkan poin bonus karena menunjukkan cara memperbarui kolom dalam sebuah tabel seperti yang diminta OP
chadrik
1
@chadrik: Saya menambahkan contoh yang lebih kompleks. Itu tidak melakukan persis apa yang Anda minta, tetapi itu harus memberi Anda ide. Perhatikan bahwa input ke jsonb_setpanggilan luar adalah output dari panggilan dalam, dan bahwa input ke panggilan dalam adalah hasil dari data #- '{tags,-1}'. Yaitu, data asli dengan tag terakhir dihapus.
Jimothy
1
@ ParaySoni: Untuk tujuan itu, saya mungkin akan menggunakan prosedur tersimpan atau, jika overhead tidak menjadi masalah, bawa kembali data itu, memanipulasi dalam bahasa aplikasi, lalu menulisnya kembali. Ini terdengar berat, tetapi perlu diingat, dalam semua contoh yang saya berikan, Anda masih belum memperbarui satu bidang di JSON (B): Anda menimpa seluruh kolom dengan cara apa pun. Jadi proc yang disimpan benar-benar tidak berbeda.
Jimothy
1
@ Alex: Ya, sedikit peretasan. Jika saya katakan {tags,0}, itu berarti "elemen pertama array tags", yang memungkinkan saya untuk memberikan nilai baru pada elemen itu. Dengan menggunakan angka besar alih-alih 0, alih-alih mengganti elemen yang ada dalam array, ia menambahkan elemen baru ke array. Namun, jika array sebenarnya memiliki lebih dari 999.999.999 elemen di dalamnya, ini akan menggantikan elemen terakhir alih-alih menambahkan yang baru.
Jimothy
1
bagaimana jika bidang berisi nol? terlihat tidak berfungsi. Misalnya bidang info jsonb adalah nol: "SET SET penyelenggaraan info = jsonb_set (info, '{country}', '" FRA "') di mana info - >> 'country' :: teks IS NULL;" Saya mendapatkan UPDATE 105 data tetapi tidak ada perubahan pada db
stackdave
24

Ini datang dalam 9,5 dalam bentuk jsonb_set oleh Andrew Dunstan berdasarkan ekstensi jsonbx yang ada yang bekerja dengan 9,4

philofinfinitejest
sumber
Masalah lain di baris ini, adalah penggunaan jsonb_build_object(), karena x->key, tidak mengembalikan pasangan objek-kunci, untuk mengisi yang Anda butuhkan jsonb_set(target, path, jsonb_build_object('key',x->key)).
Peter Krauss
18

Bagi mereka yang mengalami masalah ini dan ingin perbaikan yang sangat cepat (dan macet pada 9.4.5 atau sebelumnya), inilah yang saya lakukan:

Pembuatan tabel uji

CREATE TABLE test(id serial, data jsonb);
INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');

Perbarui pernyataan untuk mengubah nama properti jsonb

UPDATE test 
SET data = replace(data::TEXT,'"name":','"my-other-name":')::jsonb 
WHERE id = 1;

Pada akhirnya, jawaban yang diterima adalah benar karena Anda tidak dapat memodifikasi satu bagian dari objek jsonb (dalam 9.4.5 atau sebelumnya); Namun, Anda bisa melemparkan objek jsonb ke string (:: TEXT) dan kemudian memanipulasi string dan melemparkan kembali ke objek jsonb (:: jsonb).

Ada dua peringatan penting

  1. ini akan menggantikan semua properti yang disebut "nama" di json (jika Anda memiliki beberapa properti dengan nama yang sama)
  2. ini tidak seefisien jsonb_set jika Anda menggunakan 9.5

Dengan mengatakan itu, saya menemukan situasi di mana saya harus memperbarui skema untuk konten di objek jsonb dan ini adalah cara paling sederhana untuk mencapai apa yang diminta oleh poster asli.

Chad Capra
sumber
1
Tuan yang baik, saya telah mencari cara melakukan pembaruan ke jsonb selama dua jam sehingga saya bisa mengganti semua \u0000karakter nol, contoh menunjukkan gambar lengkap. Terima kasih untuk ini!
Joshua Robinson
3
kelihatan bagus! btw argumen kedua untuk menggantikan dalam contoh Anda termasuk titik dua dan yang ketiga tidak. Sepertinya panggilan Anda seharusnyareplace(data::TEXT, '"name":', '"my-other-name":')::jsonb
davidicus
@Davidicus terima kasih! Maaf atas pembaruan yang sangat tertunda, tetapi saya menghargai Anda berbagi untuk yang lain!
Chad Capra
12

Pertanyaan ini ditanyakan dalam konteks postgres 9.4, namun pemirsa baru yang datang ke pertanyaan ini harus menyadari bahwa di postgres 9.5, sub-dokumen Operasi / Perbarui / Hapus operasi pada bidang JSONB secara asli didukung oleh database, tanpa perlu ekstensi fungsi.

Lihat: JSONB memodifikasi operator dan fungsi

bguiz
sumber
7

perbarui atribut 'nama':

UPDATE test SET data=data||'{"name":"my-other-name"}' WHERE id = 1;

dan jika Anda ingin menghapus, misalnya, atribut 'nama' dan 'tag':

UPDATE test SET data=data-'{"name","tags"}'::text[] WHERE id = 1;
Arthur
sumber
5

Saya menulis fungsi kecil untuk diri saya sendiri yang bekerja secara rekursif di Postgres 9.4. Saya memiliki masalah yang sama (baik mereka memecahkan beberapa sakit kepala ini di Postgres 9.5). Pokoknya di sini adalah fungsinya (saya harap ini berfungsi dengan baik untuk Anda):

CREATE OR REPLACE FUNCTION jsonb_update(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $$
DECLARE
    result JSONB;
    v RECORD;
BEGIN
    IF jsonb_typeof(val2) = 'null'
    THEN 
        RETURN val1;
    END IF;

    result = val1;

    FOR v IN SELECT key, value FROM jsonb_each(val2) LOOP

        IF jsonb_typeof(val2->v.key) = 'object'
            THEN
                result = result || jsonb_build_object(v.key, jsonb_update(val1->v.key, val2->v.key));
            ELSE
                result = result || jsonb_build_object(v.key, v.value);
        END IF;
    END LOOP;

    RETURN result;
END;
$$ LANGUAGE plpgsql;

Berikut ini contoh penggunaannya:

select jsonb_update('{"a":{"b":{"c":{"d":5,"dd":6},"cc":1}},"aaa":5}'::jsonb, '{"a":{"b":{"c":{"d":15}}},"aa":9}'::jsonb);
                            jsonb_update                             
---------------------------------------------------------------------
 {"a": {"b": {"c": {"d": 15, "dd": 6}, "cc": 1}}, "aa": 9, "aaa": 5}
(1 row)

Seperti yang Anda lihat itu menganalisis dalam-dalam dan memperbarui / menambah nilai jika diperlukan.

J. Raczkiewicz
sumber
Ini tidak berhasil pada 9,4, karena jsonb_build_objectdiperkenalkan pada 9,5
Greg
@Greg Anda benar, saya baru saja memeriksa dan saya menjalankan PostgreSQL 9.5 sekarang - inilah sebabnya ia bekerja. Terima kasih telah menunjukkan hal itu - solusi saya tidak akan berfungsi di 9.4.
J. Raczkiewicz
4

Mungkin: UPDATE test SET data = '"my-other-name"' :: json WHERE id = 1;

Ini bekerja dengan kasus saya, di mana data adalah tipe json

Gianluigi Sartori
sumber
1
Bekerja untuk saya juga, pada postgresql 9.4.5. Seluruh catatan ditulis ulang sehingga orang tidak dapat memperbarui satu atm bidang.
kometen