144

I have a table to store information about my rabbits. It looks like this:

create table rabbits (rabbit_id bigserial primary key, info json not null);
insert into rabbits (info) values
  ('{"name":"Henry", "food":["lettuce","carrots"]}'),
  ('{"name":"Herald","food":["carrots","zucchini"]}'),
  ('{"name":"Helen", "food":["lettuce","cheese"]}');

How should I find the rabbits who like carrots? I came up with this:

select info->>'name' from rabbits where exists (
  select 1 from json_array_elements(info->'food') as food
  where food::text = '"carrots"'
);

I don't like that query. It's a mess.

As a full-time rabbit-keeper, I don't have time to change my database schema. I just want to properly feed my rabbits. Is there a more readable way to do that query?

2
  • 1
    Interesting question. I've played around with it, but then it dawned on me, I'm not sure what you mean by "better". What criteria are you judging your answers by? Readability? Efficiency? Other? – David S Nov 12 '13 at 15:55
  • @DavidS: (I updated the question.) I'd prefer readability over efficiency. I certainly don't expect anything better than a full table scan, since I'm holding the schema fixed. – Snowball Nov 13 '13 at 0:53
209

As of PostgreSQL 9.4, you can use the ? operator:

select info->>'name' from rabbits where (info->'food')::jsonb ? 'carrots';

You can even index the ? query on the "food" key if you switch to the jsonb type instead:

alter table rabbits alter info type jsonb using info::jsonb;
create index on rabbits using gin ((info->'food'));
select info->>'name' from rabbits where info->'food' ? 'carrots';

Of course, you probably don't have time for that as a full-time rabbit keeper.

Update: Here's a demonstration of the performance improvements on a table of 1,000,000 rabbits where each rabbit likes two foods and 10% of them like carrots:

d=# -- Postgres 9.3 solution
d=# explain analyze select info->>'name' from rabbits where exists (
d(# select 1 from json_array_elements(info->'food') as food
d(#   where food::text = '"carrots"'
d(# );
 Execution time: 3084.927 ms

d=# -- Postgres 9.4+ solution
d=# explain analyze select info->'name' from rabbits where (info->'food')::jsonb ? 'carrots';
 Execution time: 1255.501 ms

d=# alter table rabbits alter info type jsonb using info::jsonb;
d=# explain analyze select info->'name' from rabbits where info->'food' ? 'carrots';
 Execution time: 465.919 ms

d=# create index on rabbits using gin ((info->'food'));
d=# explain analyze select info->'name' from rabbits where info->'food' ? 'carrots';
 Execution time: 256.478 ms
6
  • how to get the rows where food array inside json is non empty , for example if we can consider , their are JSON , where food array also empty ,can you help – Bravo Sep 14 '17 at 23:54
  • 1
    @Bravo select * from rabbits where info->'food' != '[]'; – Snowball Sep 15 '17 at 5:21
  • 3
    Does anyone know how this works in case you need to select an integer instead of a string/text? – Rotareti Nov 16 '18 at 1:34
  • 4
    @Rotareti You can use the @> operator: create table t (x jsonb); insert into t (x) values ('[1,2,3]'), ('[2,3,4]'), ('[3,4,5]'); select * from t where x @> '2';. Note that '2' is a JSON number; don't be mislead by the quotes. – Snowball Nov 16 '18 at 4:26
  • @Snowball, this query select info->>'name' from rabbits where (info->'food')::jsonb ? 'carrots'; is working perfect for search word from JSON. But how I can get all the records does not contain 'carrots' word ? – Milan Apr 11 '19 at 11:10
29

You could use @> operator to do this something like

SELECT info->>'name'
FROM rabbits
WHERE info->'food' @> '"carrots"';
3
  • 1
    This is useful when the item is null as well – Lucio Nov 8 '17 at 20:33
  • 2
    Make sure you pay attention to the ' ticks surrounding "carrots"... it breaks if you leave those out, even if you're checking for an integer. (spent 3 hours trying to find an integer, having it magically work by wrapping ' ticks around the number) – skplunkerin Sep 19 '18 at 22:54
  • @skplunkerin It should be json value surrounded with ' ticks to form a string, because everything is a string for SQL in JSONB type. For example, boolean: 'true', string: '"example"', integer: '123'. – 1valdis Feb 25 '19 at 12:02
22

Not smarter but simpler:

select info->>'name' from rabbits WHERE info->>'food' LIKE '%"carrots"%';
13

A small variation but nothing new infact. It's really missing a feature...

select info->>'name' from rabbits 
where '"carrots"' = ANY (ARRAY(
    select * from json_array_elements(info->'food'))::text[]);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.