更新对象数组中的特定对象 Postgres jsonb

Update specific object in array of objects Postgres jsonb

我正在尝试更新 table Books 上的 jsonb 列 pagesRead,其中包含一个对象数组。它的结构类似于:

[{
    "book": "Moby Dick",
    "pagesRead": [
    "1",
    "2",
    "3",
    "4"
    ]
},
{
    "book": "Book Thief",
    "pagesRead": [
    "1",
    "2"
    ]
}]

我想做的是在阅读本书的特定页面时更新 pagesRead,或者如果有人开始阅读新书,则在其中添加一个额外的条目。

我可以检索到具体的图书详细信息,但我不确定如何更新它。

编辑:所以我不得不使用 S-Man 的更新查询来添加图书条目,但我使用 Barbaros Özhan 的插入查询来处理更新页面

之前的一些想法:

  1. 切勿将结构化数据按原样存储在一列中。这会产生更新、索引(因此,searching/performance)、过滤等所有问题。请将所有内容规范化为适当的表格和列
  2. 永远不要存储数组。规范化它。
  3. 不要使用文字类型来存储整数(页数)
  4. "pagesRead" 是您的过滤器元素 ("book") 的兄弟元素。这使得引用它比引用它作为 child 复杂得多。因此,将书名(或更好:id)视为 {"my_id_for_book_thief": {"name" : "Book Thief", "pagesRead": [...]}} 之类的键。在这种情况下,您可以使用路径来引用它。否则,我们需要提取数组,查看每个 book 属性并引用其兄弟

demo:db<>fiddle

添加 book 非常简单(假设您使用类型 jsonb 而不是类型 json):

SELECT mydata || '{"book": "Lord Of The Rings", "pagesRead": []}'
FROM mytable

更新:

UPDATE mytable
SET mycolumn = mycolumn || '{"book": "Lord Of The Rings", "pagesRead": []}'

添加一个 pagesRead 值:

SELECT 
    jsonb_agg(                                                         -- 4
        jsonb_build_object(                                            -- 3
            'book', elem -> 'book',
            'pagesRead', CASE WHEN elem ->> 'book' = 'Moby Dick' THEN  -- 2
                             elem -> 'pagesRead' || '"42"'
                         ELSE elem -> 'pagesRead' END
        )
    ) as new_array
FROM mytable,
    jsonb_array_elements(mydata) as elem                               -- 1
  1. 将数组提取为每个元素一条记录
  2. 如果元素包含正确的书籍,则添加一个页面
  3. 重建object
  4. 重新聚合数组。

更新为:

UPDATE mytable
SET mycolumn = s.new_array
FROM (
    -- <query above>
) s

假设你想为第二本书(Book Thief)添加一个新页面,那么使用JSONB_INSERT()函数和下面的更新语句就足够了

UPDATE books
   SET pagesRead = JSONB_INSERT(pagesRead,'{1,pagesRead,1}','"3"'::JSONB,true)

但是,为了使其成为动态解决方案,不知道书在主数组中的位置,并将新页码添加到所需书的 pagesRead 数组的末尾,确定子查询中的位置和相关数组的长度为

WITH b AS
(
 SELECT idx-1 AS pos1, 
        JSONB_ARRAY_LENGTH( (j ->> 'pagesRead')::JSONB )-1 AS pos2
   FROM books 
  CROSS JOIN JSONB_ARRAY_ELEMENTS(pagesRead) 
   WITH ORDINALITY arr(j,idx)
  WHERE j ->> 'book' = 'Book Thief'
)
UPDATE books
   SET pagesRead = 
        JSONB_INSERT(
                     pagesRead,
                     ('{'||pos1||',pagesRead,'||pos2||'}')::TEXT[], 
                    --# pos1 stands for the position within the main array
                    --# pos2 stands for the position within the related pagesRead array
                     '"3"'::JSONB, --# an arbitrary page number
                     true --# the new page value will be inserted after the target path
                     ) 
  FROM b 

Demo