9

I have an orders table where I store the summary of an order in a jsonb column

 {"users": [
   {"food": [{"name": "dinner", "price": "100"}], "room": "2", "user": "bob"}, 
   {"room": "3", "user": "foo"}
 ]}

Now I want to query all users with their food->name.

I tried the following, but that gives me also user foo, that has no food.

select 
  jsonb_array_elements(jsonb_array_elements(summary->'users')->'food')->>'name'  as food, 
  jsonb_array_elements(summary->'users')->>'user' as user_name 
from orders;

 food  | user_name 
 -------+-----------
 dinner | bob
 dinner | foo

How would I do such a query?


UPDATE

I have also a summery like this with two food options

{"users": [
  {"food": [{"name": "dinner", "price": "100"}, {"name": "breakfast", "price": "100"}], "room": "2", "user": "bob"}, 
  {"room": "3", "user": "foo"} 
]}

and than I get:

   food    | user_name 
-----------+-----------
 dinner    | bob
 breakfast | foo

ideally I want to get

   food               | user_name 
----------------------+-----------
 dinner, breakfast    | bob

1 Answer 1

10

All right, if you do

SELECT jsonb_array_elements(summary->'users') as users FROM orders;

you get

┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                      users                                                       │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ {"food": [{"name": "dinner", "price": "100"}, {"name": "breakfast", "price": "50"}], "room": "2", "user": "bob"} │
│ {"room": "3", "user": "foo"}                                                                                     │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Let's put this select inside another one, selecting what we need:

SELECT users->'user' as user_name, users->'food'->0->'name' as food FROM (
    SELECT jsonb_array_elements(summary->'users') as users FROM orders
) as s;

┌───────────┬──────────┐
│ user_name │   food   │
├───────────┼──────────┤
│ "bob"     │ "dinner" │
│ "foo"     │ (null)   │
└───────────┴──────────┘

We're close. We just need to add a WHERE.

SELECT users->'user' as user_name, users->'food'->0->'name' as food FROM (
    SELECT jsonb_array_elements(summary->'users') as users FROM orders
) as s WHERE (users->'food') is not null;

Resulting in

┌───────────┬──────────┐
│ user_name │   food   │
├───────────┼──────────┤
│ "bob"     │ "dinner" │
└───────────┴──────────┘

If you have more data in your food array like

'{"users": [{"food": [{"name": "dinner", "price": "100"}, {"name" : "breakfast", "price" : "50"}], "room": "2", "user": "bob"}, {"room": "3", "user": "foo"}]}'

You can do

SELECT users->'user' as user_name, jsonb_array_elements(users->'food')->>'name' as food FROM (
    SELECT jsonb_array_elements(summary->'users') as users FROM orders
) as s WHERE (users->'food') is not null;

And

┌───────────┬───────────┐
│ user_name │   food    │
├───────────┼───────────┤
│ "bob"     │ dinner    │
│ "bob"     │ breakfast │
└───────────┴───────────┘

Rewriting the above query to use Common Table Expressions

WITH users_data AS (
    SELECT jsonb_array_elements(summary->'users') as users FROM orders
), user_food AS (
    SELECT users->'user' as user_name, jsonb_array_elements(users->'food')->>'name' as food 
    FROM users_data
    WHERE (users->'food') is not null  
) SELECT * FROM user_food;

Now we just need to group by user_name

WITH users_data AS (
    SELECT jsonb_array_elements(summary->'users') as users FROM orders
), user_food AS (
    SELECT users->'user' as user_name, jsonb_array_elements(users->'food')->>'name' as food 
    FROM users_data
    WHERE (users->'food') is not null  
) SELECT user_name, array_agg(food) foods FROM user_food GROUP BY user_name;

Final result

┌───────────┬────────────────────┐
│ user_name │       foods        │
├───────────┼────────────────────┤
│ "bob"     │ {dinner,breakfast} │
└───────────┴────────────────────┘

That's the best I could come up with. Let me know if you find a better way.

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

1 Comment

thats great!, what if food has some more elements like "food": [{"name": "test1", "price": "100"}, {"name": "breakfast", "price": "10"}]

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.