Query jsonb column to match an array of keys - json

I have a table items which has a jsonb column data.
The data column is something like this {"name": "aaa", "age": 23, "job": "dev"}.
How do I select items that the data has only the keys name, age?.

You can use the ? and the ?& operators.
For your usecase, it will be:
SELECT * FROM table WHERE (NOT data ? 'job') AND (data ?& array ['name', 'age'])

Use the delete operator -, example:
with items (data) as (
values
('{"name": "aaa", "age": 23}'::jsonb),
('{"name": "aaa", "age": 23, "job": "dev"}'),
('{"name": "aaa", "age": 23, "gender": "f"}')
)
select *
from items
where data - 'name'- 'age' = '{}'
data
----------------------------
{"age": 23, "name": "aaa"}
(1 row)
In Postgres 10+ you can use a text array:
select *
from items
where data - array['name', 'age'] = '{}'

Related

How to update entire JSON object in JSONB postgres column except 1 field

for example I have a table:
CREATE TABLE fruit(id bigint, data jsonb);
and a row for example is:
1,
{
"type": "pinapple",
"store1": {
"first_added": "<some timestamp>",
"price": "10",
"store_id": "1",
"comments": "some comments..."
},
"store2": {
"first_added": "<some timestamp>",
"price": "11",
"store_id": "2",
"comments": "some comments..."
},
.... more stores
}
In case of update I have the fruit id and store data :
1,
"store1": {
"price": "12",
"store_id": "1",
"comments": "some comments...V2"
}
I want to update entire store object in fruit entry (for store1), except the first_added field.
Any idea how I can accomplish it via JSONB operators or functions?
Thanks
You can use
UPDATE fruit
SET data = data || jsonb_set($1::jsonb, '{store1,first_added}', data#>'{store1,first_added}')
WHERE id = 1;
(online demo)
where the parameter $1 is set to the value {"store1": {"price": "12", "store_id": "1", "comments": "some comments...V2"}}.
Or if you need the key to be dynamic, use
UPDATE fruit
SET data = jsonb_set(data, ARRAY[$2::text], jsonb_set($1::jsonb, '{first_added}', data->$2->'first_added'))
WHERE id = 1;
(online demo)
You can use the jsonb_set function to change the desired element, then use the jsonb_build_object function to create a new dataset, then concatenate the data with the || operator to keep the rest of the data(first_added,...)
update table1
set data = jsonb_set(data, '{store1}', jsonb_build_object('first_added', data->'store1'->'first_added', 'price', 12, 'store_id', 1, 'comments', 'some comments...V2'))
where id = 1;
Demo in DBfiddle

Using SQLite Json Functions I want to retrieve a value base on the value near it

Lets say I have this Json and I would like to retrieve all the age values where the name equals Chris in the Array key.
{
"Array": [
{
"age": "65",
"name": "Chris"
},
{
"age": "20",
"name": "Mark"
},
{
"age": "23",
"name": "Chris"
}
]
}
That Json is present in the Json column inside my database.
by that I would like to retrieve one age column the has the age 65 and 23 because they both named Chris.
Use json_each() table-valued function to extract all the names and ages from the json array of each row of the table and json_extract() function to filter the rows for 'Chris' and get his age:
SELECT json_extract(j.value, '$.name') name,
json_extract(j.value, '$.age') age
FROM tablename t JOIN json_each(t.col, "$.Array") j
WHERE json_extract(j.value, '$.name') = 'Chris';
Change col to the name of the json column.
See the demo.

How to delete multiple values in JSONB array Postgresql array object

I have a JSONB array below
[
{
"name": "test",
"age": "21",
"phone": "6589",
"town": "54"
},
{
"name": "test12",
"age": "67",
"phone": "6546",
"town": "54"
},
{
"name": "test123",
"age": "21",
"phone": "6589",
"town": "54"
},
{
"name": "test125",
"age": "67",
"phone": "6546",
"town": "54"
}
]
Now I want to delete the object if the name is test or test125. How to delete multiple or single values in JSONB array?
An update statement including a subquery, which eleminates the unwanted elements with NOT IN operator and aggregates the rest by using jsonb_agg() function, would find out this operation :
Choose this :
1. UPDATE tab
SET jsdata = t.js_new
FROM
(
SELECT jsonb_agg( (jsdata ->> ( idx-1 )::int)::jsonb ) AS js_new
FROM tab
CROSS JOIN jsonb_array_elements(jsdata)
WITH ORDINALITY arr(j,idx)
WHERE j->>'name' NOT IN ('test','test125')
) t
or this one :
2. WITH t AS (
SELECT jsonb_agg( (jsdata ->> ( idx-1 )::int)::jsonb ) AS js_new
FROM tab
CROSS JOIN jsonb_array_elements(jsdata)
WITH ORDINALITY arr(j,idx)
WHERE j->>'name' NOT IN ('test','test125')
)
UPDATE tab
SET jsdata = js_new
FROM t
Demo
If you have the Postgres 12, you can use jsonb_path_query_array function to filter the jsonb here is the sample for your question:
with t (j) as ( values ('[
{"name":"test","age":"21","phone":"6589","town":"54"},
{"name":"test12","age":"67","phone":"6546","town":"54"},
{"name":"test123","age":"21","phone":"6589","town":"54"},
{"name":"test125","age":"67","phone":"6546","town":"54"}
]'::jsonb) )
select jsonb_path_query_array(j,
'$[*] ? (#.name != "test" && #.name != "test125")')
from t;
more info on https://www.postgresql.org/docs/12/functions-json.html
I would create a function that does that:
create function remove_array_elements(p_data jsonb, p_key text, p_value text[])
returns jsonb
as
$$
select jsonb_agg(e order by idx)
from jsonb_array_elements(p_data) with ordinality as t(e,idx)
where t.e ->> p_key <> ALL (p_value) ;
$$
language sql
immutable;
Then you can use it like this:
update the_table
set the_column = remove_array_elements(the_column, 'name', array['test', 'test125'])
where id = ...;
Online example

Find object by key/value in an array in postgresql jsonb column

I have the following table:
CREATE TABLE api_data (
id bigserial NOT NULL PRIMARY KEY,
content JSONB NOT NULL
);
Now I insert an array like this into the content column:
[{ "id": 44, "name": "address One", "petId": 1234 },
{ "id": 45, "name": "address One", "petId": 1234 },
{ "id": 46, "name": "address One", "petId": 1111 }]
What I want next is to get exactly the objects that have the "petId" set to a given value.
I figured I could do
select val
from api_data
WHERE content #> '[{"petId":1234}]'
But that returns the whole array.
Another thing I found is this query:
select val
from api_data
JOIN LATERAL jsonb_array_elements(content) obj(val) ON obj.val->>'petId' = '1234'
WHERE content #> '[{"petId":1234}]'
Which returns the object I am looking for, but three times which matches the number of elements in the array.
What I actually need is a result like this:
[{ "id": 44, "name": "address One", "petId": 1234 },
{ "id": 45, "name": "address One", "petId": 1234 }]
If you are using Postgres 12, you can use a JSON path expression:
select jsonb_path_query_array(content, '$[*] ? (#.petId == 1234)') as content
from api_data
where content #> '[{"petId":1234}]';
If you are using an older version, you need to unnest and aggregate manually:
select (select jsonb_agg(e)
from jsonb_array_elements(d.content) as t(e)
where t.e #> '{"petId":1234}') as content
from api_data d
where d.content #> '[{"petId":1234}]'

Manipulating the JSON column and split the values

I have the following text in one of my Postgres table as TEXT datatype:
[
{"type": "text", "values": ["General"], "valueType": "string", "fieldType": "text", "value": ["General"], "customFieldId": "ee", "name": "customer_group"},
{"type": "text", "values": ["Vienna"], "valueType": "string", "fieldType": "text", "value": ["Vienna"], "customFieldId": "eU", "name": "customer_city"},
{"type": "text", "values": ["Mario"], "valueType": "string", "fieldType": "text", "value": ["Mario"], "customFieldId": "eZ", "name": "first_name"},
{"type": "text", "values": ["2019-06-30"], "valueType": "date", "fieldType": "text", "value": ["2019-06-30"], "customFieldId": "ea", "name": "created_at_date"}
]
I need to split the values of this TEXT field to columns and rows. For that I have converted the TEXT column to JSON as below:
SELECT CAST( "customFieldValues" as JSON) "customFieldValues" FROM fr.contacts
But when I tried to manipulate this JSON value I'm getting NULL as result.
WITH CTE AS(SELECT CAST( "customFieldValues" as JSON) "customFieldValues" FROM fr.contacts
)
SELECT
"customFieldValues" ->>'customer_city' as dd
FROM CTE
Does anyone have any suggestions on this? How to get the column names and it's values in rows. I want to create a TABLE based on this data.
Any suggestions would be of great help.
below is the expected result,
customer_group customer_city first_name created_at_date
General Vienna Mario 2019-06-30
Disclaimer: It is still not clear:
Why is there one element values and one value? What is the difference?
Why are these elements arrays?
step-by-step demo:db<>fiddle
SELECT
MAX(value) FILTER (WHERE column_name = 'customer_group') AS customer_group,
MAX(value) FILTER (WHERE column_name = 'customer_city') AS customer_city,
MAX(value) FILTER (WHERE column_name = 'first_name') AS first_name,
MAX(value) FILTER (WHERE column_name = 'created_at_date') AS created_at_date
FROM (
SELECT
elems ->> 'name' AS column_name,
elems -> 'value' ->> 0 AS value,
data
FROM
mytable,
json_array_elements(data::json) elems
) s
GROUP BY data
Cast text to json with ::json
Expand the JSON array: One row for each element with json_array_elements()
Getting the value: -> 'value' gets the array, ->> 0 gets the text representation of the first array element (the only one here)
Getting the column: ->> 'name' gets the text representation of the column name
Classical pivot algorithm (turning rows to columns) with the FILTER clause.