update value in list Postgres jsonb - arrays

I am trying to update json
[{"id": "1", "name": "myconf", "icons": "small", "theme": "light", "textsize": "large"},
{"id": 2, "name": "myconf2", "theme": "dark"}, {"name": "firstconf", "theme": "dark", "textsize": "large"},
{"id": 3, "name": "firstconxsf", "theme": "dassrk", "textsize": "lassrge"}]
and this is the table containing that json column :
CREATE TABLE USER_CONFIGURATIONS ( ID BIGSERIAL PRIMARY KEY, DATA JSONB );
adding new field is easy I am using:
UPDATE USER_CONFIGURATIONS
SET DATA = DATA || '{"name":"firstconxsf", "theme":"dassrk", "textsize":"lassrge"}'
WHERE id = 9;
But how to update single with where id = 1 or 2

Click: step-by-step demo:db<>fiddle
UPDATE users -- 4
SET data = s.updated
FROM (
SELECT
jsonb_agg( -- 3
CASE -- 2
WHEN ((elem ->> 'id')::int IN (1,2)) THEN
elem || '{"name":"abc", "icon":"HUGE"}'
ELSE elem
END
) AS updated
FROM
users,
jsonb_array_elements(data) elem -- 1
) s;
Expand array elements into one row each
If element has relevant id, update with || operator; if not, keep the original one
Reaggregate the array after updating the JSON data
Execute the UPDATE statement.

Related

Duplicate row detected during DML action Row Values

Each of our messages has a unique id and several attributes; the final result should combine all of these attributes into a single message. We tried using snowflake merge, but it's not working as expected. In the first run, we used row number and partition to determine unique records, and we inserted them. In the second run, we considered updating more than one record, but we received the error message "Error 3:Duplicate row detected during DML action Row Values: \n".
Session ERROR ON NONDETERMINISTIC MERGE=FALSE was tried, however the outcome might not be reliable or consistent.
We tried javascript deep merge, however the volume was really high and there were performance problems.
Sample code below:
create or replace table test1 (Job_Id VARCHAR, RECORD_CONTENT VARIANT);
create or replace table test2 like test1;
insert into test1(JOB_ID, RECORD_CONTENT) select 1,parse_json('{
"customer": "Aphrodite",
"age": 32,
"orders": {"product": "socks","quantity": 4, "price": "$6", "attribute1" : "a1"}
}');
insert into test1(JOB_ID, RECORD_CONTENT) select 1,parse_json('{
"customer": "Aphrodite",
"age": 32,
"orders": {"product": "shoe", "quantity": 2, "brand" : "Woodland","attribute2" : "a2"}
}');
insert into test1(JOB_ID, RECORD_CONTENT) select 1,parse_json('{
"customer": "Aphrodite",
"age": 32,
"orders": {"product": "shoe polish","brand" : "Helios", "attribute3" : "a3" }
}');
merge into test2 t2 using (
select * from (select
row_number() over(partition by JOB_ID order by JOB_ID desc) as rno, JOB_ID, RECORD_CONTENT
from test1) where rno>1) t1 on //1. first run - unique values inserted using "=1" -> Successfully inserted unique values
//2. second run - updating attributes using ">1" -> Duplicate row detected during DML action Row Values
t1.JOB_ID = t2.JOB_ID
WHEN MATCHED THEN
UPDATE
SET t2.JOB_ID = t1.JOB_ID,
t2.RECORD_CONTENT = t1.RECORD_CONTENT
WHEN NOT MATCHED
THEN INSERT (JOB_ID, RECORD_CONTENT) VALUES (t1.JOB_ID, t1.RECORD_CONTENT)
Expected Output:-
select * from test2;
select parse_json('{
"customer": "Aphrodite",
"age": 32,
"orders": {"product": "shoe polish","quantity": 2, "brand" : "Helios","price": "$6",
"attribute1" : "a1","attribute2" : "a2","attribute3" : "a3" }
}');

Postgresql update jsonb keys recursively

