Data katalog tidak pernah diam: harga naik menjelang akhir tahun, stok berkurang tiap kali pesanan masuk, tag promosi berganti musim. Aplikasi Anda tentu ingin mengubah dokumen jsonb itu juga — dan di sinilah sifat tipe datanya terasa: jsonb bersifat immutable, isi dokumen tidak pernah diubah di tempat. Setiap operasi pengubahan mengembalikan dokumen baru yang utuh, dan dokumen baru itulah yang Anda simpan kembali. Kabar baiknya, PostgreSQL menyediakan satu fungsi kerja keras untuk hampir semuanya: jsonb_set. Kita akan memakainya untuk menimpa nilai, menembus objek bersarang, dan menambah kunci, lalu mempraktikkan pola UPDATE lengkap pada tabel produk, penghapusan lewat - dan #-, serta penyisipan elemen array dengan jsonb_insert.

jsonb_set: Kuda Beban Pengubahan Dokumen #

Fungsi jsonb_set menerima empat argumen: dokumen target, path (array teks) yang menunjuk lokasi nilai, nilai baru bertipe jsonb, dan parameter opsional create_if_missing yang mengatur boleh-tidaknya kunci dibuat bila belum ada. Nilai baru wajib bertipe jsonb — angka '799000'::jsonb atau teks '"12 bulan"'::jsonb lengkap dengan kutipnya. File berikut memperagakan lima wujud pemakaiannya di atas tabel produk:

sql buat-tabel.sql
-- Katalog produk Toko Maju Jaya: atribut semi-terstruktur
-- dengan objek bersarang (spesifikasi) dan array (tag).
CREATE TABLE produk (
    id      integer PRIMARY KEY,
    nama    text NOT NULL,
    atribut jsonb NOT NULL
);

INSERT INTO produk (id, nama, atribut) VALUES
    (1, 'Sepatu Lari Pagi',
     '{"merek": "Elang", "harga": 750000, "stok": 12,
       "spesifikasi": {"berat_gram": 280, "warna": "merah"},
       "tag": ["olahraga", "diskon"]}'),
    (2, 'Kemeja Batik Kasih',
     '{"merek": "Nusantara", "harga": 320000, "stok": 40,
       "spesifikasi": {"bahan": "katun"},
       "tag": ["formal"]}'),
    (3, 'Tas Ransel Gunung',
     '{"merek": "Gunung Jaya", "harga": 450000, "stok": 8,
       "spesifikasi": {"kapasitas_liter": 30, "tahan_air": true},
       "tag": ["outdoor", "diskon"]}');

SELECT id, nama, atribut
FROM produk
ORDER BY id;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 3
 id |        nama        |                                                                    atribut                                                                    
----+--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------
  1 | Sepatu Lari Pagi   | {"tag": ["olahraga", "diskon"], "stok": 12, "harga": 750000, "merek": "Elang", "spesifikasi": {"warna": "merah", "berat_gram": 280}}
  2 | Kemeja Batik Kasih | {"tag": ["formal"], "stok": 40, "harga": 320000, "merek": "Nusantara", "spesifikasi": {"bahan": "katun"}}
  3 | Tas Ransel Gunung  | {"tag": ["outdoor", "diskon"], "stok": 8, "harga": 450000, "merek": "Gunung Jaya", "spesifikasi": {"tahan_air": true, "kapasitas_liter": 30}}
(3 rows)

Tiga baris produk itu menjadi pasangan uji kita: produk 1 membawa objek spesifikasi berisi berat_gram dan warna plus array tag dua elemen — struktur cukup dalam untuk semua variasi jsonb_set:

sql jsonb-set.sql
-- jsonb_set(target, path, new_value [, create_if_missing]):
-- kuda beban pengubahan dokumen jsonb.

-- 1. Menimpa nilai pada kunci yang sudah ada.
SELECT jsonb_set(atribut, '{harga}', '799000'::jsonb) AS harga_naik
FROM produk
WHERE id = 1;

-- 2. Path bertingkat menembus objek bersarang: {spesifikasi, berat_gram}.
SELECT jsonb_set(atribut, '{spesifikasi, berat_gram}', '290'::jsonb) AS berat_baru
FROM produk
WHERE id = 1;

