Как корректно распарсить с разной структурой JSON строки в одну колонку (PostgreSQL)?
Есть таблица, где в колонке pars_col лежит разная структура json строк. Один тип строки: {'values': [{'test': 'A', 'values': [{'value': 8, 'test': 'B'}]}]}, второй тип строки: {'values': [{'value': 21.0, 'test': 'D'}]}. В одной строке может быть несколько value с разными значениями и с разными наименованиями в test. Мне нужно в каждой строке вытащить все value и просуммировать их. Всё это нужно сохранить в одной колонке. У меня получается выполнить задачу, если я сохраняю в разные колонки. Если всё помещаю в одну колонку, то происходят проблемы, появляются неявные дубликаты и неверный подсчет суммы.
Код для примера:
-- создание таблицы для примера
CREATE TEMP VIEW df AS
SELECT
prg_id,
name,
dt,
pars_col::json as pars_col
FROM
(
SELECT
prg_id,
name,
dt,
pars_col
FROM
(VALUES
(1, 'Product A', '2023-01-01'::DATE, '{"values": [{"test": "A", "values": [{"value": 8, "test": "B"}]}]}'::TEXT),
(1, 'Product A', '2023-02-01'::DATE, '{"values": [{"value": 21.0, "test": "D"}]}'::TEXT),
(2, 'Product B', '2023-01-01'::DATE, '{"values": [{"test": "A", "values": [{"value": 5, "test": "B"}]}, {"value": 10, "test": "D"}]}'::TEXT),
(2, 'Product B', '2023-02-01'::DATE, '{"values": [{"test": "A", "values": [{"value": 2, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"}]}]}'::TEXT),
(3, 'Product C', '2023-01-01'::DATE, '{"values": [{"value": 15, "test": "F"}]}'::TEXT),
(3, 'Product C', '2023-02-01'::DATE, '{"values": [{"test": "G", "values": [{"value": 7, "test": "H"}]}]}'::TEXT)
) AS data(prg_id, name, dt, pars_col)
) as sub;
-- запрос, с помощью которого я пытаюсь распарсить строки в колонке pars_col
SELECT
prg_id,
name,
dt,
combined.value
FROM df
CROSS JOIN LATERAL (
SELECT
SUM((elem ->> 'value')::numeric) AS value
FROM
pg_catalog.json_array_elements(pars_col -> 'values') AS elem
UNION
SELECT
SUM((elem2 ->> 'value')::numeric) AS value
FROM
pg_catalog.json_array_elements((pars_col -> 'values') -> 0 -> 'values') AS elem2
) AS combined;
Получится вот такой результат:
Хочу чтобы не появлялись вот такие неявные дубли:
И вот такая ошибка появилась на временной маленькой таблице:
По факту это должна была бы быть одной строкой с суммой - 15.
Пожалуйста, помогите изменить код, чтобы устранить данные ошибки.
Ответы (1 шт):
Поскольку вложенность массивов у вас не определена, нужно разложить на элементы рекурсивным запросом.
Например для prg_id=402 уровень вложенности 3 и уровни рекурсии будут 0,1,2.
На каждом шаге мы разбиваем массив(elem) на элементы, если он является массивом. Если elem не массив, рекурсия останавливается.
Является ли строка конечным элементом - проверяем
where elem::jsonb ? 'value'
и далее берем только эти элементы.
Далее извлекаем значение из elem cast(elem ->>'value' as numeric) и суммируем.
Посмотрите пример.
| prg_id | name | dt | pars_col |
|---|---|---|---|
| 1 | Product A | 2023-01-01 | {"values": [{"test": "A", "values": [{"value": 8, "test": "B"}]}]} |
| 2 | Product B | 2023-02-01 | {"values": [{"value": 21.0, "test": "D"}]} |
| 23 | Product C | 2023-01-01 | {"values": [{"test": "A", "values": [{"value": 5, "test": "B"}]}, {"value": 10, "test": "D"}]} |
| 24 | Product D | 2023-02-01 | {"values": [{"test": "A", "values": [{"value": 2, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"}]}]} |
| 32 | Product E | 2023-01-01 | {"values": [{"value": 15, "test": "F"}]} |
| 31 | Product G | 2023-02-01 | {"values": [{"test": "G", "values": [{"value": 7, "test": "H"}]}]} |
| 101 | Product G | 2023-02-01 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"}]}]} |
| 401 | Product F | 2023-03-03 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"}]}]} |
| 402 | Product F | 2023-03-03 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]}]} |
with recursive r as(
select 0 lvl,prg_id,name,dt,el1.value elem,position
,pars_col
from df
cross join json_array_elements(pars_col -> 'values') with ordinality AS el1(value,position)
union all
select lvl+1 lvl,prg_id,name,dt,el1.value elem,el1.position
,pars_col
from r
cross join json_array_elements(r.elem -> 'values') with ordinality AS el1(value,position)
)
select prg_id,name,dt
,sum(cast(elem ->>'value' as numeric)) as total
-- ,elem ,elem ->>'value' elem_value,lvl,pars_col
from r
where elem::jsonb ? 'value'
group by prg_id,name,dt
order by prg_id,name,dt
| prg_id | name | dt | total |
|---|---|---|---|
| 1 | Product A | 2023-01-01 | 8 |
| 2 | Product B | 2023-02-01 | 21.0 |
| 23 | Product C | 2023-01-01 | 15 |
| 24 | Product D | 2023-02-01 | 5 |
| 31 | Product G | 2023-02-01 | 7 |
| 32 | Product E | 2023-01-01 | 15 |
| 101 | Product G | 2023-02-01 | 12 |
| 401 | Product F | 2023-03-03 | 12 |
| 402 | Product F | 2023-03-03 | 29 |
Для примера, результат запроса до группировки (показан частично)
| lvl | prg_id | name | dt | elem | position | pars_col |
|---|---|---|---|---|---|---|
| 0 | 1 | Product A | 2023-01-01 | {"test": "A", "values": [{"value": 8, "test": "B"}]} | 1 | {"values": [{"test": "A", "values": [{"value": 8, "test": "B"}]}]} |
| 1 | 1 | Product A | 2023-01-01 | {"value": 8, "test": "B"} | 1 | {"values": [{"test": "A", "values": [{"value": 8, "test": "B"}]}]} |
| 0 | 402 | Product F | 2023-03-03 | {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]} | 2 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]}]} |
| 0 | 402 | Product F | 2023-03-03 | {"test": "A", "values": [{"value": 9, "test": "B"}]} | 1 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]}]} |
| 1 | 402 | Product F | 2023-03-03 | {"value": 9, "test": "B"} | 1 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]}]} |
| 1 | 402 | Product F | 2023-03-03 | {"value": 3, "test": "E"} | 1 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]}]} |
| 1 | 402 | Product F | 2023-03-03 | {"value": 14, "test": "F"} | 2 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]}]} |
| 1 | 402 | Product F | 2023-03-03 | {"values": [{"value": 3, "test": "E"}]} | 3 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]}]} |
| 2 | 402 | Product F | 2023-03-03 | {"value": 3, "test": "E"} | 1 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]}]} |
Для примера, результат запроса до группировки, но отфильтрованы конечные элементы
| prg_id | name | dt | elem | elem_value | lvl | pars_col |
|---|---|---|---|---|---|---|
| 1 | Product A | 2023-01-01 | {"value": 8, "test": "B"} | 8 | 1 | {"values": [{"test": "A", "values": [{"value": 8, "test": "B"}]}]} |
| 2 | Product B | 2023-02-01 | {"value": 21.0, "test": "D"} | 21.0 | 0 | {"values": [{"value": 21.0, "test": "D"}]} |
| 23 | Product C | 2023-01-01 | {"value": 10, "test": "D"} | 10 | 0 | {"values": [{"test": "A", "values": [{"value": 5, "test": "B"}]}, {"value": 10, "test": "D"}]} |
| 23 | Product C | 2023-01-01 | {"value": 5, "test": "B"} | 5 | 1 | {"values": [{"test": "A", "values": [{"value": 5, "test": "B"}]}, {"value": 10, "test": "D"}]} |
| 24 | Product D | 2023-02-01 | {"value": 2, "test": "B"} | 2 | 1 | {"values": [{"test": "A", "values": [{"value": 2, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"}]}]} |
| 24 | Product D | 2023-02-01 | {"value": 3, "test": "E"} | 3 | 1 | {"values": [{"test": "A", "values": [{"value": 2, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"}]}]} |
| 31 | Product G | 2023-02-01 | {"value": 7, "test": "H"} | 7 | 1 | {"values": [{"test": "G", "values": [{"value": 7, "test": "H"}]}]} |
| 32 | Product E | 2023-01-01 | {"value": 15, "test": "F"} | 15 | 0 | {"values": [{"value": 15, "test": "F"}]} |
| 101 | Product G | 2023-02-01 | {"value": 9, "test": "B"} | 9 | 1 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"}]}]} |
| 101 | Product G | 2023-02-01 | {"value": 3, "test": "E"} | 3 | 1 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"}]}]} |
| 401 | Product F | 2023-03-03 | {"value": 9, "test": "B"} | 9 | 1 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"}]}]} |
| 401 | Product F | 2023-03-03 | {"value": 3, "test": "E"} | 3 | 1 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"}]}]} |
| 402 | Product F | 2023-03-03 | {"value": 9, "test": "B"} | 9 | 1 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]}]} |
| 402 | Product F | 2023-03-03 | {"value": 3, "test": "E"} | 3 | 1 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]}]} |
| 402 | Product F | 2023-03-03 | {"value": 14, "test": "F"} | 14 | 1 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]}]} |
| 402 | Product F | 2023-03-03 | {"value": 3, "test": "E"} | 3 | 2 | {"values": [{"test": "A", "values": [{"value": 9, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"},{"value": 14, "test": "F"},{"values": [{"value": 3, "test": "E"}]}]}]} |
P.S.
Значение with ordinality мы здесь не используем. Оставил только для интереса, реально не понадобилось.
P.P.S
Минимальный вид запроса.
with recursive r as(
select prg_id,name,dt,el1.value elem
from df
cross join json_array_elements(pars_col -> 'values') el1
union all
select prg_id,name,dt,el1.value elem
from r
cross join json_array_elements(r.elem -> 'values') el1
)
select prg_id,name,dt ,sum(cast(elem ->>'value' as numeric)) as total
from r
where elem::jsonb ? 'value'
group by prg_id,name,dt
order by prg_id,name,dt
Если разбираемая строка не имеет ни одного элемента типа
{"values": [{"value": 21.0, "test": "D"}]}
она вовсе не попадет в выходной результат.


