Postgres - updating an array element in a json column - json

I have a json column in a postgres table.
The column contains the following json data:
{
"data": {
"id": "1234",
"sites": [
{
"site": {
"code": "1",
"display": "Site1"
}
},
{
"site": {
"code": "2",
"display": "Site2"
},
"externalSite": true
},
{
"site": {
"code": "3",
"display": "Site3"
}
}
]
}
}
I need to create an update query that adds another attribute ('newAttribute' in the sample below) to all array items that have '"externalSite": true', so, after running the update query the second array element will be:
{
"site": {
"code": "2",
"display": "Site2"
},
"externalSite": true,
"newAttribute": true
}
The following query returns the array elements that need to be updated:
select * from myTable, jsonb_array_elements(data -> 'sites') sites
where sites ->'externalSite' = 'true'
What is the syntax of the update query?
Thanks
Kobi

Assuming your table is called test and your column is called data, you can update it like so:
UPDATE test SET data =
(select jsonb_set(data::jsonb, '{"data","sites"}', sites)
FROM test
CROSS JOIN LATERAL (
SELECT jsonb_agg(CASE WHEN site ? 'externalSite' THEN site || '{"newAttribute":"true"}'::jsonb
ELSE site
END) AS sites
FROM jsonb_array_elements( (data#>'{"data","sites"}')::jsonb ) as ja(site)
) as sub
);
Note that I cast the data to jsonb data as there are more functions and operators available for manipulating jsonb than plain json.
You can run the SELECT statement alone to see what it is doing, but the basic idea is to re-create the sites object by expanding it with jsonb_array_elements and adding the newAttribute attribute if externalSite exists.
This array is then aggregated with jsonb_agg and, finally, in the outer select, the sites object is replaced entirely with this newly computed version.

Related

How do I properly use deleteMany() with an $and query in the MongoDB shell?

I am trying to delete all documents in my collection infrastructure that have a type.primary property of "pipelines" and a type.secondary property of "oil."
I'm trying to use the following query:
db.infrastructure.deleteMany({$and: [{"properties.type.primary": "pipelines"}, {"properties.type.secondary": "oil"}] }),
That returns: { acknowledged: true, deletedCount: 0 }
I expect my query to work because in MongoDB Compass, I can retrieve 182 documents that match the query {$and: [{"properties.type.primary": "pipelines"}, {"properties.type.secondary": "oil"}] }
My documents appear with the following structure (relevant section only):
properties": {
"optional": {
"description": ""
},
"original": {
"Opername": "ENBRIDGE",
"Pipename": "Lakehead",
"Shape_Leng": 604328.294581,
"Source": "EIA"
},
"required": {
"unit": null,
"viz_dim": null,
"years": []
},
"type": {
"primary": "pipelines",
"secondary": "oil"
}
...
My understanding is that I just need to pass a filter to deleteMany() and that $and expects an array of objects. For some reason the two combined isn't working here.
I realized the simplest answer was the correct one -- I spelled my database name incorrectly.

Use JSON file in postgresql database

I have a table in a postgresql database with one field with json format.
It's look like this :
[
{
"transition": "transition1",
"from": "step1",
"to": "step2",
"date": {
"date": "2021-01-30 15:34:06.840859"
}
},
{
"transition": "transition2",
"from": "step2",
"to": "step3",
"date": {
"date": "2021-01-30 16:52:00.412208"
}
}
]
I want to have a new column with the date of transition1.
I tried a lot of things but I can't figure out how to extract this date, I can't use the index because the number of transition is not fixed, some user could have 3 and other more than 10.
You need to iterate over all array elements to find the correct one:
select (select (item -> 'date' ->> 'date')::timestamp
from jsonb_array_elements(jsonb_column) as x(item)
where item ->> 'transition' = 'transition1') as transition_date
from the_table
;
If you column is defined as json rather than jsonb (which it should be) you need to use json_array_elements() instead.

Update a mixed and nested object in Snowflake

I have a Snowflake table with one variant column (raw).
Every row in this table is complex (both dictionaries and arrays) and nested (multiple hierarchies).
What I want to do is to be able to update a specific item in some array.
It will be easier to understand it using an example so consider this as a row in the table:
{
"id": "1234"
"x_id": [
{
"y_id": "790437306684007491",
"y_state": "some_state"
}
],
"comments": {
"1": [
{
"comment_id": "bb288743-3b73-4423-b76b-f26b8c37f7d4",
"comment_timestamp": "2021-02-10 14:53:25.667564",
"comment_text": "Hey"
},
{
"comment_id": "7378f332-93c4-4522-9f73-3b6a8a9425ce",
"comment_text": "You",
"comment_timestamp": "2021-02-10 14:54:21.337046"
}
],
"2": [
{
"comment_id": "9dd0cbb0-df80-4b0f-b399-9ee153161462",
"comment_text": "Hello",
"comment_timestamp": "2021-02-09 09:26:17.987386"
},
{
"comment_id": "1a3bf1e8-82b5-4a9c-a959-a1da806ce7e3",
"comment_text": "World",
"comment_timestamp": "2021-02-09 09:28:32.144175"
}
]
}
}
And what I want is to update the comment text of a specific comment.
I know that I can update the whole JSON programmatically and update the whole object using PARSE_JSON, but this approach isn't sufficient because there could be other updates that will override other comments so this approach will fail (because these update will override each other).
So first, I've tried the naive approach (which I knew wouldn't work but I had to try):
update table1
set raw['comments']['1'][0]["comment_text"] = 'please work'
And not surprisingly I'm getting the following error:
SQL compilation error: syntax error line 2 at position 7 unexpected '['.
Next, I tried OBJECT_INSERT which should allow a way to update an object but it fails because of the nested key ('1'):
UPDATE table1
SET raw = OBJECT_INSERT(raw:comments:1, "comment_test", 'please work')
with the error
SQL compilation error: syntax error line 1 at position 99 unexpected '1'.
(I've also tried several permutations of this approach with raw:comments:"1" or raw:comments:1[0] or raw['comments']['1'] and some others)
I also tried to refactor the object so instead of having the comments as dictionary, to flat the comments into an array, something like:
{
"id": "1234"
"x_id": [
{
"y_id": "790437306684007491",
"y_state": "some_state"
}
],
"comments": [
{
"comment_id": "bb288743-3b73-4423-b76b-f26b8c37f7d4",
"comment_timestamp": "2021-02-10 14:53:25.667564",
"comment_text": "Hey"
"comment_key": "1"
},
{
"comment_id": "7378f332-93c4-4522-9f73-3b6a8a9425ce",
"comment_text": "You",
"comment_timestamp": "2021-02-10 14:54:21.337046"
"comment_key": "1"
}
{
"comment_id": "9dd0cbb0-df80-4b0f-b399-9ee153161462",
"comment_text": "Hello",
"comment_timestamp": "2021-02-09 09:26:17.987386",
"comment_key": "2"
},
{
"comment_id": "1a3bf1e8-82b5-4a9c-a959-a1da806ce7e3",
"comment_text": "World",
"comment_timestamp": "2021-02-09 09:28:32.144175",
"comment_key": "2"
}
]
}
But this doesn't get me any closer to a solution. I've looked for some ARRAY_REPLACE function that replace an item in array, but it doesn't look that such function exists (all semi-structured related functions)
I've also considered using JavaScript UDF's to do it, but I didn't find any source to UDF's that can actually update a row (they're all used to get data and not update it, as far from what I saw).
Is there any way to achieve what I want?
Thanks a lot!
You can update complex JSON structures using JavaScript UDFs. Here's a sample. Note that both of your JSON samples have errors. I used the second one and fixed the missing commas.
-- Create a temp table with a sigle variant. By convention, I uses "v" as the name of any
-- column in a single-column table. You can change to "raw" in your code.
create or replace temp table foo(v variant);
-- Create a UDF that updates the exact key you want to update.
-- Unfortunately, JavaScript treats the object path as a constant so you can't make this
-- a string that you pass in dynamically. There are ways around this possibly, but
-- library restrictions would require a raw JavaScript parser function. Just update the
-- path you need in the UDF.
create or replace function update_json("v" variant, "newValue" string)
returns variant
language javascript
as
$$
v.comments[0].comment_text = newValue;
return v;
$$;
-- Insert the corrected JSON into the variant field
insert into foo select parse_json('{
"id": "1234",
"x_id": [{
"y_id": "790437306684007491",
"y_state": "some_state"
}],
"comments": [{
"comment_id": "bb288743-3b73-4423-b76b-f26b8c37f7d4",
"comment_timestamp": "2021-02-10 14:53:25.667564",
"comment_text": "Hey",
"comment_key": "1"
},
{
"comment_id": "7378f332-93c4-4522-9f73-3b6a8a9425ce",
"comment_text": "You",
"comment_timestamp": "2021-02-10 14:54:21.337046",
"comment_key": "1"
},
{
"comment_id": "9dd0cbb0-df80-4b0f-b399-9ee153161462",
"comment_text": "Hello",
"comment_timestamp": "2021-02-09 09:26:17.987386",
"comment_key": "2"
},
{
"comment_id": "1a3bf1e8-82b5-4a9c-a959-a1da806ce7e3",
"comment_text": "World",
"comment_timestamp": "2021-02-09 09:28:32.144175",
"comment_key": "2"
}
]
}');
-- Show how the change works without updating the row
select update_json(v, 'please work') from foo;
-- Now update the row using the output. Note that this is updating the
-- whole variant field, not a portion of it.
update foo set v = update_json(v, 'please work');
-- Show the updated key
select v:comments[0].comment_text::string from foo;
Finally, if you want to modify a property that you have to look through the keys to find what you need first, you can do that in a loop in JavaScript. For example, if it's not the 1st comment you need but the one with a particular UUID or comment_text, etc., you can loop through to find it and update the comment_key on the same iteration of the loop.
Thanks, it works!
I've kinda managed to get it to work using built-in functions -
Assuming we know the position of the comment (in this example, position=3):
UPDATE table1 SET
raw = object_construct(
'id', raw:id,
'x_id', raw:x_id,
'comments', array_cat(array_append(array_slice(raw:comments ,0 ,2), parse_json('{"id": "3", "comment_text": "please work"}')) , ARRAY_SLICE(raw:comments,3,array_size(raw:comments)))
)
WHERE raw['id'] = 'some_id'
But I'm still thinking which approach will do the work better.
Anyway, thanks, helped a lot.

How to update a specific JSON element inside a complex JSON document in postgresql 10

I have JSON document which is stored under single column of type jsonb inside postgresql which looks like below:
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:100",
"resource": {
"resourceType": "Encounter",
"id": "110",
"status": "planned",
"priority": {
"coding": [
{
"code": "ASAP"
}
]
},
"subject": {
"reference": "Patient/123"
},
"appointment": [
{
"reference": "Appointment/12213#42"
}
],
"diagnosis": [
{
"condition": {
"reference": "Condition/condReferenceValue"
},
"use": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/diagnosis-role",
"code": "AD"
},
{
"system": "http://terminology.hl7.org/CodeSystem/diagnosis-role",
"code": "DD"
}
]
}
}
],
"hospitalization": {
"preAdmissionIdentifier": {
"system": "https://system.html"
}
},
"location": [
{
"location": {
"display": "Mumbai"
},
"status": "active"
},
{
"status": "planned"
}
]
},
"request": {
"method": "POST",
"url": "Encounter"
}
}
]
}
Now, I want to update value for reference under subject attribute. So, I tried below way but it throws an error:
update fhir.testing set names = jsonb_set(names,'{"subject":{"reference"','"Patient/1"',true) where id = 10;
Error:
SQL Error [22P02]: ERROR: malformed array literal: "{"subject":{"reference""
Detail: Unexpected array element.
I referred this link but didn't work out for me. How can I do it?
I don't use Postgres that much but from what i read in the relative jsonb_set example in the documentation of JSON functions (and since you want to update) shouldn't it be
jsonb_set(names, '{entry,0,subject,reference}','Patient/1', false)
instead of
jsonb_set(names,'{"subject":{"reference"','"Patient/1"',true)
jsonb
jsonb_set(target jsonb, path text[], new_value jsonb [, create_missing
boolean])
Returns target with the section designated by path replaced by
new_value, or with new_value added if create_missing is true (default
is true) and the item designated by path does not exist. As with the
path oriented operators, negative integers that appear in path count
from the end of JSON arrays.
EDIT
To explain the path used in jsonb_set, check this example.
jsonb_set('[{"f1":1,"f2":null},2,null,3]', '{0,f1}','[2,3,4]', false)
returns
[{"f1":[2,3,4],"f2":null},2,null,3]
As i understand if a sub-element in a complex JSON document is an array, you need to specify it's index e.g. 0,1,2,...
EDIT
Always look very carefully the structure of the JSON document. I simply write this because i did not see that subject was a child of resource and that is causing you the error.
So the correct path is actually '{entry,0,resource,subject,reference}'
Correct Query for your requirement is:
update fhir.testing
set names= jsonb_set(names, '{entry,0,resource,subject,reference}', '"Patient/1"' , false)
where id = 10;
Explanation
json_set takes 4 parameter
target_json (jsonb) - which accept jsonb type data. In your case it is names field.
path (text[]) - which accepts a text array. in your case it is '{entry,0,resource,subject,reference}'.
new_value (jsonb) - in your case you want to change it to '"Patient/1"'.
create_missing (boolean) - in your case it should be false. as you want to replace the existing one. if you want to create the reference with given value in case of not found then just mark it true.
the value is not valid json, try this out:
update fhir.testing set names = jsonb_set(names, '{"entry": [{"resource": {"subject":{"reference":"Patient/1"} }}]}',true) where id = 10;
You have to create a valid json, closing every { and every ], yours was
'{"subject":{"reference"'

Accessing an Array Inside JSON with a Postgres Query

I have a table with a data_type of json that I need to query one of the properties inside of it.
This is what the data in the column looks like:
{
"id": 7008,
"access_links": [
{
"product_code": "PRODUCT-1",
"link": "https://some.url"
},
{
"product_code": "PRODUCT-2",
"link": "https://someOther.url"
}
],
"library_id": "2d1203db-75b3-43a5-947c-8555b48371db"
}
I need to be able to pull out and filter by the product_code nested inside of the access_links.
I can get one layer deep by using this query:
SELECT
courses.course_metadata -> 'access_links' as access_links
FROM
courses
This seems to get me into the column, but I can't query any further.
The output I receive from the query looks like:
[{"product_code":"PRODUCT-1","link":"https://some.url"},{"product_code":"PRODUCT-2","link":"https://someOther.url"}]
I've tried using the ->> and #>> operators, but they both complain about the array not starting with a {. Also worth noting that the column is a data type of JSON not JSONB, so the #> operator doesn't work.
What am I missing here?
Does this help?
select
json_array_elements (x->'access_links')->'product_code' as product_code
from
(select '{
"id": 7008,
"access_links": [
{
"product_code": "PRODUCT-1",
"link": "https://some.url"
},
{
"product_code": "PRODUCT-2",
"link": "https://someOther.url"
}
],
"library_id": "2d1203db-75b3-43a5-947c-8555b48371db"
}'::json x
) as v
;
product_code
"PRODUCT-1"
"PRODUCT-2"