-- 3. Menambah kunci baru (create_if_missing default: true).
SELECT jsonb_set(atribut, '{garansi}', '"12 bulan"'::jsonb) AS dengan_garansi
FROM produk
WHERE id = 2;

-- 4. create_if_missing = false: path harus sudah ada,
--    kalau tidak ada dokumen dikembalikan tanpa perubahan.
SELECT jsonb_set(atribut, '{garansi}', '"12 bulan"'::jsonb, false) AS tetap_rupiah
FROM produk
WHERE id = 2;

-- 5. Jebakan senyap: kunci perantara tidak ada,
--    jsonb_set tidak membuat tingkat antara otomatis.
SELECT jsonb_set(atribut, '{pengiriman, estimasi_hari}', '3'::jsonb) AS tidak_berubah
FROM produk
WHERE id = 2;

Output yang Dihasilkan:

text
                                                              harga_naik                                                              
--------------------------------------------------------------------------------------------------------------------------------------
 {"tag": ["olahraga", "diskon"], "stok": 12, "harga": 799000, "merek": "Elang", "spesifikasi": {"warna": "merah", "berat_gram": 280}}
(1 row)

                                                              berat_baru                                                              
--------------------------------------------------------------------------------------------------------------------------------------
 {"tag": ["olahraga", "diskon"], "stok": 12, "harga": 750000, "merek": "Elang", "spesifikasi": {"warna": "merah", "berat_gram": 290}}
(1 row)

                                                          dengan_garansi                                                          
----------------------------------------------------------------------------------------------------------------------------------
 {"tag": ["formal"], "stok": 40, "harga": 320000, "merek": "Nusantara", "garansi": "12 bulan", "spesifikasi": {"bahan": "katun"}}
(1 row)

                                               tetap_rupiah                                                
-----------------------------------------------------------------------------------------------------------
 {"tag": ["formal"], "stok": 40, "harga": 320000, "merek": "Nusantara", "spesifikasi": {"bahan": "katun"}}
(1 row)

                                               tidak_berubah                                               
-----------------------------------------------------------------------------------------------------------
 {"tag": ["formal"], "stok": 40, "harga": 320000, "merek": "Nusantara", "spesifikasi": {"bahan": "katun"}}
(1 row)

Mari kita bedah lima query tersebut satu per satu:

  1. Query 1 ('{harga}'): path satu elemen menunjuk kunci harga di tingkat atas. Nilainya tertimpa dari 750000 menjadi 799000; kunci lain tidak tersentuh.
  2. Query 2 ('{spesifikasi, berat_gram}'): path dua elemen menembus objek bersarang — masuk ke spesifikasi, lalu timpa berat_gram menjadi 290.
  3. Query 3 (kunci baru): kunci garansi tidak ada di produk 2. Karena create_if_missing default-nya true, pasangan "garansi": "12 bulan" ditambahkan.
  4. Query 4 (false): parameter keempat false menegakkan syarat path harus sudah ada; karena garansi belum ada, dokumen dikembalikan apa adanya tanpa error.
  5. Query 5 (jalan terputus): path {pengiriman, estimasi_hari} menuntut kunci perantara pengiriman yang tidak pernah ada. jsonb_set tidak membuat tingkat antara otomatis, jadi hasilnya kembali tanpa perubahan.

Penggabungan || dan Batas Satu Tingkat #

Operator || yang diperkenalkan di chapter Operator JSON dan JSONB menggabungkan dua dokumen: kunci di sisi kanan menimpa kunci yang sama di sisi kiri, kunci lain bergabung. Untuk memperbarui beberapa kunci tingkat atas sekaligus, || lebih ringkas daripada jsonb_set bersarang. Batasnya satu tingkat: bila nilai di kedua sisi berupa objek, objek milik sisi kanan menggantikan seluruhnya objek milik sisi kiri — isinya tidak dilebur. File berikut menunjukkan batas itu, lalu merakit merge mendalam secara manual:

-- Operator || menggabungkan dua dokumen, tetapi hanya satu tingkat.

-- Recap ||: kunci di kanan menimpa kunci di kiri.
SELECT atribut || '{"stok": 20, "merek": "Elang Pro"}'::jsonb AS gabung_datar
FROM produk
WHERE id = 1;

-- Batas ||: objek bersarang di kanan menimpa SELURUH objek di kiri,
-- bukan digabung isinya. Kunci "warna" hilang.
SELECT '{"spesifikasi": {"berat_gram": 280, "warna": "merah"}}'::jsonb
       || '{"spesifikasi": {"berat_gram": 290}}'::jsonb AS objek_tertimpa;

-- Merge mendalam manual: ambil subobjek lama, gabung dengan ||,
-- lalu pasang kembali lewat jsonb_set pada path satu tingkat.
SELECT jsonb_set(
           atribut,
           '{spesifikasi}',
           atribut -> 'spesifikasi'
               || '{"berat_gram": 290, "garansi": "6 bulan"}'::jsonb
       ) AS merge_mendalam
FROM produk
WHERE id = 1;

Output yang Dihasilkan:

text
                                                               gabung_datar                                                               
------------------------------------------------------------------------------------------------------------------------------------------
 {"tag": ["olahraga", "diskon"], "stok": 20, "harga": 750000, "merek": "Elang Pro", "spesifikasi": {"warna": "merah", "berat_gram": 280}}
(1 row)

            objek_tertimpa            
--------------------------------------
 {"spesifikasi": {"berat_gram": 290}}
(1 row)

                                                                       merge_mendalam                                                                       
------------------------------------------------------------------------------------------------------------------------------------------------------------
 {"tag": ["olahraga", "diskon"], "stok": 12, "harga": 750000, "merek": "Elang", "spesifikasi": {"warna": "merah", "garansi": "6 bulan", "berat_gram": 290}}
(1 row)

Query pertama memperlihatkan sisi baik ||: stok berubah menjadi 20 dan merek menjadi "Elang Pro" dalam satu operasi. Query kedua memperlihatkan sisi buruknya: hasil hanya memuat {"spesifikasi": {"berat_gram": 290}} — kunci warna lenyap karena objek spesifikasi milik sisi kanan menimpa seluruh objek lama. Query ketiga adalah resep merge mendalamnya, mari kita baca dari dalam ke luar:

  1. atribut -> 'spesifikasi': mengambil objek bersarang lama sebagai dokumen jsonb tersendiri ({"berat_gram": 280, "warna": "merah"} setelah dinormalkan).
  2. || '{"berat_gram": 290, "garansi": "6 bulan"}': menggabungkan objek lama itu dengan pembaruan — berat_gram tertimpa, garansi ditambahkan, warna selamat karena berada di tingkat atas penggabungan ini.
  3. jsonb_set(atribut, '{spesifikasi}', ...): memasang hasil lebur itu kembali ke posisi spesifikasi di dokumen induk.

Hasil akhirnya persis seperti yang diharapkan dari merge mendalam: warna tetap ada, berat_gram menjadi 290, garansi baru ditambahkan.

Pola UPDATE pada Tabel produk #

Semua contoh sejauh ini hanya menghasilkan dokumen baru di hasil query — baris tabel belum tersentuh. Karena jsonb immutable, pembaruan permanen selalu berbentuk satu pola: UPDATE tabel SET kolom = <ekspresi pengubahan pada kolom itu>. File berikut mempraktikkan pola lengkapnya:

-- Pola utama mengubah kolom jsonb di tabel:
-- UPDATE tabel SET kolom = jsonb_set(kolom, path, nilai_baru).

-- 1. Naikkan harga produk 1.
UPDATE produk
SET atribut = jsonb_set(atribut, '{harga}', '799000'::jsonb)
WHERE id = 1;

-- 2. Dua perubahan sekaligus: jsonb_set bersarang,
--    hasil dalam menjadi target untuk jsonb_set luar.
UPDATE produk
SET atribut = jsonb_set(
        jsonb_set(atribut, '{stok}', '9'::jsonb),
        '{harga}', '405000'::jsonb)
WHERE id = 3;

-- 3. Nilai bersarang: ubah berat di dalam objek spesifikasi.
UPDATE produk
SET atribut = jsonb_set(atribut, '{spesifikasi, berat_gram}', '290'::jsonb)
WHERE id = 1;

-- 4. Elemen array: path {tag, 1} menunjuk elemen kedua array tag.
UPDATE produk
SET atribut = jsonb_set(atribut, '{tag, 1}', '"promo-akhir-tahun"'::jsonb)
WHERE id = 1;

