SQL query with json column - sql

I have a table individual customer with a column employmentDetails as json field.
The task is to get a customer that has empty locality field
[{
"employmentStatus": "E",
"communicationInfo": {
"addresses": [
{
"id": "1",
"houseName": "1",
"locality": null
}
]
}}]
I tried several variants with casting etc with no success, please help me to understand how to query from json object fields. My attempts below that should return some values but returned nothing.
SELECT * FROM crm."IndividualCustomer" AS ic
WHERE (ic."employmentDetails" -> 'employmentStatus')::text = 'E';
SELECT * FROM crm."IndividualCustomer" AS ic
WHERE ic."employmentDetails" -> 'communicationInfo' -> 'adresses[0]' ->> 'locality' = null;
SELECT * FROM crm."IndividualCustomer" AS ic
WHERE ic."employmentDetails" -> 'communicationInfo' -> 'adresses' ->> 'locality' = null;

The task is to get a customer that has empty locality field
You can use a JSON path expression:
SELECT *
FROM crm."IndividualCustomer" AS ic
WHERE ic."employmentDetails" ## '$[*].communicationInfo.addresses[*].locality == null'
This requires Postgres 12 or later. If you have an older version, you can use the contains operator #>:
WHERE ic."employmentDetails" #> '[{"communicationInfo": {"addresses": [{"locality": null}]}}]'
This assumes that "employmentDetails" is defined as jsonb (which it should be). If it's not, you need to cast it: "employmentDetails"::jsonb.
The condition: (ic."employmentDetails" -> 'employmentStatus')::text = 'E' doesn't work, because -> returns a jsonb (or json) value which can't really be cast to a proper text value (the double quotes are kept if you do so).
You need to use the ->> operator which returns a text value directly: (ic."employmentDetails" ->> 'employmentStatus') = 'E'
However, the top level object is an array, so you would need to pick the e.g. the first array element:
(ic."employmentDetails" -> 0 ->> 'employmentStatus') = 'E'
Note the -> to return the first array element as a proper jsonb value, then the use of ->> on that value.
This can also be done using a JSON path expression to search through all array elements:
WHERE ic."employmentDetails" ## '$[*].employmentStatus == "E"'`

Related

jsonb find a value in an array

There is such a data structure:
Column "recipient" type jsonb
{
"phoneNumbers": [
{
"isDefault": true,
"type": "MOBILE",
"number": "3454654645"
},
{
"isDefault": true,
"type": "MOBILE",
"number": "12423543645"
}
]
}
I need to write a search request by number. In the postgres documentation, I did not find a search by value in an array, only it is obtained by an index. It doesn't suit me
I made a query like this, it gets executed, but are there any other ways to search through an array?
SELECT *
FROM my_table
WHERE recipient -> 'phoneNumbers' #> '[{"number":3454654645}]'
That's pretty much the best way, yes.
If you have a (GIST) index on recipient the index would not be used by your condition. But the following could make use of such an index:
SELECT *
FROM my_table
WHERE recipient #> '["phoneNumbers": {"number":3454654645}]}'
If you are using Postgres 12 or later, you can also use a JSON path expression:
SELECT *
FROM my_table
WHERE recipient ## '$.phoneNumbers[*].number == "12423543645"'
If you can't pass a JSON object to your query, you can use an EXISTS sub-select:
SELECT mt.*
FROM my_table mt
WHERE EXISTS (SELECT *
FROM jsonb_array_elements_text(mt.recipient -> 'phoneNumbers') as x(element)
WHERE x.element ->> 'number' = '3454654645')
The '3454654645' can be passed as a parameter to your query. This will never make use of an index though.

Querying an array of objects in JSONB

I have a table with a column of the data type JSONB. Each row in the column has a JSON that looks something like this:
[
{
"A":{
"AA": "something",
"AB": false
}
},
{
"B": {
"BA":[
{
"BAAA": [1,2,3,4]
},
{
"BABA": {
....
}
}
]
}
}
]
Note: the JSON is a complete mess of lists and objects, and it has a total of 300 lines. Not my data but I am stuck with it. :(
I am using postgresql version 12
How would I write the following queries:
Return all row that has the value of AB set to false.
Return the values of BAAA is each row.
You can find the AB = false rows with a JSON Path query:
select *
from test
where data ## '$[*].A.AB == false'
If you don't know where exactly the key AB is located, you can use:
select *
from test
where data ## '$[*].**.AB == false'
To display all elements from the array as rows, you can use:
select id, e.*
from test
cross join jsonb_array_elements(jsonb_path_query_first(data, '$[*].B.BA.BAAA')) with ordinality as e(item, idx)
I include a column "id" as a placeholder for the primary key column, so that the source of the array element can be determined in the output.
Online example

How to update the key of a json object?

I have this json format in my database jsonb column
[
{
"firstName": "John",
"lastName": "Doe"
}
]
and I want to update the key "firstName", how to do that?
So far I have this query:
UPDATE person
SET
field = jsonb_set(
field::jsonb,
concat('{0,firstName}')::text[],
'"newFirstName"'::jsonb)
but the query above updates the value instead of the key.. how can I update just the key?
You can extract the element without the firstName key, append the new key/value and then put it back into the array:
update person
set field = jsonb_set(field,
'{0}',
((field -> 0) - 'firstName')||jsonb_build_object('newFirstName', field -> 0 -> 'firstName'));
(field -> 0) - 'firstName') returns the first element without the firstname key.
Then jsonb_build_object('newFirstName', field -> 0 -> 'firstName') builds a new key/value pair with the new keyname, but the existing value of firstName and this is appended to the result of the first expression using ||
The result of the above is then put into the first array element using jsonb_set()
Online example: https://rextester.com/YPF75074

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

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.

Recursive update jsonb object in PostgreSQL v9.5+

I use jsonb_set to partially update my jsonb object in postgres.
This what the doc says about this function.
jsonb_set(
target jsonb, # The jsonb value you're amending.
path text[], # The path to the value you wish to add to or change, represented as a text array.
new_value jsonb, # The new object, key : value pair or array value(s) to add to or change.
create_missing boolean # An optional field that, if true (default), creates the value if the key doesn't already exist.
# If false, the path must exist for the update to happen, or the value won't be updated.
)
I thought that create_missing (which is true by default) will cause to unexisting path appear in my jsonb object but it seem like this works only on one last step (e.g. not recursive).
the query
UPDATE myScheme.myTable SET data = jsonb_set(data, $1, $2, true) where id = $3;
will fail if $1 = {foo,bar,baz} and my current data = {foo: {}}
The question is: How to update my jsonb object with recursive creating unexisting subobjects in PostgreSQL v9.5+?
You can use something like that, where instead of t put the name of your table:
UPDATE t SET data = jsonb_set(data,'{foo,bar}','{"baz":{"key1":{},"key2":[2],"key3":3,"key4":"val4"}}'::JSONB);
Result will be:
SELECT jsonb_pretty(data) FROM t;
jsonb_pretty
--------------------------------
{ +
"foo": { +
"bar": { +
"baz": { +
"key1": { +
}, +
"key2": [ +
2 +
], +
"key3": 3, +
"key4": "val4"+
} +
} +
} +
}
(1 row)
With this approach (when all structure is defined inside new_value parameter) you are free to create any kinds of nested elements (arrays, nested documents, string or integer values);
On the other side, to do it in path parameter will be very tricky.