Bagaimana cara menghapus elemen yang diketahui dari array JSON [] di PostgreSQL?

8

Saya menghadapi masalah terkait penggunaan tipe data JSON di PostgreSQL. Saya mencoba untuk mencapai penyimpanan model Java didenormalisasi dalam DB. Model ini menampilkan daftar objek yang kompleks. Jadi, saya memutuskan untuk memodelkan mereka sebagai JSON dalam array PostgreSQL asli.

Ini adalah potongan dari pernyataan pembuatan tabel saya:

CREATE TABLE test.persons
(
  id UUID,
  firstName TEXT,
  lastName TEXT,
  communicationData JSON[],
  CONSTRAINT pk_person PRIMARY KEY (id)
);

Seperti yang Anda lihat itu adalah orang yang menampilkan daftar objek data komunikasi di JSON. Salah satu objek tersebut mungkin terlihat seperti ini:

{"value" : "03334/254147", "typeId" : "ea4e7d7e-7b87-4628-ba50-6a5f6e63dbf6"}

Saya dapat dengan mudah menambahkan objek JSON ke array menggunakan PostgreSQL's array_append. Namun, saya gagal menghapus nilai yang diketahui dari array. Pertimbangkan pernyataan SQL ini:

UPDATE test.persons
SET communicationData = array_remove(
      communicationData, 
      '{"value" : "03334/254147", "typeId" : "ea4e7d7e-7b87-4628-ba50-6a5f6e63dbf6"}'::JSON
    )
WHERE id = 'f671eb6a-d603-11e3-bf6f-07ba007d953d';

Ini gagal dengan ERROR: could not identify an equality operator for type json. Apakah Anda memiliki petunjuk bagaimana saya dapat menghapus nilai yang diketahui dari array JSON? Mungkin juga untuk menghapus dengan posisi dalam array, karena saya tahu bahwa satu juga ...

Versi PostgreSQL adalah 9.3.4.

spa
sumber

Jawaban:

11

jsonb dalam Postgres 9.4 atau lebih baru

Pertimbangkan jsonbtipe data dalam Postgres 9.4 - 'b' untuk 'biner'. Di antaranya, ada operator kesetaraan =untukjsonb . Sebagian besar orang ingin beralih.

Depesz blog tentang jsonb.

json

Tidak ada =operator yang ditentukan untuk tipe data json, karena tidak ada metode yang didefinisikan dengan baik untuk membangun kesetaraan untuk seluruh jsonnilai. Tapi lihat di bawah.

Anda dapat melakukan cast to textdan kemudian menggunakan =operator. Ini singkat, tetapi hanya berfungsi jika representasi teks Anda cocok. Secara inheren tidak dapat diandalkan, kecuali untuk kasus sudut. Lihat:

Atau Anda dapat unnestmengatur dan menggunakan ->>operator untuk .. get JSON object field as textdan membandingkan masing-masing bidang.

Meja tes

2 baris: yang pertama seperti dalam pertanyaan, yang kedua dengan nilai sederhana.

CREATE TABLE tbl (
   tbl_id int PRIMARY KEY
 , jar    json[]
);

INSERT INTO t VALUES
   (1, '{"{\"value\" : \"03334/254146\", \"typeId\" : \"ea4e7d7e-7b87-4628-ba50-f5\"}"
        ,"{\"value\" : \"03334/254147\", \"typeId\" : \"ea4e7d7e-7b87-4628-ba50-f6\"}"
        ,"{\"value\" : \"03334/254148\", \"typeId\" : \"ea4e7d7e-7b87-4628-ba50-f7\"}"}')

 , (2, '{"{\"value\" : \"a\", \"typeId\" : \"x\"}"
        ,"{\"value\" : \"b\", \"typeId\" : \"y\"}"
        ,"{\"value\" : \"c\", \"typeId\" : \"z\"}"}');

Demo

Demo 1: Anda dapat menggunakan array_remove()dengan textrepresentasi (tidak dapat diandalkan).

SELECT tbl_id
     , jar, array_length(jar, 1) AS jar_len
     , jar::text[] AS t, array_length(jar::text[], 1) AS t_len
     , array_remove(jar::text[], '{"value" : "03334/254147", "typeId" : "ea4e7d7e-7b87-4628-ba50-f6"}'::text) AS t_result
     , array_remove(jar::text[], '{"value" : "03334/254147", "typeId" : "ea4e7d7e-7b87-4628-ba50-f6"}'::text)::json[] AS j_result
FROM   tbl;

Demo 2: membatalkan array dan menguji bidang elemen individu.

SELECT tbl_id, array_agg(j) AS j_new
FROM   tbl, unnest(jar) AS j   -- LATERAL JOIN
WHERE  j->>'value' <> '03334/254146'
AND    j->>'typeId' <> 'ea4e7d7e-7b87-4628-ba50-6a5f6e63dbf5'
GROUP  BY 1;

Demo 3: tes alternatif dengan tipe baris.

SELECT tbl_id, array_agg(j) AS j_new
FROM   tbl, unnest(jar) AS j   -- LATERAL JOIN
WHERE  (j->>'value', j->>'typeId') NOT IN (
         ('03334/254146', 'ea4e7d7e-7b87-4628-ba50-6a5f6e63dbf5')
        ,('a', 'x')
       )
GROUP  BY 1;

UPDATE seperti yang diminta

Akhirnya, ini adalah bagaimana Anda dapat mengimplementasikan UPDATE:

UPDATE tbl t
SET    jar = j.jar
FROM   tbl t1
CROSS  JOIN LATERAL (
   SELECT ARRAY(
      SELECT j
      FROM   unnest(t1.jar) AS j  -- LATERAL JOIN
      WHERE  j->>'value'  <> 'a'
      AND    j->>'typeId' <> 'x'
      ) AS jar
   ) j
WHERE  t1.tbl_id = 2              -- only relevant rows
AND    t1.tbl_id = t.tbl_id;

db <> biola di sini

Tentang yang tersirat LATERAL JOIN:

Tentang susunan undesting:

Desain DB

Untuk menyederhanakan situasi Anda pertimbangkan skema yang dinormalisasi : tabel terpisah untuk jsonnilai - nilai (bukan kolom array), bergabung dalam hubungan: 1 ke tabel utama.

Erwin Brandstetter
sumber
Itu bekerja seperti pesona. Ya, akan lebih mudah dengan data yang dinormalisasi, tapi saya dalam skenario baca 98%, skenario menulis 2%. Jadi saya ingin bereksperimen dengan denormalisasi :-) Apakah ada sesuatu yang dirilis direncanakan untuk Postgres 9.4 yang mungkin membantu dengan pertanyaan aslinya?
spa
@ spa: Sebenarnya, Postgres 9.4 akan membawa jsonb. Saya berharap Anda akan menyukainya. Menambahkan bab dengan tautan.
Erwin Brandstetter
Itu keren sekali. Terimakasih atas peringatannya.
spa