วิธีดำเนินการอัปเดตคอลัมน์ประเภท JSONB ใน Postgres 9.4


134

เมื่อดูเอกสารสำหรับ Postgres 9.4 ประเภทข้อมูล JSONB ฉันไม่เห็นวิธีการอัปเดตในคอลัมน์ JSONB ในทันที

เอกสารสำหรับประเภทและฟังก์ชัน JSONB:

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

ตัวอย่างเช่นฉันมีโครงสร้างตารางพื้นฐานนี้:

CREATE TABLE test(id serial, data jsonb);

การแทรกทำได้ง่ายเช่นเดียวกับ:

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

ตอนนี้ฉันจะอัปเดตคอลัมน์ 'data' ได้อย่างไร? นี่เป็นไวยากรณ์ที่ไม่ถูกต้อง:

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

นี่เป็นเอกสารที่ชัดเจนว่าฉันพลาดไปหรือไม่? ขอบคุณ

คำตอบ:


33

ตามหลักการแล้วคุณไม่ได้ใช้เอกสาร JSON สำหรับข้อมูลที่มีโครงสร้างและเป็นประจำที่คุณต้องการจัดการภายในฐานข้อมูลเชิงสัมพันธ์ ใช้การออกแบบเชิงสัมพันธ์แบบปกติแทน

JSON มีวัตถุประสงค์หลักเพื่อจัดเก็บเอกสารทั้งหมดที่ไม่จำเป็นต้องจัดการภายใน RDBMS ที่เกี่ยวข้อง:

การอัปเดตแถวใน Postgres จะเขียนเวอร์ชันใหม่ของทั้งแถวเสมอ นั่นเป็นหลักการพื้นฐานของPostgres' รุ่น จากมุมมองด้านประสิทธิภาพแทบจะไม่สำคัญว่าคุณจะเปลี่ยนข้อมูลชิ้นเดียวภายในออบเจ็กต์ JSON หรือทั้งหมด: ต้องเขียนแถวเวอร์ชันใหม่

ดังนั้นคำแนะนำในคู่มือ :

ข้อมูล JSON อยู่ภายใต้การพิจารณาการควบคุมการทำงานพร้อมกันเช่นเดียวกับข้อมูลประเภทอื่น ๆ เมื่อเก็บไว้ในตาราง แม้ว่าการจัดเก็บเอกสารขนาดใหญ่จะสามารถทำได้ แต่โปรดทราบว่าการอัปเดตใด ๆ จะได้รับการล็อกระดับแถวบนทั้งแถว พิจารณา จำกัด เอกสาร JSON ให้มีขนาดที่จัดการได้เพื่อลดความขัดแย้งระหว่างการอัปเดตธุรกรรม ตามหลักการแล้วเอกสาร JSON แต่ละรายการควรแสดงถึงข้อมูลอะตอมที่กฎทางธุรกิจกำหนดไว้ไม่สามารถแบ่งย่อยออกเป็นดาต้าขนาดเล็กที่สามารถแก้ไขได้อย่างอิสระ

ความสำคัญของมัน: ในการแก้ไขสิ่งใด ๆภายในออบเจ็กต์ JSON คุณต้องกำหนดวัตถุที่แก้ไขให้กับคอลัมน์ Postgres จัดหาวิธีการที่ จำกัด ในการสร้างและจัดการjsonข้อมูลนอกเหนือจากความสามารถในการจัดเก็บข้อมูล คลังแสงของเครื่องมือเติบโตขึ้นอย่างมากเมื่อมีการเปิดตัวใหม่ทุกครั้งตั้งแต่เวอร์ชัน 9.2 แต่ยังคงเงินต้น: คุณมักจะต้องกำหนดวัตถุที่มีการปรับเปลี่ยนที่สมบูรณ์เพื่อคอลัมน์และ Postgres เสมอเขียนรุ่นแถวใหม่สำหรับการปรับปรุงใด ๆ

เทคนิคบางอย่างในการทำงานกับเครื่องมือของ Postgres 9.3 หรือใหม่กว่า:

คำตอบนี้ได้ดึงดูดเกี่ยวกับการเป็น downvotes เป็นจำนวนมากทุกคำตอบอื่น ๆ ของฉันใน SO กัน ดูเหมือนผู้คนจะไม่ชอบแนวคิดนี้: การออกแบบที่เป็นมาตรฐานนั้นดีกว่าสำหรับข้อมูลที่ไม่ใช่ไดนามิก โพสต์บล็อกที่ยอดเยี่ยมนี้โดย Craig Ringer อธิบายรายละเอียดเพิ่มเติม:


