How to query on multiple attributes in the same json object array? - sql

I have a json array similar to this structure in a column of my database -
{
"id": "123abc",
"Y/N": "Y",
"Color": "Purple",
"arr": [ {
"time": 1210.55
"person": "Sean"
"action": "yes" //The values for this field can only be 'yes', 'no, 'maybe'
},
{
"time": 1230.19
"person": "Linda"
"action": "no"
} ],
}
I need to pull all the corresponding attributes based on 2 criteria of an object in the "arr" array. I want to get the latest "arr" object based on the "time" (highest value) but only pull this index if the "action" is equal to 'no' or 'yes', so exclude all the objects when "action" = "maybe".
I have tried using a WHERE statement to have a "time" range set and ORDER BY DESC to pull the latest entry and return the entire "arr". This just returns the highest value of "time" but returns all the attributes when "action" = "maybe" but I want to return the objects with only "yes" or "no".
Here is the current query I have -
SELECT jsonb_build_object('ID', t.col -> '_id',
'Yes or No', t.col -> 'Y/N',
'arr', x.elem)
FROM tbl t
CROSS JOIN LATERAL (
SELECT elem
FROM jsonb_array_elements(t.col -> 'arr') a(elem)
WHERE a.elem -> 'time' between '1110.23' and '1514.12'
AND t.col ->> 'Color' = 'Purple'
ORDER BY a.elem -> 'time' DESC NULLS LAST
LIMIT 1
) x;
The query is returning the latest object in the array with the highest time but it is also returning objects when "action" = "maybe". I have tried adding AND a.elem -> 'action' = 'yes' after the WHERE statement but receive an error saying the Token "yes" is invalid.
Is it possible to return an object with the largest "time" that has the "action" attribute equal to "yes" or "no" only?

Your code is alternatively using -> and ->>, but it isn't using them correctly.
Lots of good details here: What is the difference between `->>` and `->` in Postgres SQL?.
Everywhere in the code using -> should be ->> if you try that in the where clause with AND a.elem ->> 'action' = 'yes' it should work.

Related

Equivalent of jsonb_path_query_array in Postgresql-11

[
{
"name" : "A",
"key" : "KA"
},
{
"name" : "B",
"key" : "KB"
}
]
Given a column containing the above data,
I use select jsonb_path_query_array(column, '$.key') to get the output [KA, KB]
However this doesn't work in Postgres-11. Are there any alternatives for the same ?
Yes. This yields a Postgres array. Use jsonb_agg instead of array_agg if you need a JSON array.
select array_agg(j ->> 'key')
from jsonb_array_elements(column) t(j);
Update
select
(select array_agg(j ->> 'key') from jsonb_array_elements(column) t(j))
from the_table;

How can i get keys from each object in array (postgresql)?

I done
let statuses = await t.any(`SELECT DISTINCT status FROM mails`)
and got
"statuses": [
{
"status": "error"
},
{
"status": "success"
}
]
How can I get array with keys of objects ? ['error', 'success'] ?
Assuming status is a jsonb column (which it should be), you can do:
select distinct st.status
from mails m
cross join jsonb_array_elements(m.status -> 'statuses') as st(status)
If status is a json column you will need to use json_array_elements() instead

Postgres: How to alter jsonb value type for each element in an array?

I have a jsonb column named items in Postgres 10.12 like this:
{
"items": [
{
"itemQty": 2,
"itemName": "snake"
},
{
"itemQty": 1,
"itemName": "x kodiyum"
}
]
}
Now I want to convert itemQty type to string for every array element so that the new values are like this:
{
"items": [
{
"itemQty": "2",
"itemName": "snake"
},
{
"itemQty": "1",
"itemName": "x kodiyum"
}
]
}
How do I do this? I have gone through the documentation for Postgres jsonb and couldn't figure out.
On the server-side, I am using Spring boot and Hibernate with com.vladmihalcea.hibernate.type.json (Hibernate Types 52) if it helps.
Thanks
You could unnest the array, modify the elements, and then rebuild it. Assuming that the primary key of your table is id, that would be:
select jsonb_build_object(
'items', jsonb_agg(
jsonb_build_object(
'itemQty', (x.obj ->> 'itemQty')::text,
'itemName', x.obj ->> 'Name'
)
)
)new_items
from mytable t
cross join lateral jsonb_array_elements(t.items -> 'items') as x(obj)
group by id
Note that the explicit cast to ::text is not really needed here, as ->> extract text values anyway: I kept it because it makes the intent clearer.
If you want an update statement:
update mytable t
set items = (
select jsonb_build_object(
'items', jsonb_agg(
jsonb_build_object(
'itemQty', (x.obj ->> 'itemQty')::text,
'itemName', x.obj ->> 'Name'
)
)
)
from jsonb_array_elements(t.items -> 'items') as x(obj)
)
Demo on DB Fiddle

Postgresql search if exists in nested jsonb

I'm new with jsonb request and i got a problem. Inside an 'Items' table, I have 'id' and 'data' jsonb. Here is what can look like a data:
[
{
"paramId": 3,
"value": "dog"
},
{
"paramId": 4,
"value": "cat"
},
{
"paramId": 5,
"value": "fish"
},
{
"paramId": 6,
"value": "",
"fields": [
{
"paramId": 3,
"value": "cat"
},
{
"paramId": 4,
"value": "dog"
}
]
},
{
"paramId": 6,
"value": "",
"fields": [
{
"paramId": 5,
"value": "cat"
},
{
"paramId": 3,
"value": "dog"
}
]
}
]
The value in data is always an array with object inside but sometimes the object can have a 'fields' value with objects inside. It is maximum one level deep.
How can I select the id of the items which as for example an object containing "paramId": 3 and "value": "cat" and also have an object with "paramId": 5 and "value" LIKE '%ish%'.
I already have found a way to do that when the object is on level 0
SELECT i.*
FROM items i
JOIN LATERAL jsonb_array_elements(i.data) obj3(val) ON obj.val->>'paramId' = '3'
JOIN LATERAL jsonb_array_elements(i.data) obj5(val) ON obj2.val->>'paramId' = '5'
WHERE obj3.val->>'valeur' = 'cat'
AND obj5.val->>'valeur' LIKE '%ish%';
but I don't know how to search inside the fields array if fields exists.
Thank you in advance for you help.
EDIT:
It looks like my question is not clear. I will try to make it better.
What I want to do is to find all the 'item' having in the 'data' column objects who match my search criteria. This without looking if the objects are at first level or inside a 'fields' key of an object.
Again for example. This record should be selected if I search:
'paramId': 3 AND 'value': 'cat
'paramId': 4 AND 'value': LIKE '%og%'
the matching ones are in the 'fields' key of the object with 'paramId': 6 and I don't know how to do that.
This can be expressed using a JSON/Path expression without the need for unnesting everything
To search for paramId = 3 and value = 'cat'
select *
from items
where data #? '$[*] ? ( (#.paramId == 3 && #.value == "cat") || exists( #.fields[*] ? (#.paramId == 3 && #.value == "cat")) )'
The $[*] part iterates over all elements of the first level array. To check the elements in the fields array, the exists() operator is used to nest the expression. #.fields[*] iterates over all elements in the fields array and applies the same expression again. I don't see a way how repeating the values could be avoided though.
For a "like" condition, you can use like_regex:
select *
from items
where data #? '$[*] ? ( (#.paramId == 4 && #.value like_regex ".*og.*") || exists( #.fields[*] ? (#.paramId == 4 && #.value like_regex ".*og.*")) )'
For now I have found a solution but it is not really clean and I don't know how it will perform in production with 10M records.
SELECT i.id, i.data
FROM ( -- A;
select it.id, it.data, i as value
from items it,
jsonb_array_elements(it.data) i
union
select it.id, it.data, f as value
from items it,
jsonb_array_elements(it.data) i,
jsonb_array_elements(i -> 'fields') f
) as i
WHERE (i.value ->> 'paramId' = '5' -- B1;
AND i.value ->> 'value' LIKE '%ish%')
OR (i.value ->> 'paramId' = '3' -- B2;
AND i.value ->> 'value' = 'cat')
group by i.id, i.data
having COUNT(*) >= 2; -- C;
A: I "flatten" the first and second level (second level is in 'fields' key)
B1, B2: These are my search criteria
C: I make sure the fields have all the criteria matching. If 3 criteria --> COUNT(*) >=3
It really doesn't look clean to me. It is working for dev purpose but I think there is a better way to do it.
If somebody have an idea Big thanks to him/her!

How to get particular object from jsonb in PostgreSQL?

I have a table called 'Test' that holds two fields 'qnId' and 'Answers'.
'qnId' stores a uuid and 'Answers' is a jsonb array that roughly looks like this:
[{ "user" : "1", "ans" : "some text" }, { "user" : "3", "ans": "some text"}]
how can I retrieve the value of "ans" of "user" with value 3.
How can I retrieve the value using normal SQL queries
try something like this:
select
x ->> 'ans' as user_3_ans
from
jsonb_array_elements('[{ "user" : "1", "ans" : "some text 1" }, { "user" : "3", "ans": "some text 3"}]'::jsonb) as x
where
x ->> 'user' = '3'
same, but from table:
with
table1 as (
select
1 as id,
'[{ "user" : "1", "ans" : "some text 1" }, { "user" : "3", "ans": "some text 3"}]'::jsonb as answers
)
select
id,
answers,
el ->> 'ans' as user_3_ans
from
( select
id,
answers,
jsonb_array_elements(answers) as el
from
table1) as x
where
el ->> 'user' = '3'
demo:db<>fiddle
You can use jsonb_array_elements() to expand the array elements into one row each. Afterwards you are able to filter the right elements using the ->> operator (documentation):
SELECT
uuid,
elements ->> 'ans'
FROM
mytable,
jsonb_array_elements(answers) elements
WHERE
elements ->> 'user' = '3'