Skip to content
Advertisement

Expand a postgres array of JSON within a single column into multiple records

I have a table called goals where each row contains 1 goal. The data about specific metrics for the goal is stored in ARRAY of JSON called goal_metrics. Sometimes there is 1 metric, sometimes there are 0 metrics, other times there are multiple metrics. I want to extract all of the goal metrics into a new table, with 1 line per metrics and the original goal_id that metric belongs to.

This is what table1 looks like currently:

+---------+------------+----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| goal_id | goal_owner | goal_create_at |                                                                                                                                                                                                             goal_metrics                                                                                                                                                                                                             |
+---------+------------+----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|   69660 |     148191 |     1566576355 | [{'metric_owner': '148191', 'metric_create_at': '1566576374', 'metric_target': '10'}, {'metric_owner': '148191', 'metric_create_at': '1566576403', 'metric_target': '1'}, {'metric_owner': '148191', 'metric_create_at': '1566576428', 'metric_target': '3'}, {'metric_owner': '148191', 'metric_create_at': '1566576450', 'metric_target': '3'}]                                                                                    |
|   68443 |     146270 |     1565911160 | [{'metric_owner': '144534', 'metric_create_at': '1565911175', 'metric_target': '1'}, {'metric_owner': '144352', 'metric_create_at': '1565911191', 'metric_target': '1'}, {'metric_owner': '144352', 'metric_create_at': '1565911212', 'metric_target': '15'}, {'metric_owner': '146270', 'metric_create_at': '1565911353', 'metric_target': '23'}]                                                                                   |
|   68440 |     146270 |     1565910356 | [{'metric_owner': '144061', 'metric_create_at': '1565910380', 'metric_target': '3'}, {'metric_owner': '144061', 'metric_create_at': '1565910462', 'metric_target': '0'}, {'metric_owner': '144534', 'metric_create_at': '1565910523', 'metric_target': '1'}, {'metric_owner': '143866', 'metric_create_at': '1565911422', 'metric_target': '6'}]                                                                                     |
|   68442 |     146270 |     1565910746 | [{'metric_owner': '143866', 'metric_create_at': '1565910863', 'metric_target': '9'}, {'metric_owner': '143866', 'metric_create_at': '1565910881', 'metric_target': '1'}, {'metric_owner': '144534', 'metric_create_at': '1565910905', 'metric_target': '1'}, {'metric_owner': '146270', 'metric_create_at': '1565910927', 'metric_target': '1'}, {'metric_owner': '144534', 'metric_create_at': '1565910956', 'metric_target': '1'}] |
+---------+------------+----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

And this is what I’m trying to get to:

+--------------+------------------+---------------+---------+
| metric_owner | metric_create_at | metric_target | goal_id |
+--------------+------------------+---------------+---------+
|       143321 |       1557774894 |            56 |   53513 |
|       143321 |       1557774933 |           100 |   53513 |
|       143321 |       1557774983 |            10 |   53513 |
|       143321 |       1557775102 |           100 |   53513 |
|       143321 |       1557775388 |           100 |   53513 |
|       148191 |       1566576374 |            10 |   69660 |
|       148191 |       1566576403 |             1 |   69660 |
|       148191 |       1566576428 |             3 |   69660 |
|       148191 |       1566576450 |             3 |   69660 |
|       144534 |       1565911175 |             1 |   68443 |
|       144352 |       1565911191 |             1 |   68443 |
|       144352 |       1565911212 |            15 |   68443 |
|       146270 |       1565911353 |            23 |   68443 |
|       144061 |       1565910380 |             3 |   68440 |
|       144061 |       1565910462 |             0 |   68440 |
|       144534 |       1565910523 |             1 |   68440 |
|       143866 |       1565911422 |             6 |   68440 |
|       143866 |       1565910863 |             9 |   68442 |
|       143866 |       1565910881 |             1 |   68442 |
|       144534 |       1565910905 |             1 |   68442 |
|       146270 |       1565910927 |             1 |   68442 |
|       144534 |       1565910956 |             1 |   68442 |
+--------------+------------------+---------------+---------+

If it was just a regular JSON column, I could use SELECT goal_metric -> 'metrics_owner' AS metrics_owner, but because it’s an ARRAY of variable length, that won’t work. I think that I need to use JSON_TO_RECORDSET, and I’ve been experimenting with that, but I haven’t been able to achieve what I’m looking for.

Advertisement

Answer

There are different possible ways (Postgres JSON documentation):

demo:db<>fiddle


A: Using json_array_elements()

SELECT
    goal_id,
    (elems ->> 'metric_owner')::int AS metric_owner,
    (elems ->> 'metric_create_at')::int AS metric_create_at,
    (elems ->> 'metric_target')::int AS metric_target
FROM
    table1,
    json_array_elements(goal_metrics) as elems

This expands every array element into one row. These expanded JSON objects can be queried for their attributes. This gives out text types. If you want them to be int types, you need a cast.


B: Using json_to_recordset()

SELECT
    goal_id,
    elems.*
FROM
    table1,
    json_to_recordset(goal_metrics) 
        AS elems(metric_owner int, metric_create_at int,metric_target int)

This does the same as in (A) in one step. You just need to define the output types.


C: If you have to do this operation regularly, it might be useful to define the output type once. Then you can use it within json_populate_recordset():

CREATE TYPE metrics_type 
    AS (metric_owner int, metric_create_at int,metric_target int);

SELECT
    goal_id,
    elems.*
FROM
    table1,
    json_populate_recordset(null::metrics_type, goal_metrics) AS elems
User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement