22

I am considering switching to PostgreSQL, because of the JSON support. However, I am wondering, if the following would be possible with a single query:

Let's say there are two tables:

Table 1) organisations:

  ID (INT)  |  members (JSONB)                                        |
------------+---------------------------------------------------------|
     1      | [{ id: 23, role: "admin" }, { id: 24, role: "default" }]|
     2      | [{ id: 23, role: "user" }]

Table 2) users:

  ID (INT)  | name TEXT | email TEXT    |
------------+-----------+---------------|
     23     | Max       | [email protected] |
     24     | Joe       | [email protected] |

Now I want to get a result like this (all i have is the ID of the organisation [1]):

  ID (INT)  |  members (JSONB)                                       |
------------+--------------------------------------------------------|
     1      | [{ id: 23, name: "Max", email: "[email protected]", role: 
                "admin" },
               { id: 24, name: "Joe", email: "[email protected] ", role: 
                "default" }]
(1 row)

I know this is not what JSONB is intended for and that there is a better solution for storing this data in SQL, but I am just curious if it would be possible.

Thanks!

2 Answers 2

27

Yes it is possible to meet this requirement with Postgres. Here is a solution for 9.6 or higher.

SELECT o.id, JSON_AGG(
    JSON_BUILD_OBJECT(
        'id'    , u.id, 
        'name'  , u.name, 
        'email' , u.email, 
        'role'  , e.usr->'role'
    )
)
FROM organisations o
CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS(o.data) AS e(usr)
INNER JOIN users u ON (e.usr->'id')::text::int = u.id
GROUP BY o.id

See this db fiddle.

Explanation :

  • the JSONB_ARRAY_ELEMENTS function splits the organisation json array into rows (one per user) ; it is usually used in combination with JOIN LATERAL

  • to join the users table, we access the content of the id field using the -> operator

  • for each user, the JSONB_BUILD_OBJECT is used to create a new object, by passing a list of values/keys pairs ; most values comes from the users table, excepted the role, that is taken from the organisation json element

  • the query aggregates by organisation id, using JSONB_AGG to generate a json array by combining above objects

For more information, you may also have a look at Postgres JSON Functions documentation.

Sign up to request clarification or add additional context in comments.

1 Comment

Is INNER JOIN LATERAL JSONB_ARRAY_ELEMENTS(o.data) AS e(usr) ON TRUE equivalent to CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS(o.data) AS e(usr)?
2

There might be more ways to do that. One way would use jsonb_to_recordset() to transform the JSON into a record set you can join. Then create a JSON from the result using jsonb_build_object() for the individual members and jsonb_agg() to aggregate them in a JSON array.

SELECT jsonb_agg(jsonb_build_object('id', "u"."id",
                                    'name', "u"."name",
                                    'email', "u"."email",
                                    'role', "m"."role"))
       FROM "organisations" "o"
            CROSS JOIN LATERAL jsonb_to_recordset(o."members") "m" ("id" integer,
                                                                    "role" text)
            INNER JOIN "users" "u"
                       ON "u"."id" = "m"."id";

db<>fiddle

What functions are available in detail depends on the version. But since you said you consider switching, assuming a more recent version should be fair.

Comments

Your Answer

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.