1

I have JSON data in field hotel_data like this:

{ 
   "title":"foo",
   "description":[ 
      { 
         "locale":"pt",
         "content":"pt text"
      },
      { 
         "locale":"fr",
         "content":"fr text"
      }
   ]
}

I would like to select description only fr description. It is possible using Postgres and how?

I was trying use ->> but it is not working...

SELECT  
    hotel_data->'description'->>'locale' = 'fr' AS description
FROM hotel LIMIT 1;

Note: I don't want to use SELECT *...

Excepted output: {description: "fr text"}

1
  • Which Postgres version are you using? Commented Oct 20, 2019 at 5:25

2 Answers 2

1

You can use a lateral join and json_to_recordset to expand the json array as a set of records. Then, you can filter on column locale in the generated records, and finally recompose a new json object with your expected result:

select json_build_object('description', d.content) hotel_data_descr_fr
from 
    mytable,
    json_to_recordset(hotel_data->'description') as d("locale" text, "content" text)
where d.locale = 'fr'

Demo on DB Fiddle:

with mytable as (
    select '{ 
   "title":"foo",
   "description":[ 
      { 
         "locale":"pt",
         "content":"pt text"
      },
      { 
         "locale":"fr",
         "content":"fr text"
      }
   ]
}'::json hotel_data
)
select json_build_object('description', d.content) hotel_data_descr_fr
from 
    mytable,
    json_to_recordset(hotel_data->'description') as d("locale" text, "content" text)
where d.locale = 'fr'
| hotel_data_descr_fr        |
| :------------------------- |
| {"description": "fr text"} |
Sign up to request clarification or add additional context in comments.

3 Comments

Sound good like correct working solution, but seems to be suicide when I need to filter more values like "description", "headline", "body", ...
@jimodefax: well, you did not state that it had to be generic... But this probably doable with json_each and the same overall logic.
@jimodefax You mean you've got multiple fields in that format of by-locale json arrays? You can JOIN them by their locale fields then, or even do it generically for all fields in your object with json_each like GMB suggested. But please ask a separate question about that
1

The filtering can be done using the @> operator which can use a GIN index on the hotel_data column. This is typically faster than expanding the array.

select ...
from hotel
where hotel_data @> '{"description": [{"locale":"fr"}] }';

This can also be extended to include more properties:

select ...
from hotel
where hotel_data @> '{"description": [{"locale":"fr", "headline": "nice view'}] }';

But you can only express equality conditions on the key/value pairs with that. Using LIKE is not possible. You will have to expand the array if you want to do that and apply the condition in the WHERE clause - see GMB's answer.

To extract that description, I would use a scalar sub-query:

select (select jsonb_build_object('description', t.descr ->> 'content')
        from jsonb_array_elements(h.hotel_data -> 'description') as t(descr)
        where t.descr ->> 'locale' = 'fr'
        limit 1)
from hotel h
where h.hotel_data @> '{"description": [{"locale":"fr"}] }';

That way you don't need to expand the array for filtering which I expect to be faster if only a few hotels qualify for that condition. But it has the drawback that you need to repeat the condition on the locale in the sub-select.

The limit 1 is only a safety net in case you have more than one french description. If you never have that, it doesn't hurt either


With Postgres 12 this is easier:

select jsonb_build_object(
         'description', 
         jsonb_path_query_first(hotel_data, '$.description ? (@.locale == "fr")') -> 'content'
       )
from hotel
where hotel_data @> '{"description": [{"locale":"fr"}] }'

All of the above assumes hotel_data is a jsonb column, if it's not (which it should be) you need to cast it: hotel_data::jsonb

2 Comments

Could you not even put another .content in the JSON path instead of using ->?
Yes, that would also be possible jsonb_path_query_first(hotel_data, '$.description ? (@.locale == "fr").content')

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.