3

I have two tables. Both have a jsonb column with an array of objects, each containing the key student_id.

I am trying to build one aggregated JSON value from these jsonb columns.

Lets say table A has four rows and each has jsonb data like:

[
 {
 "student_id: 1,
 "others element from table A" ...
 ...
 },
 {
 "student_id: 2,
 "others element from table A" ...
 ...
 }
...
]

And table B has holds jsonb values of the form:

[
 {
 "student_id": 1,
 "others element from table B" ...
 },
 {
 "student_id": 2,
 "others element from table B" ...
 }
...
]

There is only one row from table B at a time that is to be mapped to multiple rows from table A, all containing different "other_elements" but with the same "student_id" value for each object.

My expect result is of the form:

[
 {
 "student_id": 1,
 "others elements from table A" ...
 "others elements from table B" ...
 },
 {
 "student_id": 2,
 "others elements from table A" ...
 "others elements from table B" ...
 }
]

Here is a fiddle with sample data demonstrating my case.

Erwin Brandstetter
186k28 gold badges463 silver badges636 bronze badges
asked Oct 16, 2019 at 11:17

1 Answer 1

2

This should do it:

SELECT jsonb_agg(student) AS students
FROM (
 SELECT jsonb_object_agg((kv).key, (kv).value) AS student
 FROM (
 SELECT ass->>'student_id' AS student_id, "char" 'a' AS tbl, id, jsonb_each(ass) AS kv
 FROM (SELECT jsonb_array_elements(assessment ) AS ass, id FROM assessment) a
 UNION ALL
 SELECT ass->>'student_id' , 'b' , id, jsonb_each(ass)
 FROM (SELECT jsonb_array_elements(others_assessment) AS ass, id FROM result ) r
 ORDER BY student_id, tbl, id
 ) sub1
 GROUP BY student_id
-- ORDER BY student_id -- to be explicit
 ) sub2
;

db<>fiddle here

In the inner subquery sub1, for each involved table, unnest the top-level JSON array into its element objects with jsonb_array_elements(), and then expand these objects into into a set of key/value pairs. Then UNION ALL all of them together and (optionally) order rows, as the order bears significance in the next step.
Note how jsonb_each() is called once, returning two fields, which we carry over as one composite type kv - with well-known fields key and value defined by the function.

In the next subquery sub2, aggregate per student_id to unite all attributes with jsonb_object_agg(). Values from later rows overrule earlier rows in case of key conflicts.
Fields of the composite type are accessed with (kv).key, (kv).value. This way, the function is only executed once (not once per field). Alternatively use LATERAL subqueries ... See:

In the outer SELECT, aggregate all students into a jsonb array with jsonb_agg(). Voilá. (ORDER BY student_id is carried over from the inner subselect, but add it if you want to be explicit.

answered Oct 16, 2019 at 23:05

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.