I have tables level_one_table, level_two_table, and level_three_table.
level_one_table to level_two_table: one to multiple rows
level_two_table to level_three_table: one to one row
Goal: select them all and level_two_table rows able to use LIMIT and ORDER BY. Return data structure like below:
{
"level_one_table": {
"id": ..,
"... all other field in level_one_table"
"level_two_table": [{
"id": ..,
"... all other field in level_two_table",
"level_three_table": {
"id": ..,
"... all other field in level_three_table"
}
}]
}
}
level_one_table:
| "id" | ... |
|---|---|
| 1 | ... |
| 2 | ... |
level_two_table:
| "id" | "fk_level_one_id" | ... |
|---|---|---|
| 1 | 1 | ... |
| 2 | 1 | ... |
level_three_table:
| "id" | "fk_level_two_id" | ... |
|---|---|---|
| 1 | 1 | ... |
| 2 | 2 | ... |
SELECT
json_build_object(
'level_one_table', json_build_object(
'id', t0.id,
'level_two_table', json_arrayagg(json_build_object(
'id', t1.id,
// ... other t1 columns
// ORDER BY t1.column DESC LIMIT 5
'level_three_table', json_build_object(
'id', t2.id
// ... other t2 columns
)
))
)
)
FROM level_one_table t0
LEFT JOIN level_two_table t1 ON t0.id = t1.fk_level_one_id
LEFT JOIN level_three_table t2 ON t1.id = t2.fk_level_two_id
GROUP BY t0.id
// ORDER BY t0.column ... DESC LIMIT 10
Is there a way to add LIMIT or ORDER BY inside json_arrayagg()?
Or any other solution get the same result?
Do the aggregation in a
LATERALsubquery. Then you can add plainORDER BY&LIMITbefore you aggregate:fiddle
Notably, I synced table aliases with table names (table names 1-based, but aliases 0-based wasn't helpful).
Here,
json_agg()can replace the more sophisticatedjson_arrayagg()(added in Postgres 16) to aggregate the JSON array.Result:
You get a dummy entry
"level_two_table": nullif there are no related rows. Strip all null values withjson_strip_nulls(), or useCASEor similar to strip electively.Related:
For a dynamic number of nested levels: