如果说 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 会覆盖左边的 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() 跟 || 操作符差不多,但如果你要在函数、触发器或者动态 SQL 里用它会更灵活。它接收两个 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}]
实用例子
更新用户 profile。 假设有个 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() 功能上是一样的。写简单 SQL 时用 || 更简洁。如果你要在程序或触发器里用,jsonb_concat() 会更方便。
常见错误和避免方法
错误:试图合并不兼容的类型。
SELECT '{"key": "value"}'::jsonb || '["value"]'::jsonb;
结果:
ERROR: cannot concatenate jsonb objects and arrays
这里左边是对象,右边是数组 —— PostgreSQL 没法直接合并。要让操作成功,两边类型必须一样:要么都是对象,要么都是数组。
疏忽:用 JSONB 没加索引
如果你经常按 JSONB 字段里的值查数据,但没建索引,查询会很慢。这不算 bug,但性能会很差。记得用 GIN 索引:
CREATE INDEX idx_profile_data ON employees USING gin(profile);
GO TO FULL VERSION