-- Verifikasi hasil keempat pembaruan.
SELECT id, nama, atribut
FROM produk
WHERE id IN (1, 3)
ORDER BY id;

Output yang Dihasilkan:

text
UPDATE 1
UPDATE 1
UPDATE 1
UPDATE 1
 id |       nama        |                                                                     atribut                                                                     
----+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------
  1 | Sepatu Lari Pagi  | {"tag": ["olahraga", "promo-akhir-tahun"], "stok": 12, "harga": 799000, "merek": "Elang", "spesifikasi": {"warna": "merah", "berat_gram": 290}}
  3 | Tas Ransel Gunung | {"tag": ["outdoor", "diskon"], "stok": 9, "harga": 405000, "merek": "Gunung Jaya", "spesifikasi": {"tahan_air": true, "kapasitas_liter": 30}}
(2 rows)

Empat UPDATE itu menanjak kompleksitasnya secara bertahap:

  1. Baris UPDATE 1 pertama: bentuk paling dasar — atribut = jsonb_set(atribut, '{harga}', ...). Kolom dibaca, dokumen baru dihitung, lalu hasilnya menimpa kolom itu kembali.
  2. UPDATE kedua (jsonb_set bersarang): hasil jsonb_set dalam (stok menjadi 9) menjadi argumen target bagi jsonb_set luar (harga menjadi 405000). Dua kunci berubah dalam satu penugasan tanpa perantara.
  3. UPDATE ketiga: path {spesifikasi, berat_gram} membuktikan pola yang sama bekerja untuk nilai bersarang.
  4. UPDATE keempat: path {tag, 1} menunjuk elemen array — indeks 1 berarti elemen kedua, sehingga "diskon" diganti "promo-akhir-tahun" dan "olahraga" tetap di posisinya.

Blok verifikasi di akhir file memperlihatkan hasil akhirnya: produk 1 berharga 799000 dengan berat_gram 290 dan tag ["olahraga", "promo-akhir-tahun"]; produk 3 berharga 405000 dengan stok 9.

Menghapus Kunci dan Elemen: - dan #- #

Penghapusan lebih sederhana daripada pengubahan. Operator - menghapus satu kunci (atribut - 'stok'); operator #- menerima path text[] sehingga sanggup menjangkau nilai bersarang maupun elemen array. Keduanya sama-sama mengembalikan dokumen baru, jadi pola UPDATE-nya tidak berubah:

-- Penghapusan: operator - untuk satu kunci, #- untuk path.

-- Operator - menghapus kunci tingkat atas.
SELECT atribut - 'stok' AS tanpa_stok
FROM produk
WHERE id = 2;

-- Pola UPDATE dengan operator -.
UPDATE produk
SET atribut = atribut - 'stok'
WHERE id = 2;

-- Operator #- menghapus nilai pada path bertingkat: kunci dalam spesifikasi.
UPDATE produk
SET atribut = atribut #- '{spesifikasi, tahan_air}'
WHERE id = 3;

-- Path juga bisa menunjuk elemen array: {tag, 0} = elemen pertama.
UPDATE produk
SET atribut = atribut #- '{tag, 0}'
WHERE id = 1;

-- Operator - pada array jsonb dengan indeks: menghapus elemen pada posisi itu.
SELECT '["a", "b", "c"]'::jsonb - 1 AS hapus_elemen_kedua;

-- Verifikasi ketiga pembaruan.
SELECT id, atribut
FROM produk
ORDER BY id;

Output yang Dihasilkan:

text
                                          tanpa_stok                                           
-----------------------------------------------------------------------------------------------
 {"tag": ["formal"], "harga": 320000, "merek": "Nusantara", "spesifikasi": {"bahan": "katun"}}
(1 row)

UPDATE 1
UPDATE 1
UPDATE 1
 hapus_elemen_kedua 
--------------------
 ["a", "c"]
(1 row)

 id |                                                               atribut                                                               
----+-------------------------------------------------------------------------------------------------------------------------------------
  1 | {"tag": ["promo-akhir-tahun"], "stok": 12, "harga": 799000, "merek": "Elang", "spesifikasi": {"warna": "merah", "berat_gram": 290}}
  2 | {"tag": ["formal"], "harga": 320000, "merek": "Nusantara", "spesifikasi": {"bahan": "katun"}}
  3 | {"tag": ["outdoor", "diskon"], "stok": 9, "harga": 405000, "merek": "Gunung Jaya", "spesifikasi": {"kapasitas_liter": 30}}
