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"}]
我的目标是在 startDate
和 endDate
中更改 '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
我需要更新 JSON 数组的两个元素中的数据。下面的例子是简化的,通常它有更多的元素和其他键
[{"key": "startDate", "value": "2022-02-05T23:00:00Z"}, {"key": "endDate", "value": "2022-02-05T23:00:00Z"}]
我的目标是在 startDate
和 endDate
我的查询是
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