SQL tricks
September 6, 2022
How to calculate average value from json array (PostgreSQL)
Assume we have table stored values within json array. For example:
+========+=============================================+
| gameid | gameinfo |
+========+=============================================+
| 1 | {"scores": [ |
| | {"score": 1, "scoreType": "skill"}, |
| | {"score": 3, "scoreType": "speed"}, |
| | {"score": 2, "scoreType": "strength"} |
| | ] |
| | } |
+--------+---------------------------------------------+
| 2 | {"scores": [ |
| | {"score": 4, "scoreType": "skill"}, |
| | {"score": 4, "scoreType": "speed"}, |
| | {"score": 4, "scoreType": "strength"} |
| | ] |
| | } |
+--------+---------------------------------------------+
| 3 | {"scores": [ |
| | {"score": 1, "scoreType": "skill"}, |
| | {"score": 3, "scoreType": "speed"}, |
| | {"score": 5, "scoreType": "strength"} |
| | ] |
| | } |
+--------+---------------------------------------------+
We need to get average score for each game. We can do it in next way:
json_array_elements - convert json array into rows, so we can use lateral join and get next table
select gameID, s from g, lateral jsonb_array_elements((gameInfo->>'scores')::jsonb) s ;
+========+=======================================+
| gameid | s |
+========+=======================================+
| 1 | {"score": 1, "scoreType": "skill"} |
+--------+---------------------------------------+
| 1 | {"score": 3, "scoreType": "speed"} |
+--------+---------------------------------------+
| 1 | {"score": 2, "scoreType": "strength"} |
+--------+---------------------------------------+
| 2 | {"score": 4, "scoreType": "skill"} |
+--------+---------------------------------------+
| 2 | {"score": 4, "scoreType": "speed"} |
+--------+---------------------------------------+
| 2 | {"score": 4, "scoreType": "strength"} |
+--------+---------------------------------------+
| 3 | {"score": 1, "scoreType": "skill"} |
+--------+---------------------------------------+
| 3 | {"score": 3, "scoreType": "speed"} |
+--------+---------------------------------------+
| 3 | {"score": 5, "scoreType": "strength"} |
+--------+---------------------------------------+
Now we have plane table with gameid and scores object. After that we can calculate average score using aggregation by gameid:
select gameID, avg((s->>'score')::int) avg_score from g, lateral jsonb_array_elements((gameInfo->>'scores')::jsonb) s group by gameID ;
Result: +========+====================+ | gameid | avg_score | +========+====================+ | 3 | 3.0000000000000000 | +--------+--------------------+ | 2 | 4.0000000000000000 | +--------+--------------------+ | 1 | 2.0000000000000000 | +--------+--------------------+
Test this example on SQL editor online