(3 rows)

Query pembuka menunjukkan - 'stok' pada produk 2 — hasilnya dokumen tanpa kunci stok; UPDATE pertama lalu menerapkannya permanen. UPDATE kedua memakai #- '{spesifikasi, tahan_air}' untuk mencabut kunci tahan_air dari objek bersarang produk 3, dan UPDATE ketiga memakai #- '{tag, 0}' untuk membuang elemen pertama array tag produk 1. Query keempat mendemonstrasikan varian array murni: '["a","b","c"]'::jsonb - 1 membuang elemen indeks 1 sehingga tersisa ["a", "c"]. Perbedaan penting dari jsonb_set: bila path yang ditunjuk tidak ada, penghapusan hanya tidak berpengaruh — tidak pernah error.

jsonb_insert: Menyisipkan ke dalam Array #

Terakhir, fungsi khusus penyisipan: jsonb_insert(target, path, new_value [, insert_after]). Untuk array, path menunjuk indeks tujuan; default menyisipkan sebelum elemen pada indeks itu, dan insert_after := true menyisipkan sesudahnya:

-- jsonb_insert(target, path, new_value [, insert_after]):
-- menyisipkan nilai ke dalam array pada posisi indeks tertentu.

-- Default: disisipkan SEBELUM elemen pada indeks 0 (jadi elemen pertama).
SELECT jsonb_insert(atribut, '{tag, 0}', '"baru"'::jsonb) AS paling_depan
FROM produk
WHERE id = 2;

-- insert_after = true: disisipkan SETELAH elemen indeks 0.
SELECT jsonb_insert(atribut, '{tag, 0}', '"baru"'::jsonb, true) AS posisi_kedua
FROM produk
WHERE id = 2;

-- Indeks melebihi panjang array: nilai ditambahkan di akhir.
SELECT jsonb_insert('{"tag": ["formal"]}'::jsonb, '{tag, 99}', '"promo"'::jsonb)
       AS di_akhir;

-- Pola UPDATE: tambah tag baru di akhir array milik produk 2.
UPDATE produk
SET atribut = jsonb_insert(atribut, '{tag, 99}', '"unggulan"'::jsonb)
WHERE id = 2;

SELECT id, atribut -> 'tag' AS tag
FROM produk
WHERE id = 2;

Output yang Dihasilkan:

text
                                             paling_depan                                              
-------------------------------------------------------------------------------------------------------
 {"tag": ["baru", "formal"], "harga": 320000, "merek": "Nusantara", "spesifikasi": {"bahan": "katun"}}
(1 row)

                                             posisi_kedua                                              
-------------------------------------------------------------------------------------------------------
 {"tag": ["formal", "baru"], "harga": 320000, "merek": "Nusantara", "spesifikasi": {"bahan": "katun"}}
(1 row)

           di_akhir           
------------------------------
 {"tag": ["formal", "promo"]}
(1 row)

UPDATE 1
 id |          tag           
----+------------------------
  2 | ["formal", "unggulan"]
(1 row)

Dua query pertama membandingkan keduanya pada array ["formal"]: tanpa parameter tambahan, "baru" masuk di depan (["baru", "formal"]); dengan true, masuk setelahnya (["formal", "baru"]). Query ketiga memperlihatkan perilaku praktis: indeks 99 yang melebihi panjang array tidak error — nilainya ditambahkan di akhir, dan justru itu idiom yang kita pakai di UPDATE penutup untuk menambah "unggulan" ke tag produk 2. Satu batasan penting: bila path menunjuk kunci objek yang sudah ada, jsonb_insert menolak dengan error:

-- jsonb_insert pada kunci objek yang SUDAH ADA ditolak server:
-- gunakan jsonb_set untuk menimpa nilai kunci.
SELECT jsonb_insert('{"merek": "Elang"}'::jsonb, '{merek}', '"Elang Pro"'::jsonb);

Output yang Dihasilkan:

text
psql:kunci-ada-error.sql:3: ERROR:  cannot replace existing key
HINT:  Try using the function jsonb_set to replace key value.

