CodeGym /課程 /SQL SELF /用 || 運算子和 jsonb_concat() 函式合併與修改 JSON 物件

用 || 運算子和 jsonb_concat() 函式合併與修改 JSON 物件

SQL SELF
等級 34, 課堂 0
開放

如果說 JSONB 就像個魔法寶箱,讓我們放資料進去,那 || 跟 jsonb_concat() 就是可以把寶箱合併或改內容的工具。在實際開發時,你可能會想把幾個 JSON 物件合併、把一個物件的資料加到另一個,或是把兩個陣列合成一個清單。

舉例來說,假設你有兩個 JSONB 物件:

{"name": "Alice", "age": 25}

還有

{"city": "Wonderland", "hobbies": ["reading", "chess"]}

你想要得到:

{"name": "Alice", "age": 25, "city": "Wonderland", "hobbies": ["reading", "chess"]}

或者合併兩個 JSONB 陣列:

[1, 2, 3]

還有

[4, 5, 6]

讓結果變成:

[1, 2, 3, 4, 5, 6]

這些都可以用 || 或 jsonb_concat() 搞定。來看看怎麼做吧。

|| 運算子合併 JSONB

|| 運算子可以在 PostgreSQL 裡合併兩個 JSONB 物件或陣列。它很簡單、快速又好用。這裡是它的基本規則:

  • 如果合併的是兩個 JSONB 物件,結果會包含兩邊的所有 key 跟 value。
  • 如果 key 有重複,右邊的 value 會蓋掉左邊的。
  • 如果合併的是 JSONB 陣列,左邊跟右邊的元素會合成一個新陣列。

範例 1:合併兩個 JSONB 物件

SELECT '{"name": "Alice", "age": 25}'::jsonb || '{"city": "Wonderland", "hobbies": ["reading", "chess"]}'::jsonb AS merged_object;

結果:

{"name": "Alice", "age": 25, "city": "Wonderland", "hobbies": ["reading", "chess"]}

範例 2:key 重複時更新 value

SELECT '{"name": "Alice", "age": 25}'::jsonb || '{"age": 30, "city": "Wonderland"}'::jsonb AS updated_object;

結果:

{"name": "Alice", "age": 30, "city": "Wonderland"}

注意,key "age" 的值被右邊的物件蓋掉了。

範例 3:合併陣列

SELECT '[1, 2, 3]'::jsonb || '[4, 5, 6]'::jsonb AS merged_array;

結果:

[1, 2, 3, 4, 5, 6]

jsonb_concat() 函式合併 JSONB

jsonb_concat() 跟 || 運算子很像,但如果你要在 function、trigger 或動態查詢裡用,這個函式會更彈性。它吃兩個 JSONB 參數,回傳合併後的結果。

範例:用 jsonb_concat()

SELECT jsonb_concat('{"a": 1, "b": 2}'::jsonb, '{"b": 3, "c": 4}'::jsonb) AS combined;

結果:

{"a": 1, "b": 3, "c": 4}

合併物件和陣列:細節跟注意事項

合併有重複 key 的物件時要記得,右邊物件的值會優先。

舉例:

SELECT '{"key1": "value1"}'::jsonb || '{"key1": "value2"}'::jsonb AS result;

結果:

{"key1": "value2"}

如果你不想讓值被蓋掉,建議把資料放不同的 key,或用別的方式(像是陣列)。

另外,陣列永遠是把右邊的元素加到左邊。舉例:

SELECT '["a", "b"]'::jsonb || '["c", "d"]'::jsonb AS result;

結果:

["a", "b", "c", "d"]

如果陣列裡面是物件,順序也會保留:

SELECT '[{"id": 1}, {"id": 2}]'::jsonb || '[{"id": 3}]'::jsonb AS result;

結果:

[{"id": 1}, {"id": 2}, {"id": 3}]

實戰範例

更新使用者個人資料。 假設我們有個 users 資料表,profile 是 JSONB 格式:

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    profile JSONB
);

INSERT INTO users (profile) VALUES ('{"name": "Alice", "age": 25}');

現在我們想加上居住城市:

UPDATE users 
SET profile = profile || '{"city": "Wonderland"}'
WHERE id = 1;

查詢結果:

{"name": "Alice", "age": 25, "city": "Wonderland"}

合併訂單資料。 假設有個 orders 資料表:

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    details JSONB
);

INSERT INTO orders (details) VALUES ('{"items": [{"product": "laptop", "quantity": 1}]}');

現在我們要再加一個商品到訂單裡:

UPDATE orders
SET details = jsonb_set(
    details,
    '{items}',
    details->'items' || '[{"product": "mouse", "quantity": 2}]'::jsonb
)
WHERE id = 1;

查詢結果:

{"items": [{"product": "laptop", "quantity": 1}, {"product": "mouse", "quantity": 2}]}

|| 跟 jsonb_concat() 的差異

功能上 || 跟 jsonb_concat() 幾乎一樣。寫簡單查詢時用 || 比較短;如果你要在程式或 trigger 裡明確呼叫,jsonb_concat() 會比較方便。

常見錯誤與避免方式

錯誤:合併不相容的型別。

SELECT '{"key": "value"}'::jsonb || '["value"]'::jsonb;

結果:

ERROR:  cannot concatenate jsonb objects and arrays

這裡左邊是物件,右邊是陣列 — PostgreSQL 沒辦法直接合併。要讓操作成功,兩邊型別要一樣:要嘛都是物件,要嘛都是陣列。

疏忽:沒幫 JSONB 欄位加 index

如果你常常根據 JSONB 欄位裡的值查資料,卻沒加 index,查詢會超慢。這不算 bug,但效能會很慘。記得加 GIN index:

CREATE INDEX idx_profile_data ON employees USING gin(profile);
2
任務
SQL SELF, 等級 34, 課堂 0
上鎖
更新 JSONB 物件並替換鍵值
更新 JSONB 物件並替換鍵值
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION