How to update the key of a json object? - sql

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

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

How to deal with list of JSON data stored in text format in postgreSQL?

create table test (data text);
insert into test values
(
'[{
"name": "Alexa",
"age": "20"
},
{
"name": "Siri",
"age": "42"
}]'
);
Here data column is of text type (It has list of JSON DATA). I want to change lets say "name" = "Alexa" to "name" = "Cortana". How to do this?
If it would be of JSON type I would have done this.
UPDATE test
SET data = jsonb_set(data, '{0,name}', '"Cortana"', true)
Since data is of text type, do i have to do something like this?
cast to jsonb -> update that jsonb data -> cast to text -> replace old text to new text
Please suggest any good way to do this.

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

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.

updating a value in an array in mongodb from java

I have couple of documens in mongodb as follow:
{
"_id" : ObjectId("54901212f315dce7077204af"),
"Date" : ISODate("2014-10-20T04:00:00.000Z"),
"Type" : "Twitter",
"Entities" : [
{
"ID" : 4,
"Name" : "test1",
"Sentiment" : {
"Value" : 20,
"Neutral" : 1
}
},
{
"ID" : 5,
"Name" : "test5",
"Sentiment" : {
"Value" : 10,
"Neutral" : 1
}
}
]
}
Now I want to update the document that has Entities.ID=4 by adding (Sentiment.Value+4)/2 for example in the above example after update we have 12.
I wrote the following code but I am stuck in the if statement as you can see:
DBCollection collectionG;
collectionG = db.getCollection("GraphDataCollection");
int entityID = 4;
String entityName = "test";
BasicDBObject queryingObject = new BasicDBObject();
queryingObject.put("Entities.ID", entityID);
DBCursor cursor = collectionG.find(queryingObject);
if (cursor.hasNext())
{
BasicDBObject existingDocument = new BasicDBObject("Entities.ID", entityID);
//not sure how to update the sentiment.value for entityid=4
}
First I thought I should unwind the Entities array first to get the value of sentiment but if I do that then how can I wind them again and update the document with the same format as it has now but with the new sentiment value ?
also I found the this link as well :
MongoDB - Update objects in a document's array (nested updating)
but I could not understand it since it is not written in java query,
can anyone explain how I can do this in java?
You need to do this in two steps:
Get all the _id of the records which contain a Entity with sentiment
value 4.
During the find, project only the entity sub document that has
matched the query, so that we can process it to consume only its
Sentiment.Value. Use the positional operator($) for this purpose.
Instead of hitting the database every time to update each matched
record, use the Bulk API, to queue up the updates and execute it
finally.
Create the Bulk operation Writer:
BulkWriteOperation bulk = col.initializeUnorderedBulkOperation();
Find all the records which contain the value 4 in its Entities.ID field. When you match documents against this query, you would get the whole document returned. But we do not want the whole document, we would like to have only the document's _id, so that we can update the same document using it, and the Entity element in the document that has its value as 4. There may be n other Entity documents, but they do not matter. So to get only the Entity element that matches the query we use the positional operator $.
DBObject find = new BasicDBObject("Entities.ID",4);
DBObject project = new BasicDBObject("Entities.$",1);
DBCursor cursor = col.find(find, project);
What the above could would return is the below document for example(since our example assumes only a single input document). If you notice, it contains only one Entity element that has matched our query.
{
"_id" : ObjectId("54901212f315dce7077204af"),
"Entities" : [
{
"ID" : 4,
"Name" : "test1",
"Sentiment" : {
"Value" : 12,
"Neutral" : 1
}
}
]
}
Iterate each record to queue up for update:
while(cursor.hasNext()){
BasicDBObject doc = (BasicDBObject)cursor.next();
int curVal = ((BasicDBObject)
((BasicDBObject)((BasicDBList)doc.get("Entities")).
get(0)).get("Sentiment")).getInt("Value");
int updatedValue = (curVal+4)/2;
DBObject query = new BasicDBObject("_id",doc.get("_id"))
.append("Entities.ID",4);
DBObject update = new BasicDBObject("$set",
new BasicDBObject("Entities.$.Sentiment.Value",
updatedValue));
bulk.find(query).update(update);
}
Finally Update:
bulk.execute();
You need to do a find() and update() and not simply an update, because currently mongodb does not allow to reference a document field to retrieve its value, modify it and update it with a computed value, in a single update query.