postgresql 9.5使用jsonb_set更新特定的jsonb数组值

Dan*_*ter 10 arrays postgresql json sql-update jsonb

目前我正在使用postgreSQL 9.5并尝试更新jsonb字段数组中的值.但我无法获得所选值的索引

我的表看起来像:

 CREATE TABLE samples (
    id serial,
    sample jsonb
 );
Run Code Online (Sandbox Code Playgroud)

我的JSON看起来像:

{"result": [
    {"8410": "ABNDAT", "8411": "Abnahmedatum"},
    {"8410": "ABNZIT", "8411": "Abnahmezeit"},
    {"8410": "FERR_R", "8411": "Ferritin"}
]}
Run Code Online (Sandbox Code Playgroud)

我的SELECT语句可以获得正确的值:

SELECT 
    id, value 
FROM 
    samples s, jsonb_array_elements(s.sample#>'{result}') r  
WHERE 
    s.id = 26 and r->>'8410' = 'FERR_R';
Run Code Online (Sandbox Code Playgroud)

结果是:

id | value
----------------------------------------------
26 | {"8410": "FERR_R", "8411": "Ferritin"}
Run Code Online (Sandbox Code Playgroud)

好的,这就是我想要的.现在我想使用以下UPDATE语句执行更新以添加新元素"ratingtext"(如果尚未存在):

UPDATE 
    samples s
SET
    sample = jsonb_set(sample,
              '{result,2,ratingtext}',
              '"Some individual text"'::jsonb,
              true)
WHERE
      s.id = 26;
Run Code Online (Sandbox Code Playgroud)

执行UPDATE语句后,我的数据看起来像这样(也正确):

{"result": [
    {"8410": "ABNDAT", "8411": "Abnahmedatum"},
    {"8410": "ABNZIT", "8411": "Abnahmezeit"},
    {"8410": "FERR_R", "8411": "Ferritin", "ratingtext": "Some individual text"}
]}
Run Code Online (Sandbox Code Playgroud)

到目前为止一切顺利,但我手动搜索了索引值2以获得JSON数组中的正确元素.如果订单将被更改,这将不起作用.

所以我的问题:

有没有办法获取所选JSON数组元素的索引并将SELECT语句和UPDATE语句合并为一个?

就像:

UPDATE 
    samples s
SET
    sample = jsonb_set(sample,
              '{result,' || INDEX OF ELEMENT || ',ratingtext}',
              '"Some individual text"'::jsonb,
              true)
WHERE
      s.id = 26;
Run Code Online (Sandbox Code Playgroud)

在准备语句之前,samples.id"8410"的值是已知的.

或者目前这不可能吗?

kli*_*lin 16

您可以使用找到搜索元素的索引jsonb_array_elements() with ordinality(注意,ordinality从1开始,而json数组的第一个索引是0):

select 
    pos- 1 as elem_index
from 
    samples, 
    jsonb_array_elements(sample->'result') with ordinality arr(elem, pos)
where
    id = 26 and
    elem->>'8410' = 'FERR_R';

 elem_index 
------------
          2
(1 row) 
Run Code Online (Sandbox Code Playgroud)

使用上面的查询根据索引更新元素(注意第二个参数jsonb_set()是文本数组):

update 
    samples
set
    sample = 
        jsonb_set(
            sample,
            array['result', elem_index::text, 'ratingtext'],
            '"some individual text"'::jsonb,
            true)
from (
    select 
        pos- 1 as elem_index
    from 
        samples, 
        jsonb_array_elements(sample->'result') with ordinality arr(elem, pos)
    where
        id = 26 and
        elem->>'8410' = 'FERR_R'
    ) sub
where
    id = 26;    
Run Code Online (Sandbox Code Playgroud)

结果:

select id, jsonb_pretty(sample)
from samples;

 id |                   jsonb_pretty                   
----+--------------------------------------------------
 26 | {                                               +
    |     "result": [                                 +
    |         {                                       +
    |             "8410": "ABNDAT",                   +
    |             "8411": "Abnahmedatum"              +
    |         },                                      +
    |         {                                       +
    |             "8410": "ABNZIT",                   +
    |             "8411": "Abnahmezeit"               +
    |         },                                      +
    |         {                                       +
    |             "8410": "FERR_R",                   +
    |             "8411": "Ferritin",                 +
    |             "ratingtext": "Some individual text"+
    |         }                                       +
    |     ]                                           +
    | }
(1 row)
Run Code Online (Sandbox Code Playgroud)

最后一个参数jsonb_set()应该是true强制添加一个新值,如果它的键还不存在.但是可以跳过它,因为它的默认值是true.

虽然并发问题似乎不太可能(由于限制性的WHERE条件和可能的少量受影响的行),您可能也对Postgres中的Atomic UPDATE .. SELECT感兴趣.

  • 非常感谢您的回答!它就像一个魅力!我没有意识到,我可以在 WHERE 子句中使用 **elem**。 (2认同)
  • 谢谢 klin,这拯救了我的一天! (2认同)