postgresql, jsonb field, array append via jsonb_set and jsonb_array_length - sql

I have postgresql with jsonb field that always contains array.
I need to append new values to that array or update already existing values by index.
Looks like jsonb_set function meet my requirements. And for append new element i just need to max array index and update element with it.
But i have a trouble doing this. Lets make it step by step.
We have table campaigns with jsonb field team_members.
select id, jsonb_set(team_members, '{0}', '{"name" : "123"}') from campaigns;
id | jsonb_set
-----+-------------------
102 | [{"name": "123"}]
Okay great, if set path '{0}' statically everything works.
Lets do that dynamically
SQL for getting array length (it is our index for append)
select '{' || jsonb_array_length(team_members) || '}'::text from campaigns;
?column?
----------
{0}
Getting all together
select jsonb_set(team_members, '{' || jsonb_array_length(team_members) || '}', '{"name" : "123"}') from campaigns;
ERROR: function jsonb_set(jsonb, text, unknown) does not exist
LINE 1: select jsonb_set(team_members, '{' ||
jsonb_array_length(tea...
^ HINT: No function matches the given name and argument types. You might
need to add explicit type casts.
My question is - how can i get rid of this error ? What i'm doing wrong ?
Thanks in advance.

something like this?..
t=# with jpath as (select concat('{',0,'}')::text[] path) select jsonb_set('[]'::jsonb,path,'{"name": "123"}'::jsonb) from jpath;
jsonb_set
-------------------
[{"name": "123"}]
(1 row)
In your case should be like:
select
jsonb_set(
team_members
, concat('{',jsonb_array_length(team_members),'}')::text[]
, '{"name" : "123"}'
)
from campaigns;

Related

SQL get the value of a nested key in a jsonb field

Let's suppose I have a table my_table with a field named data, of type jsonb, which thus contains a json data structure.
let's suppose that if I run
select id, data from my_table where id=10;
I get
id | data
------------------------------------------------------------------------------------------
10 | {
|"key_1": "value_1" ,
|"key_2": ["value_list_element_1", "value_list_element_2", "value_list_element_3" ],
|"key_3": {
| "key_3_1": "value_3_1",
| "key_3_2": {"key_3_2_1": "value_3_2_1", "key_3_2_2": "value_3_2_2"},
| "key_3_3": "value_3_3"
| }
| }
so in pretty formatting, the content of column data is
{
"key_1": "value_1",
"key_2": [
"value_list_element_1",
"value_list_element_2",
"value_list_element_3"
],
"key_3": {
"key_3_1": "value_3_1",
"key_3_2": {
"key_3_2_1": "value_3_2_1",
"key_3_2_2": "value_3_2_2"
},
"key_3_3": "value_3_3"
}
}
I know that If I want to get directly in a column the value of a key (of "level 1") of the json, I can do it with the ->> operator.
For example, if I want to get the value of key_2, what I do is
select id, data->>'key_2' alias_for_key_2 from my_table where id=10;
which returns
id | alias_for_key_2
------------------------------------------------------------------------------------------
10 |["value_list_element_1", "value_list_element_2", "value_list_element_3" ]
Now let's suppose I want to get the value of key_3_2_1, that is value_3_2_1.
How can I do it?
I have tryed with
select id, data->>'key_3'->>'key_3_2'->>'key_3_2_1' alias_for_key_3_2_1 from my_table where id=10;
but I get
select id, data->>'key_3'->>'key_3_2'->>'key_3_2_1' alias_for_key_3_2_1 from my_table where id=10;
^
HINT: No operators found with name and argument types provided. Types may need to be converted explicitly.
what am I doing wrong?
The problem in the query
select id, data->>'key_3'->>'key_3_2'->>'key_3_2_1' alias_for_key_3_2_1 --this is wrong!
from my_table
where id=10;
was that by using the ->> operand I was turning a json to a string, so that with the next ->> operand I was trying to get a json key object key_3_2 out of a string object, which makes no sense.
Thus one has to use the -> operand, which does not convert json into string, until one gets to the "final" key.
so the query I was looking for was
select id, data->'key_3'->'key_3_2'->>'key_3_2_1' alias_for_key_3_2_1 --final ->> : this gets the value of 'key_3_2_1' as string
from my_table
where id=10;
or either
select id, data->'key_3'->'key_3_2'->'key_3_2_1' alias_for_key_3_2_1 --final -> : this gets the value of 'key_3_2_1' as json / jsonb
from my_table
where id=10;
More info on JSON Functions and Operators can be find here

How to fetch all rows where an array contains any of the fields array elements

I have a table that has a column video_ids, it is of a bigint[] type. I would like to find all the rows that have any of the elements from the array passed in a select statement. So, if I have a row that has a video_ids field that looks like this:
{9529387, 9548200, 9579636}
I would like to fetch it if I pass an array that has any of this video_ids. I thought I would do that with any, but I am not sure how to do this in SQL, I have tried with this:
select id, finished, failed, video_ids, invoiced_video_ids, failed_video_ids
from video_order_execution
where order_ids = any(
'{9548200, 11934626, 9579636, 11936321, 11509698, 11552728, 11592106, 11643565, 11707543, 11810386, 11846268}'
::bigint[]);
I get an error if I do that:
ERROR: operator does not exist: bigint[] = bigint Hint: No operator
matches the given name and argument types. You might need to add
explicit type casts.
How can I make such a statement that would do the job for what I need?
Use the operator && which returns true if the 2 operands have any common items:
select id, finished, failed, video_ids, invoiced_video_ids, failed_video_ids
from video_order_execution
where order_ids &&
'{9548200, 11934626, 9579636, 11936321, 11509698, 11552728, 11592106, 11643565, 11707543, 11810386, 11846268}'::bigint[];

How to get the value of jsonb data using only the key number id?

I have a jsonb column with the following data:
{"oz": "2835", "cup": "229", "jar": "170"}
I have the key number 0 that represents the first item "oz". How can I pull this value using the 0?
I'm thinking something similar to:
SELECT units->[0] as test
I only have the key ID to reference this data. I do not have the key name "oz".
Sounds like a horrible idea. But you can still create a function to implement this horrible idea:
create function jsonb_disaster(jsonb,int) returns jsonb language SQL as $$
select value from jsonb_each($1) with ordinality where ordinality=1+$2
$$;
select jsonb_disaster('{"oz": "2835", "cup": "229", "jar": "170"}',0);
jsonb_disaster
----------------
"2835"
You could also create your own operator to wrap up this disaster:
create operator !> ( function = jsonb_disaster, leftarg=jsonb, rightarg=int);
select '{"cup": "229", "jar": "170", "oz": "2835"}' !> 1;
?column?
----------
"229"

How to add a key value pair in Json type column in Postgres

I have a json type column(Status) in Postgres database(9.4.9). I want to add new key value pair for existing value of status. Example:
Existing Status:
"status": {
"keysterStatus": "In Progress"
}
After Adding Key value pair i want it to look like this
"status": {
"provisioningStatus": "In Progress",
"keysterStatus": "In Progress"
}
I have been using repository save() method as of now to get this done but that is writing whole row and there is chance of concurrent read and write in case of multiple request. So wanted to get rid of save() method and go with column level update.
First of all PG9.4 is obsolette and even unsopperted now. PG9.5 contains as json_set function:
SELECT jsonb_set(status::jsonb,
'{provisioningStatus}',
to_jsonb('In Progress'))::jsonb
FROM ....;
as possibility to use concatenation || swith converting to jsonb and than back:
SELECT (status::jsonb || '{"provisioningStatus": "In Progress"}')::json
FROM ....;
For PG9.4,if you know schema for json, uou can use json_populate_record/row_to_json :
SELECT (
SELECT row_to_json(r)
FROM (
SELECT r.*, 'In Progress' AS provisioningStatus
FROM json_populate_record(null::myrowtype, status) AS r
) AS r
) AS result
FROM ....
Or you can use json_each_text:
SELECT (
SELECT json_object_agg(key, value)
FROM (
SELECT *
FROM json_each_text(status)
UNION ALL
SELECT 'provisioningStatus', 'In Progress'
) AS a
) AS result
FROM ...
And probably the last (but ugly) method is just convert json to string, remove last '}', add "provisioningStatus": "In Progress"}' and convert back to json:
SELECT (substr(status::text, 1, length(status::text) - 1)
|| ', "provisioningStatus": "In Progress"}')::json
FROM ...
UPDATE table_name SET column_name = jsonb_set(cast(column_name as jsonb), '{key}', '"value"', true) WHERE id = 'target_id';
This will add the key value pair in the json column if it doesn't exist already, if the key exist it will override the value of it.

SQL Query to Substitute value in the scanned results and update that field of Table

I have a 1 to many Organization: Users relationship.
I want to fetch the usernames of all User model of an Organization, capture a part of that username and append/substitute it with new value.
Here is how I am doing:
Form the raw SQL to Get the matching usernames and replace them with new value.
raw = "SELECT REGEXP_REPLACE($1::string[], '(^[a-z0-9]+)((\s[a-z0-9]+))*\#([a-z0-9]+)$', m.name[1] || '#' || $2) FROM (SELECT REGEXP_MATCHES($1::string[], '(^[a-z0-9]+)((\s[a-z0-9]+))*\#([a-z0-9]+)$') AS name) m"
Get the matching usernames and replace them with new value.
usernames: list of usernames retrieved from queryable
Repo.query(raw, [usernames, a_string])
Error I am getting
SELECT REGEXP_REPLACE($1::string[], '(^[a-z0-9]+)(( [a-z0-9]+))#([a-z0-9]+)$', m.name[1] || '#' || $2) FROM (SELECT REGEXP_MATCHES($1::string[], '(^[a-z0-9]+)(( [a-z0-9]+))#([a-z0-9]+)$') AS name) m [["tradeboox#trdbx18"], "trdbx17"]
{:error,
%Postgrex.Error{connection_id: 7222, message: nil,
postgres: %{code: :undefined_object, file: "parse_type.c", line: "257",
message: "type \"string[]\" does not exist", pg_code: "42704",
position: "137", routine: "typenameType", severity: "ERROR",
unknown: "ERROR"}}}
FYI: The username field of User model is of type citext
Once I get the replaced values, I want to update the User with something like
update([u], set: [username: new_values])
Any ideas on how to proceed with this?
`
There is no string type in PostgreSQL.
Function regexp_matches accepts as first parameter only text and it can't be array. So what you need to do is first change that type to text, then unnest($1::text[]) your array. Iterate over resulting set of rows with those regexp.
raw = "SELECT REGEXP_REPLACE(m.item, '(^[a-z0-9]+)((\s[a-z0-9]+))*\#([a-z0-9]+)$', m.name[1] || '#' || $2)
FROM (
SELECT item, REGEXP_MATCHES(item, '(^[a-z0-9]+)((\s[a-z0-9]+))*\#([a-z0-9]+)$') AS name
FROM unnest($1::text[]) AS items(item)
) m"
If I understand it correctly, you are trying to replace everything after # with some different string - if that is the case, then your regexp will put anything after spacebar into second element of matches array. You would need this instead: ((^[a-z0-9]+)(\s[a-z0-9]+)*).
If above is true, then you can do all that much easier with this:
SELECT REGEXP_REPLACE(item, '((^[a-z0-9]+)(\s[a-z0-9]+)*)\#([a-z0-9]+)$', '\1' || $2) AS name
FROM unnest($1::text[]) AS items(item)
Best practice however is to simply do replace in UPDATE statement:
UPDATE "User" SET
name = concat(split_part(name, '#', 1), '#', $2)
WHERE organization_id = $3
AND name ~* '^[a-z0-9]+(\s[a-z0-9]+)*\#[a-z0-9]+$'
It will split name by #, take first part, then append # and whatever is assigned to $2 (domain name I guess). It will update only rows that have organization_id matching to some id and have names matching your regexp (you can omit regexp if you want to change all names from organization). Make sure that table in actually named User, case sensitive, or remove double quotes to have case insensitive version.
I sadly do not know how to do this in your ORM.