6
คำตอบนี้เกี่ยวข้องกับประเภท JSON เท่านั้นและไม่สนใจ JSONB
fiatjaf

7
@fiatjaf: คำตอบนี้ใช้ได้อย่างสมบูรณ์กับประเภทข้อมูลjsonและjsonbเหมือนกัน ทั้งสองจัดเก็บข้อมูล JSON jsonbในรูปแบบไบนารีที่เป็นมาตรฐานซึ่งมีข้อดีบางประการ (และข้อเสียเล็กน้อย) stackoverflow.com/a/10560761/939860ข้อมูลทั้งสองประเภทไม่เหมาะสำหรับการจัดการจำนวนมากภายในฐานข้อมูล ไม่มีประเภทเอกสารคือ มันดีสำหรับเอกสาร JSON ขนาดเล็กที่แทบจะไม่มีโครงสร้าง แต่เอกสารขนาดใหญ่ที่ซ้อนกันจะเป็นเรื่องโง่เขลา
Erwin Brandstetter

7
"คำแนะนำในการทำงานกับเครื่องมือของ Postgres 9.3" เป็นเรื่องแรกในคำตอบของคุณเนื่องจากตอบคำถามที่ถาม .. บางครั้งการอัปเดต json เพื่อการบำรุงรักษา / การเปลี่ยนแปลง schema เป็นต้นและเหตุผลที่ไม่ควรอัปเดต json don ใช้ไม่ได้จริงๆ
Michael Wasser

22
คำตอบนี้ไม่เป็นประโยชน์ขออภัย @jvous คุณไม่ต้องการยอมรับคำตอบของ Jimothy เพราะมันตอบคำถามของคุณได้จริงหรือ
Bastian Voigt

11
ตอบคำถามก่อนเพิ่มความคิดเห็น / ความคิดเห็น / การอภิปรายของคุณเอง
Ppp

334

หากคุณสามารถอัปเกรดเป็น Postgresql 9.5 ได้jsonb_setคำสั่งดังกล่าวจะพร้อมใช้งานตามที่คนอื่นกล่าวถึง

ในแต่ละคำสั่ง SQL ต่อไปนี้ฉันได้ละเว้นwhereประโยคสำหรับความกะทัดรัด เห็นได้ชัดว่าคุณต้องการเพิ่มกลับเข้าไป

ชื่ออัปเดต:

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

แทนที่แท็ก (ตรงกันข้ามกับการเพิ่มหรือลบแท็ก):

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

การแทนที่แท็กที่สอง (ดัชนี 0):

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

ต่อท้ายแท็ก ( จะใช้ได้ตราบเท่าที่มีน้อยกว่า 999 แท็กการเปลี่ยนอาร์กิวเมนต์ 999 ถึง 1,000 ขึ้นไปจะทำให้เกิดข้อผิดพลาดซึ่งดูเหมือนจะไม่เป็นเช่นนั้นใน Postgres 9.5.3 อีกต่อไปสามารถใช้ดัชนีที่ใหญ่กว่านี้ได้มาก) :

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

ลบแท็กสุดท้าย:

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

การอัปเดตที่ซับซ้อน (ลบแท็กสุดท้ายแทรกแท็กใหม่และเปลี่ยนชื่อ):

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

โปรดทราบว่าในแต่ละตัวอย่างเหล่านี้คุณไม่ได้อัปเดตข้อมูล JSON เพียงช่องเดียว แต่คุณกำลังสร้างข้อมูลเวอร์ชันชั่วคราวที่แก้ไขแล้วและกำหนดเวอร์ชันที่แก้ไขนั้นกลับไปที่คอลัมน์ ในทางปฏิบัติผลลัพธ์ควรจะเหมือนกัน แต่การคำนึงถึงสิ่งนี้ควรทำการอัปเดตที่ซับซ้อนเช่นตัวอย่างสุดท้ายเพื่อให้เข้าใจได้มากขึ้น

ในตัวอย่างที่ซับซ้อนมีการเปลี่ยนแปลงสามแบบและเวอร์ชันชั่วคราวสามแบบ: อันดับแรกแท็กสุดท้ายจะถูกลบออก จากนั้นเวอร์ชันดังกล่าวจะถูกเปลี่ยนโดยการเพิ่มแท็กใหม่ ถัดไปเวอร์ชันที่สองจะถูกเปลี่ยนโดยการเปลี่ยนnameฟิลด์ ค่าในdataคอลัมน์จะถูกแทนที่ด้วยเวอร์ชันสุดท้าย


