How to use JSONB_SET for updating JSONB Column in PostgreSQL Procedure - postgresql

I am using a Stored Procedure to update a JSONB column in the table and it does not seem to work. Any help would be much appreciated. Many Thanks.
My Table is:
CREATE TABLE mn_customer (
cust_id int,
f_name varchar(20),
l_name varchar(20),
json_payload jsonb);
My SPROC is:
create or replace procedure update_mn_customer
(
update_cust_id int DEFAULT null,
update_json_payload jsonb DEFAULT null
)
language plpgsql as
$proc$
begin
update mn_customer
set json_payload = jsonb_set(json_payload, update_json_payload)
where cust_id = update_cust_id;
commit;
end
$proc$;
My call to procedure to perform an Update is:
call update_mn_customer(
1998, to_jsonb('{"jsonpayload-fld1": "John Davis", "jsonpayload-fld2": "Lenny Pascoe","jsonpayload-fld3": "Undefined"}'::text)
);
I keep getting the Error:
ERROR: function jsonb_set(jsonb, jsonb) does not exist
LINE 2: set json_payload = jsonb_set(json_payload, update_js...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY: update mn_customer
set json_payload = jsonb_set(json_payload, update_json_payload)
where cust_id = update_cust_id
CONTEXT: PL/pgSQL function update_mn_customer(integer,jsonb) line 8 at SQL statement

You use jsonb_set when you want to replace only one part of the jsonb data identified by a path with a new jsonb value. In your case, it seems that update_json_payload is the new value for the json_payload column so you just have to update it :
update mn_customer
set json_payload = update_json_payload
where cust_id = update_cust_id ;
Then you don't need to commit inside the plpgsql procedure.

Related

Postgresql - Returned type record does not match expected type uuid

I have very simple function that should return few rows including the uuid type as the first column.
Function:
CREATE OR REPLACE FUNCTION public.export_wizard()
RETURNS TABLE(id uuid, my_column text)
LANGUAGE plpgsql
AS $function$
BEGIN
return query select (w.id, w.my_column) from wizard w;
END;
$function$
;
Table (short version):
CREATE TABLE IF NOT EXISTS public.wizard
(
id uuid NOT NULL,
my_column text COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT wizard_pkey PRIMARY KEY (id)
)
After calling the function like this: select * from export_wizard();
I got an error:
SQL Error [42804]: ERROR: structure of query does not match function result type
Detail: Returned type record does not match expected type uuid in column 1.
Where: PL/pgSQL function export_wizard() line 3 at RETURN QUERY
Thanks for any advices.
I made terrible mistake by adding (no idea why) brackets in SELECT statement as mentioned by #Adrian Klaver.
Correct: return query select w.id, w.my_column from wizard w;

"missing FROM-clause entry" in PLPGSQL function to update a table

I am trying to update a table in Postgres, but I get below error.
Userid is the primary key.
CREATE OR REPLACE FUNCTION public.update_user(username character varying,
userid character varying,
roleid integer,
deptid integer,
status integer)
RETURNS integer AS
$$
BEGIN
UPDATE public."M_User" SET public."M_User".user_name = public.update_user.username,
public."M_User".user_role_id = public.update_user.roleid,
public."M_User".dept_id = public.update_user.deptid,
public."M_User".status = public.update_user.status
WHERE public."M_User".user_id = userid;
RETURN 0;
END
$$
LANGUAGE 'plpgsql';
Call:
SELECT * FROM public.update_user ('ji','gis', 123, 24, 1);
Error:
ERROR: missing FROM-clause entry for table "update_user"
LINE 1: ...E public."M_User" SET public."M_User".user_name = public.upd...
^
QUERY: UPDATE public."M_User" SET public."M_User".user_name = public.update_user.username,
public."M_User".user_role_id = public.update_user.roleid,
public."M_User".dept_id = public.update_user.deptid,
public."M_User".status = public.update_user.status
WHERE public."M_User".user_id = userid
CONTEXT: PL/pgSQL function update_user(character varying,character varying,integer,integer,integer) line 4 at SQL
statement
SQL state: 42P01
How to resolve the issue?
CREATE OR REPLACE FUNCTION public.update_user(_username varchar,
_userid varchar,
_roleid integer,
_deptid integer,
_status integer)
RETURNS integer
LANGUAGE sql AS
$func$
UPDATE public."M_User" u
SET user_name = _username
, user_role_id = _roleid
, dept_id = _deptid
, status = _status
WHERE u.user_id = _userid
AND (u.user_name, u.user_role_id, u.dept_id, u.status) IS DISTINCT FROM
( _username , _roleid , _deptid , _status) -- optional addition
RETURNING u.user_id;
$func$
You can qualify function parameters with the function name to disambiguate, but that's typically an awkward solution. Breaks when renaming the function. There are other ways. See:
PL/pgSQL column name the same as variable
I like to prefix parameters and variables with an underscore, and never do the same for column names. Not standard, but a widespread convention. Make it a habit to table-qualify all column names, and you are safe. A table alias helps to keep the noise down. Basics in the manual on UPDATE.
But target columns in an UPDATE statement cannot be qualified. (They are never ambiguous.)
I added an optional predicate to skip the UPDATE if it wouldn't change anything. See:
How do I (or can I) SELECT DISTINCT on multiple columns?
You do not need PL/pgSQL for this. (Though you can, of course.) A simpler SQL function does the job. See:
Difference between language sql and language plpgsql in PostgreSQL functions
RETURN 0 does nothing useful. I changed it to return the effective user_id of the updated row. (Typically the same as the input _userid, but it might differ with triggers.) The important difference: you only get the return value if a row is actually updated, making it useful. Else, you might as well declare the function as RETURNS void. Just as useful as an unconditional RETURN 0;, but cheaper.
The doc says one can refer to an input as function_name.parameter_name, so try removing references to the schema_name
UPDATE public."M_User"
SET user_name = update_user.username,
user_role_id = update_user.roleid,
dept_id = update_user.deptid,
status = update_user.status
WHERE user_id = update_user.userid;

Using parameter as column name in Postgres function

I have a Postgres table bearing the following form
CREATE TABLE "public"."days"
(
"id" integer NOT NULL,
"day" character varying(9) NOT NULL,
"visits" bigint[] NOT NULL,
"passes" bigint[] NOT NULL
);
I would like to write a function that allows me to return the visits or the passees column as its result for a specified id. My first attempt goes as follows
CREATE OR REPLACE FUNCTION day_entries(INT,TEXT) RETURNS BIGINT[] LANGUAGE sql AS
'SELECT $2 FROM days WHERE id = $1;'
which fails with an error along the lines of
return type mismatch in function declared to return bigint[]
DETAIL: Actual return type is text.
If I put in visits in place of the $2 things work just as expected. It would make little sense to define several functions to match different columns from the days table. Is there a way to pass the actual column name as a parameter while still keeping Postgres happy?
You can't use parameters as identifiers (=column name), you need dynamic SQL for that. And that requires PL/pgSQL:
CREATE OR REPLACE FUNCTION day_entries(p_id int, p_column text)
RETURNS BIGINT[]
AS
$$
declare
l_result bigint[];
begin
execute format('SELECT %I FROM days WHERE id = $1', p_column)
using p_id
into l_result;
return l_result;
end;
$$
LANGUAGE plpgsql;
format() properly deals with identifiers when building dynamic SQL. The $1 is a parameter placeholder and the value for that is passed with the using p_id clause of the execute statement.

Postgres: inserting dynamic number of columns and values in table

I am very new to Postgres SQL. My requirement is to pass a dynamic number of columnId, columnValue pair and insert this combination in a table(Example: employeeId, employeeName combination). The length of list can be anything. I am thinking of building dynamic query at the code-side and pass it as string to function and execute the statement. Is there any better approach for this problem. Any example or idea will be much appreciated.
If you are allowed to pass that information as a structured JSON value, this gets quite easy. Postgres has a feature to map a JSON value to a table type using the function json_populate_record
Sample table:
create table some_table
(
id integer primary key,
some_name text,
some_date date,
some_number integer
);
The insert function:
create function do_insert(p_data text)
returns void
as
$$
insert into some_table (id, some_name, some_date, some_number)
select (json_populate_record(null::some_table, p_data::json)).*;
$$
language sql;
Then you can use:
select do_insert('{"id": 42, "some_name": "Arthur"}');
select do_insert('{"id": 1, "some_value": 42}');
Note that columns that are not part of the passed JSON string are explicitly set to NULL using this approach.
If the passed string contains column names that do not exist, they are simply ignored, so
select do_insert('{"id": 12, "some_name": "Arthur", "not_there": 123}');
will ignore the not_there "column".
Online example: https://rextester.com/JNIBL25827
Edit
A similar approach can be used for updating:
create function do_update(p_data text)
returns void
as
$$
update some_table
set id = t.id,
some_name = t.some_name,
some_date = t.some_date,
some_number = t.some_number
from json_populate_record(null::some_table, p_data::json) as t;
$$
language sql;
or using insert on conflict to cover both use cases with one function:
create function do_upsert(p_data text)
returns void
as
$$
insert into some_table (id, some_name, some_date, some_number)
select (json_populate_record(null::some_table, p_data::json)).*
on conflict (id) do update
set id = excluded.id,
some_name = excluded.some_name,
some_date = excluded.some_date,
some_number = excluded.some_number
$$
language sql;

Trying to use CAST on a string to varchar after SET in UPDATE statement - Postgres

I'm trying to cast a string to a varchar from user input on a web application. I want to change the value of one of the columns given a certain ID (Primary Key) and the column name is what I'm casting as the varchar.
CREATE OR REPLACE FUNCTION changeQuantities(productID varchar, warehouseID int, change int)
RETURNS void AS $$
BEGIN
EXECUTE format('UPDATE warehouses SET CAST(%I AS VARCHAR) = %s WHERE warehouseID = %s', productID, change, warehouseID)
USING change, warehouseID;
END;
$$ LANGUAGE plpgsql;
The 'productID' is the column name, 'change' is the new value, and 'warehouseID' is the primary key for the table. 'warehouses' is the table. Here is the error I receive:
SELECT changeQuantities('bg412',1,100);
ERROR: syntax error at or near "CAST"
LINE 1: UPDATE warehouses SET CAST(bg412 AS VARCHAR) = 100 WHERE war...
^
QUERY: UPDATE warehouses SET CAST(bg412 AS VARCHAR) = 100 WHERE warehouseID = 1
CONTEXT: PL/pgSQL function changequantities(character varying,integer,integer) line 3 at EXECUTE statement
I have another function just like it that uses a SELECT statement while casting the column name and it works just fine. Can I just not cast something after SET? I haven't found anything on this particular case, so I'm either going to be humiliated or I will help someone else out with similar issues. Thanks for any help.
You can't have a cast() on the left side of the assignment - and you don't need it, as the data type of a column is known. If at all you would need to cast the right hand side of an assignment to the data type of the left hand side.
Assuming that bg412 is a column name, you need:
CREATE OR REPLACE FUNCTION changeQuantities(productID varchar, warehouseID int, change int)
RETURNS void AS $$
BEGIN
EXECUTE format('UPDATE warehouses SET %I = %s WHERE warehouseID = %s', productID, change, warehouseID)
USING change, warehouseID;
END;
$$ LANGUAGE plpgsql;
Unrelated, but: using the the ID of a product as a column name in a table seems like a horrible design. What do you do if you have a million products?