I have a JSONB Object:
{"name": "Foo", "interfaces": [{"name": "Bar", "status": "up"}]}
It is stored in a jsonb column of a table
create table device ( device_name character varying not null, device_data jsonb not null );
So i was trying to get a count by name of devices which have interfaces that are not ‘up’. Group By is used for developing counts by naame, but i am having issues querying the json list for values.
MY first Attempt was:
select device_name, count(*) from device where device_json -> 'interfaces' -> 'status' != 'up' group by device_name;
Some surrounding data that made me think something was going to be difficult was:
select count(device_data -> 'interfaces') from device;
which I thought that was going to get me a count of all interfaces from all devices, but that is not correct. It seems like it is just returning the count from the first item.
Im thinking I might need to do a sub query or join of inner content.
Ive been thinking it over and when looking up psql it seems like i havent found a way to query a list type in a jsonb object. Maybe im mistaken. I didnt want to build a business layer on top of this as I figured that the DBMS would be able to handle this heavy lifting.
I saw there is a function jsonb_array_elements_text(device_data -> ‘interfaces’)::jsonb -> ‘status’ which would return the information, but I cant do any sort of count in it, as count(jsonb_array_elements_text(device_data -> 'interfaces')::jsonb -> 'status')
will return ERROR: set-valued function called in context that cannot accept a set
Advertisement
Answer
You need a lateral join to unnest the array and count the elements that are down (or not up)
select d.device_name, t.num_down from device d cross join lateral ( select count(*) num_down from jsonb_array_elements(d.device_data -> 'interfaces') as x(i) where i ->> 'status' = 'down' ) t
To count all interfaces and the down interfaces, you can use filtered aggregation:
select d.device_name, t.* from device d cross join lateral ( select count(*) as all_interfaces, count(*) filter (where i ->> 'status' = 'down') as down_interfaces from jsonb_array_elements(d.device_data -> 'interfaces') as x(i) ) t