42
คุณจะได้รับคะแนนโบนัสจากการแสดงวิธีอัปเดตคอลัมน์ในตารางตามที่ OP ร้องขอ
chadrik

1
@chadrik: ฉันได้เพิ่มตัวอย่างที่ซับซ้อนมากขึ้น มันไม่ได้ทำตามที่คุณร้องขออย่างแน่นอน แต่ควรให้ความคิด โปรดทราบว่าการป้อนข้อมูลไปยังด้านนอกโทรถูกขับออกจากการโทรภายในและการป้อนข้อมูลเพื่อการโทรภายในที่เป็นผลมาจากjsonb_set data #- '{tags,-1}'กล่าวคือข้อมูลเดิมที่มีการลบแท็กสุดท้าย
Jimothy

1
@PranaySoni: เพื่อจุดประสงค์นั้นฉันอาจจะใช้ขั้นตอนการจัดเก็บหรือถ้าค่าใช้จ่ายไม่น่ากังวลให้นำข้อมูลนั้นกลับมาจัดการในภาษาของแอปพลิเคชันจากนั้นเขียนกลับ ฟังดูหนัก แต่โปรดจำไว้ว่าในตัวอย่างทั้งหมดที่ฉันให้ไว้คุณยังไม่ได้อัปเดตฟิลด์เดียวใน JSON (B): คุณกำลังเขียนทับคอลัมน์ทั้งหมด ดังนั้น proc ที่เก็บไว้ก็ไม่ต่างกัน
Jimothy

1
@ อเล็กซ์: ใช่แฮ็คนิดหน่อย ถ้าฉันพูด{tags,0}นั่นจะหมายถึง "องค์ประกอบแรกของอาร์เรย์tags" ทำให้ฉันสามารถให้ค่าใหม่กับองค์ประกอบนั้นได้ ด้วยการใช้ตัวเลขจำนวนมากแทน 0 แทนที่จะแทนที่องค์ประกอบที่มีอยู่ในอาร์เรย์จะเพิ่มองค์ประกอบใหม่ให้กับอาร์เรย์ อย่างไรก็ตามหากอาร์เรย์มีองค์ประกอบมากกว่า 999,999,999 รายการจริงสิ่งนี้จะแทนที่องค์ประกอบสุดท้ายแทนที่จะเพิ่มองค์ประกอบใหม่
Jimothy

1
แล้วถ้าฟิลด์มี null ล่ะ? ดูไม่ได้ผล เช่นฟิลด์ข้อมูล jsonb เป็นโมฆะ: "UPDATE organizer SET info = jsonb_set (info, '{country}', '" FRA "') โดยที่ข้อมูล - >> 'country' :: text IS NULL;" ฉันได้รับการอัปเดต 105 บันทึก แต่ ไม่มีการเปลี่ยนแปลงบน db
stackdave

24

สิ่งนี้มาใน 9.5 ในรูปแบบของjsonb_setโดยAndrew Dunstanตามส่วนขยายที่มีอยู่jsonbxซึ่งทำงานร่วมกับ 9.4


ปัญหาในสายนี้อีกคือการใช้jsonb_build_object()เพราะไม่ได้ผลตอบแทนที่คู่คีย์วัตถุเพื่อเติมที่คุณต้องการx->key jsonb_set(target, path, jsonb_build_object('key',x->key))
Peter Krauss

19

สำหรับผู้ที่พบปัญหานี้และต้องการการแก้ไขอย่างรวดเร็ว (และติดอยู่ที่ 9.4.5 หรือก่อนหน้า) นี่คือสิ่งที่ฉันทำ:

การสร้างตารางทดสอบ

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

อัปเดตคำสั่งเพื่อเปลี่ยนชื่อคุณสมบัติ jsonb

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

ท้ายที่สุดคำตอบที่ยอมรับนั้นถูกต้องเนื่องจากคุณไม่สามารถปรับเปลี่ยนชิ้นส่วนของวัตถุ jsonb แต่ละชิ้นได้ (ใน 9.4.5 หรือก่อนหน้า) อย่างไรก็ตามคุณสามารถส่งวัตถุ jsonb ไปยังสตริง (:: TEXT) จากนั้นจัดการสตริงและส่งกลับไปที่วัตถุ jsonb (:: jsonb)

