Как корректно распарсить с разной структурой JSON строки в одну колонку (PostgreSQL)?

Рейтинг: -1Ответов: 1Опубликовано: 28.02.2025

Есть таблица, где в колонке pars_col лежит разная структура json строк. Один тип строки: {'values': [{'test': 'A', 'values': [{'value': 8, 'test': 'B'}]}]}, второй тип строки: {'values': [{'value': 21.0, 'test': 'D'}]}. В одной строке может быть несколько value с разными значениями и с разными наименованиями в test. Мне нужно в каждой строке вытащить все value и просуммировать их. Всё это нужно сохранить в одной колонке.

У кода было несколько ошибок, спасибо @ValNik, благодаря его комментарию стало намного лучше. Осталась одна проблема, мой код из строк с двойным вложением массива не вытаскивает все значения.

Например, в таблице вложена строка: {"values": [{"test": "A", "values": [{"value": 2, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"}]}]}'::TEXT), код из нее вытащит только 2, а надо чтобы вытащил 2 и 3 и сложил их.

Мой код:

-- временная таблица для примера 
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),
            (2, 'Product B', '2023-02-01'::DATE, '{"values": [{"value": 21.0, "test": "D"}]}'::TEXT),
            (23, 'Product C', '2023-01-01'::DATE, '{"values": [{"test": "A", "values": [{"value": 5, "test": "B"}]}, {"value": 10, "test": "D"}]}'::TEXT),
            (245, 'Product D', '2023-02-01'::DATE, '{"values": [{"test": "A", "values": [{"value": 2, "test": "B"}]}, {"test": "D", "values": [{"value": 3, "test": "E"}]}]}'::TEXT),
            (32, 'Product E', '2023-01-01'::DATE, '{"values": [{"value": 15, "test": "F"}]}'::TEXT),
            (31, 'Product G', '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,
    sum(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
GROUP BY prg_id, name, dt

На выходе получается: результат запроса

В таблице видно, что prg_id 245 не полностью вытащил value. Пожалуйста, помогите исправить ошибку.

Ответы

▲ 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"}]}]}]}

fiddle

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"}]}
она вовсе не попадет в выходной результат.