如果說 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);
GO TO FULL VERSION