Querying Postgres 9.6 JSONB array of objects - postgresql

I have the following table:
CREATE TABLE trip
(
id SERIAL PRIMARY KEY ,
gps_data_json jsonb NOT NULL
);
The JSON in gps_data_json contains an array of of trip objects with the following fields (sample data below):
mode
timestamp
latitude
longitude
I'm trying to get all rows that contain a certain "mode".
SELECT * FROM trip
where gps_data_json ->> 'mode' = 'WALK';
I pretty sure I'm using the ->> operator wrong, but I'm unsure who to tell the query that the JSONB field is an array of objects?
Sample data:
INSERT INTO trip (gps_data_json) VALUES
('[
{
"latitude": 47.063480377197266,
"timestamp": 1503056880725,
"mode": "TRAIN",
"longitude": 15.450349807739258
},
{
"latitude": 47.06362533569336,
"timestamp": 1503056882725,
"mode": "WALK",
"longitude": 15.450264930725098
}
]');
INSERT INTO trip (gps_data_json) VALUES
('[
{
"latitude": 47.063480377197266,
"timestamp": 1503056880725,
"mode": "BUS",
"longitude": 15.450349807739258
},
{
"latitude": 47.06362533569336,
"timestamp": 1503056882725,
"mode": "WALK",
"longitude": 15.450264930725098
}
]');

The problem arises because ->> operator cannot walk through array:
First unnest your json array using json_array_elements function;
Then use the operator for filtering.
Following query does the trick:
WITH
A AS (
SELECT
Id
,jsonb_array_elements(gps_data_json) AS point
FROM trip
)
SELECT *
FROM A
WHERE (point->>'mode') = 'WALK';

Unnesting the array works fine, if you only want the objects containing the values queried.
The following checks for containment and returns the full JSONB:
SELECT * FROM trip
WHERE gps_data_json #> '[{"mode": "WALK"}]';
See also Postgresql query array of objects in JSONB field

select * from
(select id, jsonb_array_elements(gps_data_json) point from trip where id = 16) t
where point #> '{"mode": "WALK"}';
In My Table, id = 16 is to make sure that the specific row is jsonb-array datatype ONLY. Since other rows data is just JSONB object. So you must filter out jsonb-array data FIRST. Otherwise : ERROR: cannot extract elements from an object

Related

How to transform to regular columns a list of dictionary field in postgresql

I have a column in postgresl 14 table that is a list of dictionary with 4 keys like this:
[{"id": 14771, "stock": "alfa-12", "name": "rooftop", "store": "MI"},
{"id": 14700, "stock": "beta-10", "name": "stove", "store": "UK"}]
This list can contain dozens of dicts but they all have id, stock, name and store as keys.
Is there a way to query this field and get it as regular columns like this:
id stock name store
14771 alfa-12 rooftop MI
14700 beta-10 stove UK
Use jsonb_array_elements() to get all elements of the json array and jsonb_to_record() to convert the elements to records. Read about the functions in the documentation.
select id, name, stock, store
from my_table
cross join jsonb_array_elements(json_col)
cross join jsonb_to_record(value) as t(id int, name text, stock text, store text)
Test it in db<>fiddle.

PostgreSQL update a jsonb column multiple times

Consider the following:
create table query(id integer, query_definition jsonb);
create table query_item(path text[], id integer);
insert into query (id, query_definition)
values
(100, '{"columns":[{"type":"integer","field":"id"},{"type":"str","field":"firstname"},{"type":"str","field":"lastname"}]}'::jsonb),
(101, '{"columns":[{"type":"integer","field":"id"},{"type":"str","field":"firstname"}]}'::jsonb);
insert into query_item(path, id) values
('{columns,0,type}'::text[], 100),
('{columns,1,type}'::text[], 100),
('{columns,2,type}'::text[], 100),
('{columns,0,type}'::text[], 101),
('{columns,1,type}'::text[], 101);
I have a query table which has a jsonb column named query_definition.
The jsonb value looks like the following:
{
"columns": [
{
"type": "integer",
"field": "id"
},
{
"type": "str",
"field": "firstname"
},
{
"type": "str",
"field": "lastname"
}
]
}
In order to replace all "type": "..." with "type": "string", I've built the query_item table which contains the following data:
path |id |
----------------+---+
{columns,0,type}|100|
{columns,1,type}|100|
{columns,2,type}|100|
{columns,0,type}|101|
{columns,1,type}|101|
path matches each path from the json root to the "type" entry, id is the corresponding query's id.
I made up the following sql statement to do what I want:
update query q
set query_definition = jsonb_set(q.query_definition, query_item.path, ('"string"')::jsonb, false)
from query_item
where q.id = query_item.id
But it partially works, as it takes the 1st matching id and skips the others (the 1st and 4th line of query_item table).
I know I could build a for statement, but it requires a plpgsql context and I'd rather avoid its use.
Is there a way to do it with a single update statement?
I've read in this topic it's possible to make it with strings, but I didn't find out how to adapt this mechanism with jsonb treatment.