Error cannot replace existing key lengkap dengan petunjuk HINT: Try using the function jsonb_set to replace key value — server sendiri mengarahkan Anda ke fungsi yang tepat: penyisipan itu urusan jsonb_insert, penimpaan nilai itu urusan jsonb_set.

Dengan jsonb_set, ||, -, #-, dan jsonb_insert, dokumen di kolom jsonb kini bisa Anda bentuk sesukanya: ditimpa, dilebur, dipangkas, disisipi. Pertanyaan berikutnya berbalik arah — bagaimana merakit dokumen JSON dari baris-baris tabel, bukan mengubah dokumen yang sudah ada? Itu pekerjaan fungsi agregasi jsonb_agg dan jsonb_build_object di chapter berikutnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

buat-tabel.sql

Unduh berkas
sql
-- Katalog produk Toko Maju Jaya: atribut semi-terstruktur
-- dengan objek bersarang (spesifikasi) dan array (tag).
CREATE TABLE produk (
    id      integer PRIMARY KEY,
    nama    text NOT NULL,
    atribut jsonb NOT NULL
);

INSERT INTO produk (id, nama, atribut) VALUES
    (1, 'Sepatu Lari Pagi',
     '{"merek": "Elang", "harga": 750000, "stok": 12,
       "spesifikasi": {"berat_gram": 280, "warna": "merah"},
       "tag": ["olahraga", "diskon"]}'),
    (2, 'Kemeja Batik Kasih',
     '{"merek": "Nusantara", "harga": 320000, "stok": 40,
       "spesifikasi": {"bahan": "katun"},
       "tag": ["formal"]}'),
    (3, 'Tas Ransel Gunung',
     '{"merek": "Gunung Jaya", "harga": 450000, "stok": 8,
       "spesifikasi": {"kapasitas_liter": 30, "tahan_air": true},
       "tag": ["outdoor", "diskon"]}');

SELECT id, nama, atribut
FROM produk
ORDER BY id;

hapus-elemen.sql

Unduh berkas
sql
-- Penghapusan: operator - untuk satu kunci, #- untuk path.

-- Operator - menghapus kunci tingkat atas.
SELECT atribut - 'stok' AS tanpa_stok
FROM produk
WHERE id = 2;

-- Pola UPDATE dengan operator -.
UPDATE produk
SET atribut = atribut - 'stok'
WHERE id = 2;

-- Operator #- menghapus nilai pada path bertingkat: kunci dalam spesifikasi.
UPDATE produk
SET atribut = atribut #- '{spesifikasi, tahan_air}'
WHERE id = 3;

-- Path juga bisa menunjuk elemen array: {tag, 0} = elemen pertama.
UPDATE produk
SET atribut = atribut #- '{tag, 0}'
WHERE id = 1;

-- Operator - pada array jsonb dengan indeks: menghapus elemen pada posisi itu.
SELECT '["a", "b", "c"]'::jsonb - 1 AS hapus_elemen_kedua;

-- Verifikasi ketiga pembaruan.
SELECT id, atribut
FROM produk
ORDER BY id;

jsonb-insert.sql

Unduh berkas
sql
-- jsonb_insert(target, path, new_value [, insert_after]):
-- menyisipkan nilai ke dalam array pada posisi indeks tertentu.

-- Default: disisipkan SEBELUM elemen pada indeks 0 (jadi elemen pertama).
SELECT jsonb_insert(atribut, '{tag, 0}', '"baru"'::jsonb) AS paling_depan
FROM produk
WHERE id = 2;

-- insert_after = true: disisipkan SETELAH elemen indeks 0.
SELECT jsonb_insert(atribut, '{tag, 0}', '"baru"'::jsonb, true) AS posisi_kedua
FROM produk
WHERE id = 2;

-- Indeks melebihi panjang array: nilai ditambahkan di akhir.
SELECT jsonb_insert('{"tag": ["formal"]}'::jsonb, '{tag, 99}', '"promo"'::jsonb)
       AS di_akhir;

-- Pola UPDATE: tambah tag baru di akhir array milik produk 2.
UPDATE produk
SET atribut = jsonb_insert(atribut, '{tag, 99}', '"unggulan"'::jsonb)
WHERE id = 2;

SELECT id, atribut -> 'tag' AS tag
FROM produk
WHERE id = 2;

jsonb-set.sql

Unduh berkas
sql
-- jsonb_set(target, path, new_value [, create_if_missing]):
-- kuda beban pengubahan dokumen jsonb.

-- 1. Menimpa nilai pada kunci yang sudah ada.
SELECT jsonb_set(atribut, '{harga}', '799000'::jsonb) AS harga_naik
FROM produk
WHERE id = 1;

-- 2. Path bertingkat menembus objek bersarang: {spesifikasi, berat_gram}.
SELECT jsonb_set(atribut, '{spesifikasi, berat_gram}', '290'::jsonb) AS berat_baru
FROM produk
WHERE id = 1;

-- 3. Menambah kunci baru (create_if_missing default: true).
SELECT jsonb_set(atribut, '{garansi}', '"12 bulan"'::jsonb) AS dengan_garansi
FROM produk
WHERE id = 2;

-- 4. create_if_missing = false: path harus sudah ada,
--    kalau tidak ada dokumen dikembalikan tanpa perubahan.
SELECT jsonb_set(atribut, '{garansi}', '"12 bulan"'::jsonb, false) AS tetap_rupiah
FROM produk
WHERE id = 2;

-- 5. Jebakan senyap: kunci perantara tidak ada,
--    jsonb_set tidak membuat tingkat antara otomatis.
SELECT jsonb_set(atribut, '{pengiriman, estimasi_hari}', '3'::jsonb) AS tidak_berubah
FROM produk
WHERE id = 2;

kunci-ada-error.sql

Unduh berkas
sql
-- jsonb_insert pada kunci objek yang SUDAH ADA ditolak server:
-- gunakan jsonb_set untuk menimpa nilai kunci.
SELECT jsonb_insert('{"merek": "Elang"}'::jsonb, '{merek}', '"Elang Pro"'::jsonb);

merge-mendalam.sql

Unduh berkas
sql
-- Operator || menggabungkan dua dokumen, tetapi hanya satu tingkat.

-- Recap ||: kunci di kanan menimpa kunci di kiri.
SELECT atribut || '{"stok": 20, "merek": "Elang Pro"}'::jsonb AS gabung_datar
FROM produk
WHERE id = 1;

-- Batas ||: objek bersarang di kanan menimpa SELURUH objek di kiri,
-- bukan digabung isinya. Kunci "warna" hilang.
SELECT '{"spesifikasi": {"berat_gram": 280, "warna": "merah"}}'::jsonb
       || '{"spesifikasi": {"berat_gram": 290}}'::jsonb AS objek_tertimpa;

-- Merge mendalam manual: ambil subobjek lama, gabung dengan ||,
-- lalu pasang kembali lewat jsonb_set pada path satu tingkat.
SELECT jsonb_set(
           atribut,
           '{spesifikasi}',
           atribut -> 'spesifikasi'
               || '{"berat_gram": 290, "garansi": "6 bulan"}'::jsonb
       ) AS merge_mendalam
FROM produk
WHERE id = 1;

update-tabel.sql

Unduh berkas
sql
-- Pola utama mengubah kolom jsonb di tabel:
-- UPDATE tabel SET kolom = jsonb_set(kolom, path, nilai_baru).

-- 1. Naikkan harga produk 1.
UPDATE produk
SET atribut = jsonb_set(atribut, '{harga}', '799000'::jsonb)
WHERE id = 1;

-- 2. Dua perubahan sekaligus: jsonb_set bersarang,
--    hasil dalam menjadi target untuk jsonb_set luar.
UPDATE produk
SET atribut = jsonb_set(
        jsonb_set(atribut, '{stok}', '9'::jsonb),
        '{harga}', '405000'::jsonb)
WHERE id = 3;

-- 3. Nilai bersarang: ubah berat di dalam objek spesifikasi.
UPDATE produk
SET atribut = jsonb_set(atribut, '{spesifikasi, berat_gram}', '290'::jsonb)
WHERE id = 1;

-- 4. Elemen array: path {tag, 1} menunjuk elemen kedua array tag.
UPDATE produk
SET atribut = jsonb_set(atribut, '{tag, 1}', '"promo-akhir-tahun"'::jsonb)
WHERE id = 1;

-- Verifikasi hasil keempat pembaruan.
SELECT id, nama, atribut
FROM produk
WHERE id IN (1, 3)
ORDER BY id;