Postgres 9.4'te JSONB türü sütunlarda güncelleme işlemleri nasıl gerçekleştirilir


133

Postgres 9.4 veri türü JSONB belgelerine baktığımda, JSONB sütunlarında güncellemelerin nasıl yapılacağı hemen anlaşılmıyor.

JSONB türleri ve işlevleri için belgeler:

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

Örnek olarak, şu temel tablo yapısına sahibim:

CREATE TABLE test(id serial, data jsonb);

Takmak, aşağıdaki gibi kolaydır:

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

Şimdi, 'veri' sütununu nasıl güncellerim? Bu geçersiz sözdizimidir:

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

Bu, gözden kaçırdığım belli bir yerde belgelendi mi? Teşekkürler.

Yanıtlar:


34

İdeal olarak, ilişkisel bir veritabanı içinde işlemek istediğiniz yapılandırılmış, düzenli veriler için JSON belgelerini kullanmazsınız. Bir kullan normalize ilişkisel tasarımı yerine.

JSON, öncelikle RDBMS içinde manipüle edilmesi gerekmeyen tüm belgeleri depolamaya yöneliktir. İlişkili:

Postgres'te bir satırı güncellemek her zaman tüm satırın yeni bir versiyonunu yazar . Postgres'in MVCC modelinin temel ilkesi budur . Performans açısından bakıldığında, bir JSON nesnesi içindeki tek bir veri parçasını mı yoksa tamamını mı değiştirdiğiniz pek önemli değildir: satırın yeni bir sürümü yazılmalıdır.

Bu nedenle kılavuzdaki tavsiyeler :

JSON verileri, bir tabloda depolandıklarında diğer veri türleriyle aynı eşzamanlılık denetimi hususlarına tabidir. Büyük belgeleri saklamak pratik olsa da, herhangi bir güncellemenin tüm satırda satır düzeyinde bir kilit elde edeceğini unutmayın. Güncelleme işlemleri arasındaki kilit çekişmesini azaltmak için JSON belgelerini yönetilebilir bir boyutla sınırlamayı düşünün. İdeal olarak, JSON belgelerinin her biri, iş kurallarının dikte ettiği atomik bir veriyi temsil etmelidir, bağımsız olarak değiştirilebilecek daha küçük verilere makul bir şekilde daha fazla bölünemez.

İşin özü: JSON nesnesi içindeki herhangi bir şeyi değiştirmek için, sütuna değiştirilmiş bir nesne atamanız gerekir. Postgres json, depolama yeteneklerine ek olarak verileri oluşturmak ve işlemek için sınırlı araçlar sağlar. Araç cephaneliği, 9.2 sürümünden bu yana her yeni sürümde önemli ölçüde artmıştır. Ancak asıl kalan kalır: Her zaman sütuna tam olarak değiştirilmiş bir nesne atamanız gerekir ve Postgres her güncelleme için her zaman yeni bir satır sürümü yazar.

Postgres 9.3 veya sonraki sürümlerin araçlarıyla nasıl çalışılacağı konusunda bazı teknikler:

Bu cevap SO tüm benim diğer cevaplar gibi birçok downvotes olarak yaklaşık çekti birlikte . İnsanlar fikirden hoşlanmıyor gibi görünüyor: normalleştirilmiş bir tasarım dinamik olmayan verilerden daha üstün. Craig Ringer'ın bu mükemmel blog yazısı daha ayrıntılı olarak açıklıyor:


6
Bu yanıt yalnızca JSON türü ile ilgilidir ve JSONB'yi yok sayar.
fiatjaf

7
@fiatjaf: Bu cevap, veri türleri jsonve jsonbbenzerleri için tamamen geçerlidir . Her ikisi de JSON verilerini depolar, jsonbbunu bazı avantajları (ve birkaç dezavantajı) olan normalleştirilmiş bir ikili biçimde yapar. stackoverflow.com/a/10560761/939860 Ne veri türü olduğu için iyidir manipüle veritabanı içinde çok. Hiçbir belge türü değil. Küçük, zor yapılandırılmış JSON belgeleri için sorun değil. Ancak büyük, iç içe geçmiş belgeler bu şekilde bir aptallık olur.
Erwin Brandstetter

7
"Postgres 9.3 araçlarıyla nasıl çalışılacağına ilişkin talimatlar", sorulan soruyu yanıtlarken cevabınızda gerçekten ilk sırada yer alacaktır .. bazen bakım / şema değişiklikleri vb. İçin json'u güncellemek ve json don'u güncellememek için nedenler gerçekten geçerli değil
Michael Wasser

22
Bu cevap yardımcı olmadı, üzgünüm. @jvous, Jimothy'nin cevabını gerçekten soruna cevap verdiği için kabul etmek istemiyor musun?
Bastian Voigt

10
Kendi yorumunuzu / fikrinizi / tartışmanızı eklemeden önce soruyu yanıtlayın.
Ppp

334

Postgresql 9.5'e yükseltme yapabiliyorsanız, jsonb_setkomut diğerlerinin de bahsettiği gibi kullanılabilir.

Aşağıdaki SQL ifadelerinin her birinde, wherekısalık cümlesini atladım; açıkçası, bunu geri eklemek istersiniz.

Adı güncelle:

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

Etiketleri değiştirin (etiket eklemenin veya kaldırmanın aksine):

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

İkinci etiketin değiştirilmesi (0 dizine alınmış):

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

Bir etiket ekleyin ( bu, 999 etiketten az olduğu sürece işe yarar; 999'un 1000 veya daha üstündeki bağımsız değişkeni değiştirmek bir hata oluşturur . Postgres 9.5.3'te artık durum böyle görünmüyor; çok daha büyük bir dizin kullanılabilir) :

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

Son etiketi kaldırın:

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

Karmaşık güncelleme (son etiketi silin, yeni bir etiket ekleyin ve adı değiştirin):

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

Bu örneklerin her birinde, aslında JSON verilerinin tek bir alanını güncellemediğinizi unutmamak önemlidir. Bunun yerine, verilerin geçici, değiştirilmiş bir sürümünü oluşturuyorsunuz ve bu değiştirilmiş sürümü tekrar sütuna atıyorsunuz. Pratikte sonuç aynı olmalı, ancak bunu akılda tutmak son örnekte olduğu gibi karmaşık güncellemeleri daha anlaşılır hale getirmelidir.

Karmaşık örnekte, üç dönüşüm ve üç geçici sürüm vardır: Birincisi, son etiket kaldırılır. Daha sonra bu sürüm, yeni bir etiket eklenerek dönüştürülür. Ardından namealan değiştirilerek ikinci versiyon dönüştürülür . dataSütundaki değer , son sürümle değiştirilir.


42
Eğer OP istendiği gibi bir tablodaki bir sütunu güncelleştirmek için nasıl göstermek için bonus puan almak
chadrik

1
@chadrik: Daha karmaşık bir örnek ekledim. Tam olarak istediğini yapmıyor, ama sana bir fikir vermeli. Dış jsonb_setçağrının girdisinin iç çağrının çıktısı olduğuna ve bu iç çağrının girdisinin bunun sonucu olduğuna dikkat edin data #- '{tags,-1}'. Yani, son etiketi kaldırılmış orijinal veriler.
Jimothy

1
@PranaySoni: Bu amaçla, muhtemelen bir saklı yordamı kullanırım veya ek yük bir sorun değilse, bu verileri geri getirir, uygulamanın dilinde işleyip sonra geri yazırım. Bu kulağa ağır geliyor, ancak aklınızda bulundurun, verdiğim tüm örneklerde hala JSON'da (B) tek bir alanı güncellemiyorsunuz: her iki şekilde de tüm sütunun üzerine yazıyorsunuz. Yani depolanan bir işlem gerçekten de farklı değil.
Jimothy

1
@Alex: Evet, biraz hile. Söylersem {tags,0}, bu "dizinin ilk öğesi" anlamına gelir tagsve bu öğeye yeni bir değer vermeme izin verir. 0 yerine büyük bir sayı kullanarak, dizideki mevcut bir elemanı değiştirmek yerine, diziye yeni bir eleman ekler. Bununla birlikte, dizi gerçekten 999.999.999'dan fazla öğe içeriyorsa, bu yeni bir öğe eklemek yerine son öğenin yerini alacaktır.
Jimothy

1
Peki alan null içeriyorsa? işe yaramıyor görünüyor. Örneğin bilgi jsonb alanı boş: "GÜNCELLEME düzenleyici SET bilgisi = jsonb_set (bilgi, '{ülke}', '" FRA "') burada bilgi - >> 'ülke' :: metin NULL;" GÜNCELLEME 105 kaydı alıyorum ama
db'de


19

Bu sorunla karşılaşan ve çok hızlı bir düzeltme isteyenler için (ve 9.4.5 veya daha önce takılmış olanlar), işte yaptığım şey:

Test masasının oluşturulması

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

Jsonb özelliğinin adını değiştirmek için deyimi güncelleyin

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

Nihayetinde, kabul edilen cevap, bir jsonb nesnesinin tek bir parçasını değiştiremeyeceğiniz için doğrudur (9.4.5 veya öncesinde); ancak, jsonb nesnesini bir dizeye (:: TEXT) dönüştürebilir ve ardından dizeyi değiştirebilir ve jsonb nesnesine (:: jsonb) geri dönüş yapabilirsiniz.

İki önemli uyarı var

  1. bu, json'daki "ad" olarak adlandırılan tüm özelliklerin yerini alacaktır (aynı ada sahip birden çok mülkünüz olması durumunda)
  2. 9.5 kullanıyorsanız bu, jsonb_set kadar verimli değildir

Bununla birlikte, jsonb nesnelerindeki içerik için şemayı güncellemem gereken bir durumla karşılaştım ve bu, orijinal posterin istediğini tam olarak gerçekleştirmenin en basit yoluydu.


1
Yüce Tanrım, jsonb'ye nasıl güncelleme yapacağımı yaklaşık iki saat aradım, böylece tüm \u0000boş karakterleri değiştirebilirdim , örnek resmin tamamını gösterdi. Bunun için teşekkürler!
Joshua Robinson

3
iyi görünüyor! btw Örneğinizde değiştirilecek ikinci bağımsız değişken iki nokta üst üste işaretini içerir ve üçüncü bağımsız değişken içermez. Görünüşe göre aramanız şöyle olmalıreplace(data::TEXT, '"name":', '"my-other-name":')::jsonb
davidicus

Teşekkür ederim @davidicus! Çok geciken güncelleme için özür dilerim, ancak başkaları için paylaşmanızı takdir ediyorum!
Chad Capra

12

Bu soru postgres 9.4 bağlamında sorulmuştur, ancak bu soruya gelen yeni izleyiciler, postgres 9.5'te, JSONB alanlarındaki alt belge Oluşturma / Güncelleme / Silme işlemlerinin uzantıya ihtiyaç duymadan veritabanı tarafından yerel olarak desteklendiğinin farkında olmalıdır. fonksiyonlar.

Bkz: JSONB operatörleri ve işlevleri değiştirme


8

"ad" özelliğini güncelleyin:

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

ve örneğin "ad" ve "etiketler" özelliklerini kaldırmak isterseniz:

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

6

Postgres 9.4'te kendim için yinelemeli olarak çalışan küçük bir işlev yazdım. Aynı problemi yaşadım (iyi ki bu baş ağrısının bir kısmını Postgres 9.5'te çözdüler). Her neyse, işte işlev (umarım sizin için iyi çalışır):

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;

İşte örnek kullanım:

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)

Gördüğünüz gibi derinlemesine analiz edin ve gerektiğinde değerleri güncelleyin / ekleyin.


Bu 9.4'te çalışmıyor, çünkü jsonb_build_object
Greg

@Greg Haklısınız, az önce kontrol ettim ve şimdi PostgreSQL 9.5'i çalıştırıyorum - bu yüzden çalışıyor. Bunu belirttiğiniz için teşekkürler - benim çözümüm 9.4'te işe yaramayacak.
J. Raczkiewicz

4

Belki: GÜNCELLEME test SET verisi = '"diğer-adım"' :: json NEREDE id = 1;

Verilerin bir json türü olduğu durumumla çalıştı


1
Postgresql 9.4.5'te benim için de çalıştı. Tüm kayıt yeniden yazılır, böylece tek bir alan atm güncellenemez.
kometen

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.