更新 jsonb PostgreSQL 中数组的对象字段
Update object field of array in jsonb PostgreSQL
我在 PostgreSQL 中有以下 table:
CREATE TABLE resume (
resume_id UUID PRIMARY KEY,
data JSONB
);
在 table 里面,我有一列 data
,它有 JSONB
数据类型,并且包含这样的值:
{"educations": [{"major": "MAJOR-1", "minor": "MINOR-1"}, {"major": "MAJOR-2", "minor": "MINOR-2"}]}
这里是测试数据:
INSERT INTO resume VALUES('7e29d793-a4ba-4bfb-a93a-c2d34b7a5c8a', '{"educations": [{"major": "MAJOR-1", "minor": "MINOR-1"}, {"major": "MAJOR-2", "minor": "MINOR-2"}]}');
INSERT INTO resume VALUES('7e29d793-a4ba-4bfb-a93a-c2d34b7a5c8b', '{"educations": [{"major": "ANOTHER-MAJOR-1", "minor": "ANOTHER-MINOR-1"}, {"major": "ANOTHER-MAJOR-2", "minor": "ANOTHER-MINOR-2"}]}');
但现在我需要将主要值和次要值转换为数组,因此,对于第一行,我希望收到以下结果:
{"educations": [{"major": ["MAJOR-1"], "minor": ["MINOR-1"]}, {"major": ["MAJOR-2"], "minor": ["MINOR-2"]}]}
对于第二行,我希望收到此结果:
{"educations": [{"major": ["ANOTHER-MAJOR-1"], "minor": ["ANOTHER-MINOR-1"]}, {"major": ["ANOTHER-MAJOR-2"], "minor": ["ANOTHER-MINOR-2"]}]}
现在我创建了这个查询来更新 major
:
with sub as (
select pos - 1 as elem_index, elem, resume_id
from resume, jsonb_array_elements(data -> 'educations') with ordinality arr(elem, pos)
)
update resume cv
set data = jsonb_set(data, array['educations', sub.elem_index::text, 'major'], ('[' || (sub.elem -> 'major')::text || ']')::jsonb, true)
from sub
where cv.resume_id = sub.resume_id
但是它只更新了所有行的数组的第一个元素,所以现在我收到这个结果:
{"educations": [{"major": ["MAJOR-1"], "minor": "MINOR-1"}, {"major": "MAJOR-2", "minor": "MINOR-2"}]}
{"educations": [{"major": ["ANOTHER-MAJOR-1"], "minor": "ANOTHER-MINOR-1"}, {"major": "ANOTHER-MAJOR-2", "minor": "ANOTHER-MINOR-2"}]}
所以我的问题是如何解决这个问题?
请帮助我:)
方案一:jsonb更新基于jsonb_set
jsonb_set()
无法对相同的 jsonb 数据进行多次更新,因此您需要基于 jsonb_set()
创建一个 aggregate
函数,该函数将迭代一组行:
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(x jsonb, p text[], z jsonb, b boolean)
( SFUNC = jsonb_set
, STYPE = jsonb
) ;
然后你可以在下面的查询中使用聚合函数jsonb_set_agg()
:
SELECT jsonb_set_agg(r.data, array['educations', (a.id - 1) :: text, b.key], to_jsonb(CASE WHEN b.value IS NULL THEN array[] :: text[] ELSE array[b.value] END), True)
FROM resume AS r
CROSS JOIN LATERAL jsonb_array_elements(r.data->'educations') WITH ORDINALITY AS a(data, id)
CROSS JOIN LATERAL jsonb_each_text(a.data) AS b
WHERE b.key = 'major' OR b.key = 'minor'
GROUP BY resume_id
最后在更新语句中:
WITH sub AS (
SELECT jsonb_set_agg(r.data, array['educations', (a.id - 1) :: text, b.key], to_jsonb(CASE WHEN b.value IS NULL THEN array[] :: text[] ELSE array[b.value] END), True)
FROM resume AS r
CROSS JOIN LATERAL jsonb_array_elements(r.data->'educations') WITH ORDINALITY AS a(data, id)
CROSS JOIN LATERAL jsonb_each_text(a.data) AS b
WHERE b.key = 'major' OR b.key = 'minor'
GROUP BY resume_id
)
UPDATE resume cv
SET data = sub.data
FROM sub
WHERE cv.resume_id = sub.resume_id
解决方案 2:分解并重建 jsonb 数据
SELECT jsonb_agg(c.data ORDER BY c.id)
FROM
( SELECT resume_id
, a.id
, jsonb_object_agg(b.key,to_jsonb(CASE WHEN b.value IS NULL THEN array[] :: text[] ELSE array[b.value] END)) AS data
FROM resume AS r
CROSS JOIN LATERAL jsonb_array_elements(r.data->'educations') WITH ORDINALITY AS a(data, id)
CROSS JOIN LATERAL jsonb_each_text(a.data) AS b
GROUP BY resume_id, a.id
) AS c
GROUP BY c.resume_id
查看dbfiddle中的测试结果。
我在 PostgreSQL 中有以下 table:
CREATE TABLE resume (
resume_id UUID PRIMARY KEY,
data JSONB
);
在 table 里面,我有一列 data
,它有 JSONB
数据类型,并且包含这样的值:
{"educations": [{"major": "MAJOR-1", "minor": "MINOR-1"}, {"major": "MAJOR-2", "minor": "MINOR-2"}]}
这里是测试数据:
INSERT INTO resume VALUES('7e29d793-a4ba-4bfb-a93a-c2d34b7a5c8a', '{"educations": [{"major": "MAJOR-1", "minor": "MINOR-1"}, {"major": "MAJOR-2", "minor": "MINOR-2"}]}');
INSERT INTO resume VALUES('7e29d793-a4ba-4bfb-a93a-c2d34b7a5c8b', '{"educations": [{"major": "ANOTHER-MAJOR-1", "minor": "ANOTHER-MINOR-1"}, {"major": "ANOTHER-MAJOR-2", "minor": "ANOTHER-MINOR-2"}]}');
但现在我需要将主要值和次要值转换为数组,因此,对于第一行,我希望收到以下结果:
{"educations": [{"major": ["MAJOR-1"], "minor": ["MINOR-1"]}, {"major": ["MAJOR-2"], "minor": ["MINOR-2"]}]}
对于第二行,我希望收到此结果:
{"educations": [{"major": ["ANOTHER-MAJOR-1"], "minor": ["ANOTHER-MINOR-1"]}, {"major": ["ANOTHER-MAJOR-2"], "minor": ["ANOTHER-MINOR-2"]}]}
现在我创建了这个查询来更新 major
:
with sub as (
select pos - 1 as elem_index, elem, resume_id
from resume, jsonb_array_elements(data -> 'educations') with ordinality arr(elem, pos)
)
update resume cv
set data = jsonb_set(data, array['educations', sub.elem_index::text, 'major'], ('[' || (sub.elem -> 'major')::text || ']')::jsonb, true)
from sub
where cv.resume_id = sub.resume_id
但是它只更新了所有行的数组的第一个元素,所以现在我收到这个结果:
{"educations": [{"major": ["MAJOR-1"], "minor": "MINOR-1"}, {"major": "MAJOR-2", "minor": "MINOR-2"}]}
{"educations": [{"major": ["ANOTHER-MAJOR-1"], "minor": "ANOTHER-MINOR-1"}, {"major": "ANOTHER-MAJOR-2", "minor": "ANOTHER-MINOR-2"}]}
所以我的问题是如何解决这个问题? 请帮助我:)
方案一:jsonb更新基于jsonb_set
jsonb_set()
无法对相同的 jsonb 数据进行多次更新,因此您需要基于 jsonb_set()
创建一个 aggregate
函数,该函数将迭代一组行:
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(x jsonb, p text[], z jsonb, b boolean)
( SFUNC = jsonb_set
, STYPE = jsonb
) ;
然后你可以在下面的查询中使用聚合函数jsonb_set_agg()
:
SELECT jsonb_set_agg(r.data, array['educations', (a.id - 1) :: text, b.key], to_jsonb(CASE WHEN b.value IS NULL THEN array[] :: text[] ELSE array[b.value] END), True)
FROM resume AS r
CROSS JOIN LATERAL jsonb_array_elements(r.data->'educations') WITH ORDINALITY AS a(data, id)
CROSS JOIN LATERAL jsonb_each_text(a.data) AS b
WHERE b.key = 'major' OR b.key = 'minor'
GROUP BY resume_id
最后在更新语句中:
WITH sub AS (
SELECT jsonb_set_agg(r.data, array['educations', (a.id - 1) :: text, b.key], to_jsonb(CASE WHEN b.value IS NULL THEN array[] :: text[] ELSE array[b.value] END), True)
FROM resume AS r
CROSS JOIN LATERAL jsonb_array_elements(r.data->'educations') WITH ORDINALITY AS a(data, id)
CROSS JOIN LATERAL jsonb_each_text(a.data) AS b
WHERE b.key = 'major' OR b.key = 'minor'
GROUP BY resume_id
)
UPDATE resume cv
SET data = sub.data
FROM sub
WHERE cv.resume_id = sub.resume_id
解决方案 2:分解并重建 jsonb 数据
SELECT jsonb_agg(c.data ORDER BY c.id)
FROM
( SELECT resume_id
, a.id
, jsonb_object_agg(b.key,to_jsonb(CASE WHEN b.value IS NULL THEN array[] :: text[] ELSE array[b.value] END)) AS data
FROM resume AS r
CROSS JOIN LATERAL jsonb_array_elements(r.data->'educations') WITH ORDINALITY AS a(data, id)
CROSS JOIN LATERAL jsonb_each_text(a.data) AS b
GROUP BY resume_id, a.id
) AS c
GROUP BY c.resume_id
查看dbfiddle中的测试结果。