sql - 如何在 Postgres 中加入 jsonb 数组元素?

标签 sql json postgresql jsonb

我使用的是 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 $$;

有没有办法实现这个需求?

最佳答案

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;

如何?

  1. 取消嵌套 jsonb 数组,每个数组元素生成 1 行:

    jsonb_array_elements(p.content) AS c(elem)
    
  2. 对于每个元素LEFT JOINimages的条件是

    • ... key 'type' 有 value 'image': c.elem->>'type' = 'image'
    • ... image_id 中的 UUID 匹配:i.id = (elem->>'image_id')::uuid
      content 中的无效 UUID 会引发异常。
  3. 对于图像类型,找到匹配的图像

    c.elem->>'type' = 'image' AND i.id IS NOT NULL
    

    删除键 'image_id' 并将相关图像行添加为 jsonb 值:

    elem - 'image_id' || jsonb_build_object('image', i)
    

    否则保留原始元素。

  4. 使用 jsonb_agg() 将修改后的元素重新聚合到新的 content 列。
    可以使用普通的 ARRAY constructor

  5. 无条件地LEFT JOIN LATERAL将结果posts并选择所有列,仅将p.content替换为生成的替换c.content

  6. 在外层 SELECT 中,使用简单的 to_jsonb() 将整行转换为 jsonb
    jsonb_pretty() 仅用于人类可读的表示并且完全可选。

所有 jsonb 函数均已记录 in the manual .

关于sql - 如何在 Postgres 中加入 jsonb 数组元素?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40413861/

相关文章:

sql - 如何检查 XML 在 SQL 中是否有值(value)?

json - 我想向 JSON 对象添加一个属性,并将整数数组作为 Java 中的值

sql - 为什么 ruby​​ 对字符串数组的排序不同于 sql 顺序(postgres)?

sql - 如何在 PostgreSQL 中使用条件查询和子查询创建唯一索引?

mysql - 如何使用 Rails 删除 MySQL 中的重复项?

mysql - 根据最大订单从 2 个不同的表中选择数据

用于存储在字符串中的小数的 MySQL Round() 函数

javascript - 确保以 JSON 格式接收 HTTP POST 请求并使用 REST API 将其保存到 MySQL

javascript - JSON:parseInt 不起作用

sql - 尝试优化 SQL 查询