如何在 Postgres 中加入 jsonb 数组元素?
How to join jsonb array elements in Postgres?
我使用的是 Postgres 9.5,我有以下表格:
用户
- id UUID
- 姓名文字
图片
- id UUID
- 键文本
- 宽度整数
- 身高整数
帖子
- id UUID
- 标题文字
- author_id UUID
- 内容 JSONB
帖子内容如下:
[
{ "type": "text", "text": "learning pg" },
{ "type": "image", "image_id": "8f4422b4-3936-49f5-ab02-50aea5e6755f" },
{ "type": "image", "image_id": "57efc97c-b9b4-4cd5-b1e1-3539f5853835" },
{ "type": "text", "text": "pg is awesome" }
]
现在我要加入图片类型的内容,并用image_id
填充它们,如:
{
"id": "cb1267ca-b1ac-4daa-8c7e-72d4c000e9fa",
"title": "Learning join jsonb in Postgres",
"author_id": "deba01b7-ec58-4cc2-b3ae-7dc42e582767",
"content": [
{ "type": "text", "text": "learning pg" },
{
"type": "image",
"image": {
"id": "8f4422b4-3936-49f5-ab02-50aea5e6755f",
"key": "/upload/test1.jpg",
"width": 800,
"height": 600
}
},
{
"type": "image",
"image": {
"id": "57efc97c-b9b4-4cd5-b1e1-3539f5853835",
"key": "/upload/test2.jpg",
"width": 1280,
"height": 720
}
},
{ "type": "text", "text": "pg is awesome" }
]
}
这是我的测试sql文件:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
DROP TABLE IF EXISTS Users;
DROP TABLE IF EXISTS Images;
DROP TABLE IF EXISTS Posts;
CREATE TABLE Users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name text NOT NULL
);
CREATE TABLE Images (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
key TEXT,
width INTEGER,
height INTEGER,
creator_id UUID
);
CREATE TABLE Posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
title TEXT,
author_id UUID,
content JSONB
);
DO $$
DECLARE user_id UUID;
DECLARE image1_id UUID;
DECLARE image2_id UUID;
BEGIN
INSERT INTO Users (name) VALUES ('test user') RETURNING id INTO user_id;
INSERT INTO Images (key, width, height, creator_id) VALUES ('upload/test1.jpg', 800, 600, user_id) RETURNING id INTO image1_id;
INSERT INTO Images (key, width, height, creator_id) VALUES ('upload/test2.jpg', 600, 400, user_id) RETURNING id INTO image2_id;
INSERT INTO Posts (title, author_id, content) VALUES (
'test post',
user_id,
('[ { "type": "text", "text": "learning pg" }, { "type": "image", "image_id": "' || image1_id || '" }, { "type": "image", "image_id": "' || image2_id || '" }, { "type": "text", "text": "pg is awesome" } ]') :: JSONB
);
END $$;
有什么办法可以实现这个要求吗?
假设至少是 Postgres 9.5,这将完成这项工作:
SELECT jsonb_pretty(to_jsonb(p)) AS post_row_as_json
FROM (
SELECT id, title, author_id, c.content
FROM posts p
LEFT JOIN LATERAL (
SELECT jsonb_agg(
CASE WHEN c.elem->>'type' = 'image' AND i.id IS NOT NULL
THEN elem - 'image_id' || jsonb_build_object('image', i)
ELSE c.elem END) AS content
FROM jsonb_array_elements(p.content) AS c(elem)
LEFT JOIN images i ON c.elem->>'type' = 'image'
AND i.id = (elem->>'image_id')::uuid
) c ON true
) p;
怎么办?
取消嵌套 jsonb
数组,每个数组元素生成 1 行:
jsonb_array_elements(p.content) AS c(elem)
对于每个元素LEFT JOIN
到images
,条件是
一种。 键 'type' 具有 值 'image': c.elem->>'type' = 'image'
b. image_id
中的 UUID 匹配:i.id = (elem->>'image_id')::uuid
请注意,content
中的无效 UUID 会引发异常。
对于图像类型,找到匹配图像的位置
c.elem->>'type' = 'image' AND i.id IS NOT NULL
删除键 'image_id' 并将相关图像行添加为 jsonb
值:
elem - 'image_id' || jsonb_build_object('image', i)
否则保留原始元素。
使用jsonb_agg()
.
将修改后的元素重新聚合到新的content
列
也适用于普通 ARRAY constructor。
无条件LEFT JOIN LATERAL
结果到posts
和select所有列,只用生成的替换c.content
替换p.content
在外部 SELECT
中,使用简单的 to_jsonb()
.
将整行转换为 jsonb
jsonb_pretty()
对于人类可读表示来说是完全可选的。
我使用的是 Postgres 9.5,我有以下表格:
用户
- id UUID
- 姓名文字
图片
- id UUID
- 键文本
- 宽度整数
- 身高整数
帖子
- id UUID
- 标题文字
- author_id UUID
- 内容 JSONB
帖子内容如下:
[
{ "type": "text", "text": "learning pg" },
{ "type": "image", "image_id": "8f4422b4-3936-49f5-ab02-50aea5e6755f" },
{ "type": "image", "image_id": "57efc97c-b9b4-4cd5-b1e1-3539f5853835" },
{ "type": "text", "text": "pg is awesome" }
]
现在我要加入图片类型的内容,并用image_id
填充它们,如:
{
"id": "cb1267ca-b1ac-4daa-8c7e-72d4c000e9fa",
"title": "Learning join jsonb in Postgres",
"author_id": "deba01b7-ec58-4cc2-b3ae-7dc42e582767",
"content": [
{ "type": "text", "text": "learning pg" },
{
"type": "image",
"image": {
"id": "8f4422b4-3936-49f5-ab02-50aea5e6755f",
"key": "/upload/test1.jpg",
"width": 800,
"height": 600
}
},
{
"type": "image",
"image": {
"id": "57efc97c-b9b4-4cd5-b1e1-3539f5853835",
"key": "/upload/test2.jpg",
"width": 1280,
"height": 720
}
},
{ "type": "text", "text": "pg is awesome" }
]
}
这是我的测试sql文件:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
DROP TABLE IF EXISTS Users;
DROP TABLE IF EXISTS Images;
DROP TABLE IF EXISTS Posts;
CREATE TABLE Users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name text NOT NULL
);
CREATE TABLE Images (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
key TEXT,
width INTEGER,
height INTEGER,
creator_id UUID
);
CREATE TABLE Posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
title TEXT,
author_id UUID,
content JSONB
);
DO $$
DECLARE user_id UUID;
DECLARE image1_id UUID;
DECLARE image2_id UUID;
BEGIN
INSERT INTO Users (name) VALUES ('test user') RETURNING id INTO user_id;
INSERT INTO Images (key, width, height, creator_id) VALUES ('upload/test1.jpg', 800, 600, user_id) RETURNING id INTO image1_id;
INSERT INTO Images (key, width, height, creator_id) VALUES ('upload/test2.jpg', 600, 400, user_id) RETURNING id INTO image2_id;
INSERT INTO Posts (title, author_id, content) VALUES (
'test post',
user_id,
('[ { "type": "text", "text": "learning pg" }, { "type": "image", "image_id": "' || image1_id || '" }, { "type": "image", "image_id": "' || image2_id || '" }, { "type": "text", "text": "pg is awesome" } ]') :: JSONB
);
END $$;
有什么办法可以实现这个要求吗?
假设至少是 Postgres 9.5,这将完成这项工作:
SELECT jsonb_pretty(to_jsonb(p)) AS post_row_as_json
FROM (
SELECT id, title, author_id, c.content
FROM posts p
LEFT JOIN LATERAL (
SELECT jsonb_agg(
CASE WHEN c.elem->>'type' = 'image' AND i.id IS NOT NULL
THEN elem - 'image_id' || jsonb_build_object('image', i)
ELSE c.elem END) AS content
FROM jsonb_array_elements(p.content) AS c(elem)
LEFT JOIN images i ON c.elem->>'type' = 'image'
AND i.id = (elem->>'image_id')::uuid
) c ON true
) p;
怎么办?
取消嵌套
jsonb
数组,每个数组元素生成 1 行:jsonb_array_elements(p.content) AS c(elem)
对于每个元素
LEFT JOIN
到images
,条件是
一种。 键 'type' 具有 值 'image':c.elem->>'type' = 'image'
b.image_id
中的 UUID 匹配:i.id = (elem->>'image_id')::uuid
请注意,content
中的无效 UUID 会引发异常。对于图像类型,找到匹配图像的位置
c.elem->>'type' = 'image' AND i.id IS NOT NULL
删除键 'image_id' 并将相关图像行添加为
jsonb
值:elem - 'image_id' || jsonb_build_object('image', i)
否则保留原始元素。
使用
jsonb_agg()
.
将修改后的元素重新聚合到新的content
列 也适用于普通 ARRAY constructor。无条件
LEFT JOIN LATERAL
结果到posts
和select所有列,只用生成的替换c.content
替换p.content
在外部
SELECT
中,使用简单的to_jsonb()
.
将整行转换为jsonb
jsonb_pretty()
对于人类可读表示来说是完全可选的。