Having the following datamodel:
create table test
(
id int primary key,
js jsonb
);
insert into test values (1, '{"id": "total", "price": 400, "breakdown": [{"id": "product1", "price": 400}] }');
insert into test values (2, '{"id": "total", "price": 1000, "breakdown": [{"id": "product1", "price": 400}, {"id": "product2", "price": 600}]}');
I need to update all the price keys to a new name cost.
It is easy to do that on the static field, using:
update test
set js = jsonb_set(js #- '{price}', '{cost}', js #> '{price}');
result:
1 {"id": "total", "cost": 1000, "breakdown": [{"id": "product1", "price": 400}]}
2 {"id": "total", "cost": 2000, "breakdown": [{"id": "product1", "price": 400}, {"id": "product2", "price": 600}]}
But I also need to do this inside the breakdown array.
How can I do this without knowing the number of items in the breakdown array?
In other words, how can I apply a function in place on every element from a jsonb array.
Thank you!
SOLUTION 1 : clean but heavy
First you create an aggregate function simlilar to jsonb_set :
CREATE OR REPLACE FUNCTION jsonb_set(x jsonb, y jsonb, _path text[], _key text, _val jsonb, create_missing boolean DEFAULT True)
RETURNS jsonb LANGUAGE sql IMMUTABLE AS
$$
SELECT jsonb_set(COALESCE(x, y), COALESCE(_path, '{}' :: text[]) || _key, COALESCE(_val, 'null' :: jsonb), create_missing) ;
$$ ;
DROP AGGREGATE IF EXISTS jsonb_set_agg (jsonb, text[], text, jsonb, boolean) CASCADE ;
CREATE AGGREGATE jsonb_set_agg (jsonb, text[], text, jsonb, boolean)
(
sfunc = jsonb_set
, stype = jsonb
) ;
Then, you call the aggregate function while iterating on the jsonb array elements :
WITH list AS (
SELECT id, jsonb_set_agg(js #- '{breakdown,' || ind || ',price}', '{breakdown,' || ind || ',cost}', js #> '{breakdown,' || ind || ',price}', true) AS js
FROM test
CROSS JOIN LATERAL generate_series(0, jsonb_array_length(js->'{breakdown}') - 1) AS ind
GROUP BY id)
UPDATE test AS t
SET js = jsonb_set(l.js #- '{price}', '{cost}', l.js #> '{price}')
FROM list AS l
WHERE t.id = l.id ;
SOLUTION 2 : quick and dirty
You simply convert jsonb to string and replace the substring 'price' by 'cost' :
UPDATE test
SET js = replace(js :: text, 'price', 'cost') :: jsonb
In the general case, this solution will replace the substring 'price' even in the jsonb string values and in the jsonb keys which include the substring 'price'. In order to reduce the risk, you can replace the substring '"price" :' by '"cost" :' but the risk still exists.
This query is sample and easy for change field:
You can see my query structure in: dbfiddle
update test u_t
set js = tmp.new_js
from (
select t.id,
(t.js || jsonb_build_object('cost', t.js ->> 'price')) - 'price'
||
jsonb_build_object('breakdown', jsonb_agg(
(b.value || jsonb_build_object('cost', b.value ->> 'price')) - 'price')) as new_js
from test t
cross join jsonb_array_elements(t.js -> 'breakdown') b
group by t.id) tmp
where u_t.id = tmp.id;
Another way to replace jsonb key in all jsonb objets into a jsonb array:
My query disaggregate the jsonb array. For each object, if price key exist, remove the price key from jsonb object, add the new cost key with the old price's value, then create a new jsonb array with the modified objects. Finally replace the old jsonb array with the new one.
WITH cte AS (SELECT id, jsonb_agg(CASE WHEN item ? 'price'
THEN jsonb_set(item - 'price', '{"cost"}', item -> 'price')
ELSE item END) AS cost_array
FROM test
CROSS JOIN jsonb_array_elements(js -> 'breakdown') WITH ORDINALITY arr(item, index)
GROUP BY id)
UPDATE test
SET js = jsonb_set(js, '{breakdown}', cte.cost_array, false)
FROM cte
WHERE cte.id = test.id;

SQL Server - return count of json array items in sql server column across multiple rows

Using SQL Server 2019, I have data in a table with the following columns:
id, alias, value
The value column contains json in this format:
[
{
"num": 1,
"description": "test 1"
},
{
"num": 2,
"description": "test 2"
},
{
"num": 3,
"description": "test 2"
}
]
I want to get a count of "num" items within the json for each row in my SQL table.
For example query result should look like
id CountOfNumFromValueColumn
------------------------------
1 3
Update - The SQL below works with the above json and gives results in the above format
SELECT id, count(Num) AS CountOfNumFromValueColumn
FROM MyTable
CROSS APPLY OPENJSON ([value], N'$')
WITH (
num int)
WHERE ISJSON([value]) > 0
GROUP BY id

Update/Add Array Value PostgreSQL

My table:
CREATE TABLE public.invoice (
id bigint NOT NULL,
json_data jsonb NOT NULL
);
My Data:
INSERT INTO public.invoice (id,json_data) VALUES
(1,'{"Id": 1, "Items": ["2", "3", "1"], "Invoice": "CR000012"}');
Todo List:
Need to add to "Items" a new value i.e "5". (output of items ["2", "3", "1","5"])
Need to update items value 2 to 9. (output of items ["9", "3", "1","5"])
I have tried below but this will replace the array values not update or add
UPDATE invoice SET json_data = jsonb_set(json_data, '{invoice }', '"4"') where Id ='1'
I recommend you to use this aproach, you should point element with index of array.
In your case, your code should look something like this,
1. Add to Items a new value i.e "5
UPDATE invoice SET json_data = jsonb_set(json_data, {Items,0}, json_data->'Items' || '"5"', TRUE) where Id =1
2. Update Items value 2 to 9.
UPDATE invoice SET json_data = jsonb_set(json_data, {Items,0}, '"9"') where Id =1
You can check PostgreSQL, JSON Functions and Operators from here.

Query JSON Key:Value Pairs in AWS Athena

I have received a data set from a client that is loaded in AWS S3. The data contains unnamed JSON key:value pairs. This isn't my area of expertise, so I was looking for a little help.
The structure of JSON data that I've typically worked with in the past looks similar to this:
{ "name":"John", "age":30, "car":null }
The data that I have received from my client is formatted as such:
{
"answer_id": "cc006",
"answer": {
"101086": 1,
"101087": 2,
"101089": 2,
"101090": 7,
"101091": 5,
"101092": 3,
"101125": 2
}
}
This is survey data, where the key on the left is a numeric customer identifier, and the value on the right is their response to a survey question, i.e. customer "101125" answered the survey with a value of "2". I need to be able to query the JSON data using Athena such that my result set looks similar to:
Cross joining the unnested children against the parent node isn't an issue. What I can't figure out is how to select all of the keys from the array "answer" without specifying that actual key name. Similarly, I want to be able to select all of the values as well.
Is it possible to create a virtual table in Athena that would allow for these results, or do I need to convert the JSON to a format this looks more similar to the following:
{
"answer_id": "cc006",
"answer": [
{ "key": "101086", "value": 1 },
{ "key": "101087", "value": 2 },
{ "key": "101089", "value": 2 },
{ "key": "101090", "value": 7 },
{ "key": "101091", "value": 5 },
{ "key": "101092", "value": 3 },
{ "key": "101125", "value": 2 }
]
}
EDIT 6/4/2020
I was able to use the code that Theon provided below along with the following table structure:
CREATE EXTERNAL TABLE answer_example (
answer_id string,
answer string
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://mybucket/'
That allowed me to use the following query to generate the results that I needed.
WITH Data AS(
SELECT
answer_id,
CAST(json_extract(answer, '$') AS MAP(VARCHAR, VARCHAR)) as answer
FROM
answer_example
)
SELECT
answer_id,
key,
element_at(answer, key) AS value
FROM
Data
CROSS JOIN UNNEST (map_keys(answer)) AS answer (key)
EDIT 6/5/2020
Taking additional advice from Theon's response below, the following DDL and Query simplify this quite a bit.
DDL:
CREATE EXTERNAL TABLE answer_example (
answer_id string,
answer map<string,string>
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://mybucket/'
Query:
SELECT
answer_id,
key,
element_at(answer, key) AS value
FROM
answer_example
CROSS JOIN UNNEST (map_keys(answer)) AS answer (key)
Cross joining with the keys of the answer property and then picking the corresponding value. Something like this:
WITH data AS (
SELECT
'cc006' AS answer_id,
MAP(
ARRAY['101086', '101087', '101089', '101090', '101091', '101092', '101125'],
ARRAY[1, 2, 2, 7, 5, 3, 2]
) AS answers
)
SELECT
answer_id,
key,
element_at(answers, key) AS value
FROM data
CROSS JOIN UNNEST (map_keys(answers)) AS answer (key)
You could probably do something with transform_keys to create rows of the key value pairs, but the SQL above does the trick.

Resources