มีสองคำเตือนที่สำคัญ

  1. สิ่งนี้จะแทนที่คุณสมบัติทั้งหมดที่เรียกว่า "name" ใน json (ในกรณีที่คุณมีหลายคุณสมบัติที่มีชื่อเดียวกัน)
  2. สิ่งนี้ไม่มีประสิทธิภาพเท่า jsonb_set ถ้าคุณใช้ 9.5

ด้วยเหตุนี้ฉันจึงเจอสถานการณ์ที่ฉันต้องอัปเดตสคีมาสำหรับเนื้อหาในวัตถุ jsonb และนี่เป็นวิธีที่ง่ายที่สุดในการบรรลุสิ่งที่ผู้โพสต์ดั้งเดิมถาม


1
ลอร์ดที่ดีฉันค้นหาวิธีการอัปเดต jsonb เป็นเวลาสองชั่วโมงดังนั้นฉันจึงสามารถแทนที่\u0000อักขระว่างทั้งหมดได้ตัวอย่างแสดงให้เห็นภาพที่สมบูรณ์ ขอบคุณสำหรับสิ่งนี้!
Joshua Robinson

3
ดูดี! btw อาร์กิวเมนต์ที่สองที่จะแทนที่ในตัวอย่างของคุณมีโคลอนและอันที่สามไม่ได้ ดูเหมือนว่าคุณควรโทรมาreplace(data::TEXT, '"name":', '"my-other-name":')::jsonb
davidicus

ขอบคุณ @davidicus! ขออภัยสำหรับการอัปเดตที่ล่าช้ามาก แต่ขอขอบคุณที่แบ่งปันให้กับผู้อื่น!
Chad Capra

12

คำถามนี้ถูกถามในบริบทของ postgres 9.4 อย่างไรก็ตามผู้ชมใหม่ที่มาถึงคำถามนี้ควรทราบว่าใน postgres 9.5 การดำเนินการสร้าง / อัปเดต / ลบเอกสารย่อยในฟิลด์ JSONB ได้รับการสนับสนุนโดยฐานข้อมูลโดยไม่จำเป็นต้องมีส่วนขยาย ฟังก์ชั่น.

ดู: JSONB แก้ไขตัวดำเนินการและฟังก์ชัน


8

อัปเดตแอตทริบิวต์ "ชื่อ":

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

และหากคุณต้องการลบเช่นแอตทริบิวต์ "name" และ "tags":

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

6

ฉันเขียนฟังก์ชันเล็ก ๆ สำหรับตัวเองซึ่งทำงานซ้ำได้ใน Postgres 9.4 ฉันมีปัญหาเดียวกัน (ดีพวกเขาแก้อาการปวดหัวนี้ได้บ้างใน Postgres 9.5) อย่างไรก็ตามนี่คือฟังก์ชั่น (ฉันหวังว่ามันจะทำงานได้ดีสำหรับคุณ):

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;

นี่คือตัวอย่างการใช้งาน:

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)

ดังที่คุณเห็นการวิเคราะห์เชิงลึกและอัปเดต / เพิ่มค่าตามต้องการ


สิ่งนี้ใช้ไม่ได้ใน 9.4 เนื่องจากjsonb_build_objectเปิดตัวใน 9.5
Greg

@Greg คุณพูดถูกฉันเพิ่งตรวจสอบและฉันใช้งาน PostgreSQL 9.5 ตอนนี้ - นี่คือเหตุผลว่าทำไมมันถึงได้ผล ขอบคุณที่ชี้ให้เห็น - วิธีแก้ปัญหาของฉันใช้ไม่ได้ใน 9.4
เจ Raczkiewicz

4

อาจจะ: UPDATE test SET data = '"my-other-name"' :: json WHERE id = 1;

มันใช้ได้กับกรณีของฉันโดยที่ข้อมูลเป็นประเภท json


1
ทำงานให้ฉันด้วยเช่นกันใน postgresql 9.4.5 เร็กคอร์ดทั้งหมดถูกเขียนใหม่ดังนั้นจึงไม่สามารถอัปเดต atm ฟิลด์เดียวได้
kometen

2

Matheus de Oliveira สร้างฟังก์ชันที่มีประโยชน์สำหรับการดำเนินการ JSON CRUD ใน postgresql สามารถนำเข้าโดยใช้คำสั่ง \ i สังเกต jsonb fork ของฟังก์ชันถ้า jsonb ถ้าประเภทข้อมูลของคุณ

9.3 json https://gist.github.com/matheusoliveira/9488951

9.4 jsonb https://gist.github.com/inindev/2219dff96851928c2282

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.