JSONb 用一个查询更新多个字段

JSONb update multiply fields with one query

我需要更新 JSON 数组的两个元素中的数据。下面的例子是简化的,通常它有更多的元素和其他键

[{"key": "startDate", "value": "2022-02-05T23:00:00Z"}, {"key": "endDate", "value": "2022-02-05T23:00:00Z"}]

我的目标是在 startDateendDate

中更改 'value'

我的查询是

UPDATE my_table ti
SET fields = jsonb_set(ti.fields, path, temp, false)
FROM my_table ti1,
     LATERAL (
         SELECT ARRAY [(ordinal - 1)::text, 'value']                                               AS path,
                to_jsonb(((field ->> 'value')::timestamp with time zone at time zone 'CET')::date) AS temp
         FROM jsonb_array_elements(ti1.fields #> '{}') WITH ORDINALITY arr(field, ordinal)
         WHERE field ->> 'key' = 'startDate'
           AND field ->> 'value' IS NOT NULL
         ) field
WHERE ti.id = ti1.id;

我为 endDate 执行的相同查询,只是替换了 WHERE 条件。 所以,我有两个查询,但我想用一个替换它

我试图将 WHERE 条件重写为 WHERE field ->> 'key' in ('startDate', 'endDate),但没有成功,结果我只更新了一个(第一个)键值

[{"key": "startDate", "value": "2022-02-06"}, {"key": "endDate", "value": "2022-02-05T23:00:00Z"}]

如何在一个查询中更新两个值?

解决方案 1

如果你想在同一个jsonb数据中进行两次更新,你需要调用jsonb_set函数两次:

UPDATE my_table ti
   SET fields = jsonb_set(jsonb_set(ti1.fields, e.path, e.temp, false), s.path, s.temp, false)
  FROM my_table ti1
 CROSS JOIN LATERAL
     ( SELECT ARRAY [(arr.ordinal - 1)::text, 'value'] AS path
            , to_jsonb(((arr.field ->> 'value')::timestamp with time zone at time zone 'CET')::date) AS temp
         FROM jsonb_array_elements(ti1.fields) WITH ORDINALITY AS arr(field, ordinal)
        WHERE arr.field ->> 'key' = 'startDate'
          AND arr.field ->> 'value' IS NOT NULL
     ) AS s
 CROSS JOIN LATERAL
     ( SELECT ARRAY [(arr.ordinal - 1)::text, 'value'] AS path
            , to_jsonb(((arr.field ->> 'value')::timestamp with time zone at time zone 'CET')::date) AS temp
         FROM jsonb_array_elements(ti1.fields) WITH ORDINALITY AS arr(field, ordinal)
        WHERE arr.field ->> 'key' = 'endDate'
          AND arr.field ->> 'value' IS NOT NULL
     ) AS e
 WHERE ti.id = ti1.id

解决方案 2

您可以基于 jsonb_set 函数创建聚合函数 jsonb_set_agg :

CREATE OR REPLACE FUNCTION jsonb_set(x jsonb, y jsonb, p text[], z jsonb, b boolean)
RETURNS jsonb LANGUAGE sql IMMUTABLE AS
$$ SELECT jsonb_set(COALESCE(x, y), p, z, b) $$ ;

CREATE OR REPLACE AGGREGATE jsonb_set_agg (jsonb, text[], jsonb, boolean)
( SFUNC = jsonb_set
, STYPE = jsonb
) ;

然后你可以迭代新的聚合函数 jsonb_set_agg :

UPDATE my_table AS ti
   SET fields = r.res
  FROM my_table AS ti1
 CROSS JOIN LATERAL
     ( SELECT jsonb_set_agg( ti1.fields
                           , ARRAY [(arr.ordinal - 1)::text, 'value']
                           , to_jsonb(((arr.field ->> 'value')::timestamp with time zone at time zone 'CET')::date)
                           , false
                           ) AS res
         FROM jsonb_array_elements(ti1.fields) WITH ORDINALITY AS arr(field, ordinal)
        WHERE arr.field ->> 'key' IN ('startDate', 'endDate')
          AND arr.field ->> 'value' IS NOT NULL
     ) AS r
 WHERE ti.id = ti1.id