使用 WHERE 子句更新数组的第 n 个元素

Ara*_*our 4 postgresql update json postgresql-10

我在 PostgreSQL 10 数据库product中有一个jsonb名为“元数据”的列的表。这是我第一次使用文档和 Postgres。jsonb值看起来像这样:

{
  "name": "l33t 衬衫",
  “价格”:“1200”,
  "数量": "60",
  “选项” : 
    {
      “类型”:“收音机”,
      "title": "颜色",
      “选择”:[
        {“价值”:“红色”,“价格”:“-100”,“数量”:“30”},
        {“价值”:“蓝色”,“价格”:“+200”,“数量”:“10”},
        {“价值”:“绿色”,“价格”:“+300”,“数量”:“20”}
      ]
    }
}

两个问题:

1.如何选择“opts”数组中的特定元素?

select metadata->'options'->'opts'->(element here) from product
where  metadata->'options'->'opts' @> '[{"value" : "blue"}]'
Run Code Online (Sandbox Code Playgroud)

2 、当售出一件或多件时,如何更新“数量”(减去当前的“数量”)?

对指南/注释的进一步链接表示赞赏。

Erw*_*ter 6

  1. 如何选择“opts”数组中的特定元素?

按照手册中的说明使用元素编号的索引。对于较长的路径,路径符号较短:

SELECT metadata -> 'options' -> 'opts' -> 0 AS elem0
     , metadata #> '{options, opts, 0}'     AS elem0_path
FROM   product;
Run Code Online (Sandbox Code Playgroud)

获取第n 个元素,就像问题标题所要求的一样(JSON 数组索引从 0 开始)。但是您的示例表明您实际上想要带有"value":"blue". 这可不是小事。您可以jsonb_array_elements()LATERAL连接中取消嵌套的 JSON 数组并过滤您想要的数组。

SELECT opt AS elem_blue
--   , metadata #> '{options, opts}' -> (arr.ord::int - 1) AS elem_blue2
FROM   product
     , jsonb_array_elements(metadata #> '{options, opts}') WITH ORDINALITY arr(opt, ord)
WHERE  opt ->> 'value' = 'blue';
Run Code Online (Sandbox Code Playgroud)

如果未找到匹配元素,则不返回任何内容。

WITH ORDNALITYelem_blue2在这里不需要,但演示我们将在下一步中使用的技术。

更多解释:

2.当一个或多个售出时,如何更新“数量”(减去当前数量)?

从 Postgres 9.5 开始,有jsonb_set(). 更新第一个元素的qty键值:

UPDATE product
SET    metadata =  jsonb_set(metadata, '{options, opts, 0, qty}', '"29"', false)
WHERE  ... -- some filter
Run Code Online (Sandbox Code Playgroud)

旁白:有什么理由将您的整数qty存储为字符串 ( "30") 而不是数字 ( 30) 吗?

为了更新“蓝色”元素,我们使用上面演示的技术确定数组索引,再加上一些UPDATE使其完全动态的魔法:

UPDATE product p
SET    metadata = jsonb_set(p.metadata, path, qty, false)
FROM   product p1
     , LATERAL ( -- move computations to subquery
   SELECT ARRAY['options', 'opts', (ord - 1)::text, 'qty'] AS path  -- fix off-by-one
        , to_jsonb((opt ->> 'qty')::int - 1)               AS qty   -- subtract here!
   FROM   jsonb_array_elements(p1.metadata #> '{options, opts}') WITH ORDINALITY arr(opt, ord)
   WHERE  opt ->> 'value' = 'blue'
-- AND    ...  -- more filters
-- FOR UPDATE  -- see below
   ) opt
WHERE  p1.product_id = p.product_id  -- use PK for match
Run Code Online (Sandbox Code Playgroud)

最后一个查询将一个数字写入“qty” ( '9'),而不是字符串 ( '"9"'),假设您已按照注释修复了它。

我们需要再次productFROM子句中列出表以允许LATERAL连接(否则这是不可能的) - 并自连接到它。

注意一个微小的竞争条件。在并发写负载很重的情况下,您可能希望在子查询 ( FOR UPDATE) 中添加一个 lock 子句,以防止其他事务更改内部SELECT和外部之间的行UPDATE。看:


你不应该,真的。如您所见,jsonb(或任何与此相关的文档类型)不适合对单个属性进行定期更新。这很麻烦,而且相对昂贵。每次都必须写入包含完整文档新版本的新行。改用标准化的 DB 设计,只需重写相对较小的行,而其他部分和索引保持不变。

如果仅定期更新数量,您可以将其移至 1:n 表并将其合并到 JSON 文档中的VIEWMATERIALIZED VIEW。重新设计超出了这个问题的范围。