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