Recursive update jsonb object in PostgreSQL v9.5+ - sql

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.

Related

SQL query with json column

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"'`

Cannot update document by index in FaunaDB

I'm attempting to update a document using an index in my FaunaDB collection using FQL.
Update(
Match(
Index('users_by_id'),
'user-1'
),
{
data: {
name: 'John'
}
}
)
This query gives me the following error:
Error: [
{
"position": [
"update"
],
"code": "invalid argument",
"description": "Ref expected, Set provided."
}
]
How can I update the document using the index users_by_id?
Match returns a set reference, not a document reference, because there could be zero or more matching documents.
If you are certain that there is a single document that matches, you can use Get. When you call Get with a set reference (instead of a document reference), the first item of the set is retrieved. Since Update requires a document reference, you can then use Select to retrieve the fetched document's reference.
For example:
Update(
Select(
"ref",
Get(Match(Index('users_by_id'), 'user-1'))
),
{
data: {
name: 'John'
}
}
)
If you have more than one match, you should use Paginate to "realize" the set into an array of matching documents, and then Map over the array to perform a bulk update:
Map(
Paginate(
Match(Index('users_by_id'), 'user-1')
),
Lambda(
"ref",
Update(
Var("ref"),
{
data: {
name: "John",
}
}
)
)
)
Note: For this to work, your index has to have an empty values definition, or it must explicitly define the ref field as the one and only value. If your index returns multiple fields, the Lambda function has to be updated to accept the same number of parameters as are defined in your index's values definition.

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

Postgres - query JSON column value of nested object

I'm using following schema for the JSONB column of my table (named fields). There are several of these field entries.
{
"FIELD_NAME": {
"value" : "FIELD_VALUE",
"meta": {
"indexable": true
}
}
}
I need to find all the fields that contain this object
"meta": {
"indexable": true
}
Here is a naive attempt at having json_object_keys in where clause, which doesn't work, but illustrates what I'm trying to do.
with entry(fields) as (values('{
"login": {
"value": "fred",
"meta": {
"indexable": true
}
},
"password_hash": {
"value": "88a3d1c7463d428f0c44fb22e2d9dc06732d1a4517abb57e2b8f734ce4ef2010",
"meta": {
"indexable": false
}
}
}'::jsonb))
select * from entry where fields->jsonb_object_keys(fields) #> '{"meta": {"indexable": "true"}}'::jsonb;
How can I query on the value of nested object? Can I somehow join the result of json_object_keys with the table iself?
demo:db<>fiddle
First way: using jsonb_each()
SELECT
jsonb_build_object(elem.key, elem.value) -- 3
FROM
entry,
jsonb_each(fields) as elem -- 1
WHERE
elem.value #> '{"meta": {"indexable": true}}' -- 2
Expand all subobjects into one row per "field". This creates 2 columns: the key and the value (in your case login and {"meta": {"indexable": true}, "value": "fred"})
Filter the records by checking the value column for containing the meta object using the #> as you already mentioned
Recreate the JSON object (combining the key/value columns)
Second way: Using jsonb_object_keys()
SELECT
jsonb_build_object(keys, fields -> keys) -- 3
FROM
entry,
jsonb_object_keys(fields) as keys -- 1
WHERE
fields -> keys #> '{"meta": {"indexable": true}}' -- 2
Finding all keys as you did
and 3. are very similar to the first way