How to remove field from each elements json array postgres?

I have table
CREATE TABLE items (
id BIGINT PRIMARY KEY ,
data jsonb
);
Format of data
[
{
"id": "d20fe90c-137c-4713-bde1-2d4e75178ad3",
"text": "text",
"count": 1
},
{
"id": "d20fe90c-137c-4713-bde1-2d4e75178ad4",
""text": "text",
"count": 1
}
]
How I can remove field count from all elements of data json array?
I try
UPDATE items
SET data = data #- '{count}';
But this query requires index of array element before count as
UPDATE items
SET data = data #- '{0, count}';
There is no operator or built-in function to do that. Unnest the array and aggregate modified elements in the way like this:
update items t
set data = (
select jsonb_agg(elem- 'count')
from items
cross join lateral jsonb_array_elements(data) as arr(elem)
where id = t.id)
Test it in db<>fiddle.

How to use IN for search in JSON-b with index?

I have table with JSON-b field like this:
id | data
----------
1 | '{"points": [{"id": 10, "address": "Test 1"}, {"id": 20, "address": "Test 2"}, {"id": 30, "address": "Test 3"}]}'
2 | '{"points": [{"id": 40, "address": "Test 444"}, {"id": 20, "address": "Test 222"}, {"id": 50, "address": "Test 555"}]}'
The JSON-b field "data" contains "points" array.
How to get all "points" whose point id is contained in an array [40, 20]? Like classic IN:
... IN (40,20)
Query must use GIN index!!! Array IDs will be sub-query.
You could almost do it with a functional index using a jsonb_path_query_array to extract the data. But as far as I can tell, not quite.
create index on t using gin (jsonb_path_query_array(x,'$.points[*].id'));
And then query with:
select * from t where jsonb_path_query_array(x,'$.points[*].id') ?| '{20,40}';
The problem is that ?| only works with text elements, while in your data the values of 'id' are integers, not text. I thought jsonpath would provide a way to convert them to text, but if it does, I cannot find it.
So instead I think you will have to define your own function which accepts jsonb, and returns int[] or text[] (or jsonb which is an array of text conversions). Then you can build an index on the results of this function. Don't forget to declare it immutable.
You will need to unnest the array (essentially normalizing your data model "on-the-fly") then you can use a subquery to check the value:
select t.*
from the_table t
where exists (select *
from jsonb_array_elements(t.data -> 'points') as x(element)
where (x.element ->> 'id')::int in (select id
from other_table))

Update selected values in a jsonb column containing a array

Table faults contains column recacc (jsonb) which contains an array of json objects. Each of them contains a field action. If the value for action is abc, I want to change it to cba. Changes to be applied to all rows.
[
{
"action": "abc",
"created": 1128154425441
},
{
"action": "lmn",
"created": 1228154425441
},
{
"action": "xyz",
"created": 1328154425441
}
]
The following doesn't work, probably because of the data being in array format
update faults
set recacc = jsonb_set(recacc,'{action}', to_jsonb('cbe'::TEXT),false)
where recacc ->> 'action' = 'abc'
I'm not sure if this is the best option, but you may first get the elements of jsonb using jsonb_array_elements, replace it and then reconstruct the json using array_agg and array_to_json.
UPDATE faults SET recacc = new_recacc::jsonb
FROM
(SELECT array_to_json(array_agg(s)) as new_recacc
FROM
( SELECT
replace(c->>'action','abc','cba') , --this to change the value
c->>'created' FROM faults f
cross join lateral jsonb_array_elements(f.recacc) as c
) as s (action,created)
) m;
Demo