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

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;

Related

Setting return table data type be the same type as another table

I have a postgres table function currently declared as such:
CREATE OR REPLACE FUNCTION apolloqa.my_func(arguments...)
RETURNS TABLE(first_name text, last_name text, age int)
LANGUAGE plpgsql AS $function$
BEGIN
RETURN QUERY
select first_name, last_name, age
from person_table ;
END
$function$ ;
When I run this code, postgres complains that the first_name, and last_name in the return table does not match the query's return type. That is true. But how do I declare first_name and last_name so that it either matches the query's return type or the underlying person_table's column type without repeating the same type? Is there a way to say something like:
RETURNS TABLE(first_name TYPE is person_table.first_name, ... ) ?
Postgres has a 'like' feature, but it selects all columns from a given table. I want to select just a few from one table, and a few from another. My solutions in the past would be to hard code the datatype from the underlying table, so varchar(150), or something. But, I'd like to have the type reference another type, if that's possible.
Yes, you can do almost exactly you indicated, the syntax is just a little different. Use
returns table(first_name person_table.first_name%type
,last_name person_table.last_name%type
,age int
);
Since your function has just SQL you can also define it as an SQL function:
create or replace function my_func(arguments text)
returns table(first_name person_table.first_name%type
,last_name person_table.last_name%type
,age int
)
language sql as $function$
select first_name, last_name, age
from person_table ;
$function$ ;
You can use Copying type
By using %TYPE you don't need to know the data type of the structure
you are referencing, and most importantly, if the data type of the
referenced item changes in the future (for instance: you change the
type of user_id from integer to real), you might not need to change
your function definition.
https://www.postgresql.org/docs/current/plpgsql-declarations.html#PLPGSQL-DECLARATION-TYPE
your function INPUT and OUTPUT parameter name the same with query body column name. That may lead to some mistake in the future. See DB fiddle: the last code block
CREATE OR REPLACE FUNCTION apolloqa.my_func(p_age person_table.age%type)
RETURNS TABLE(_first_name person_table.first_name%type,
_last_name person_table.last_name%type,
_age person_table.age%type)
LANGUAGE plpgsql AS $function$
BEGIN
RETURN QUERY
select first_name, last_name, age from person_table where age = p_age;
END
$function$;

How to use JSONB_SET for updating JSONB Column in PostgreSQL Procedure

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.

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.

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?

How to clone a RECORD in PostgreSQL

I want to loop through a query, but also retain the actual record for the next loop, so I can compare two adjacent rows.
CREATE OR REPLACE FUNCTION public.test ()
RETURNS void AS
$body$
DECLARE
previous RECORD;
actual RECORD;
query TEXT;
isdistinct BOOLEAN;
tablename VARCHAR;
columnname VARCHAR;
firstrow BOOLEAN DEFAULT TRUE;
BEGIN
tablename = 'naplo.esemeny';
columnname = 'esemeny_id';
query = 'SELECT * FROM ' || tablename || ' LIMIT 2';
FOR actual IN EXECUTE query LOOP
--do stuff
--save previous record
IF NOT firstrow THEN
EXECUTE 'SELECT ($1).' || columnname || ' IS DISTINCT FROM ($2).' || columnname
INTO isdistinct USING previous, actual;
RAISE NOTICE 'previous: %', previous.esemeny_id;
RAISE NOTICE 'actual: %', actual.esemeny_id;
RAISE NOTICE 'isdistinct: %', isdistinct;
ELSE
firstrow = false;
END IF;
previous = actual;
END LOOP;
RETURN;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100;
The table:
CREATE TABLE naplo.esemeny (
esemeny_id SERIAL,
felhasznalo_id VARCHAR DEFAULT "current_user"() NOT NULL,
kotesszam VARCHAR(10),
idegen_azonosito INTEGER,
esemenytipus_id VARCHAR(10),
letrehozva TIMESTAMP WITHOUT TIME ZONE DEFAULT now() NOT NULL,
szoveg VARCHAR,
munkalap_id VARCHAR(13),
ajanlat_id INTEGER,
CONSTRAINT esemeny_pkey PRIMARY KEY(esemeny_id),
CONSTRAINT esemeny_fk_esemenytipus FOREIGN KEY (esemenytipus_id)
REFERENCES naplo.esemenytipus(esemenytipus_id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
NOT DEFERRABLE
)
WITH (oids = true);
The code above doesn't work, the following error message is thrown:
ERROR: could not identify column "esemeny_id" in record data type
LINE 1: SELECT ($1).esemeny_id IS DISTINCT FROM ($2).esemeny_id
^
QUERY: SELECT ($1).esemeny_id IS DISTINCT FROM ($2).esemeny_id
CONTEXT: PL/pgSQL function "test" line 18 at EXECUTE statement
LOG: duration: 0.000 ms statement: SET DateStyle TO 'ISO'
What am I missing?
Disclaimer: I know the code doesn't make too much sense, I only created so I can demonstrate the problem.
This does not directly answer your question, and may be of no use at all, since you did not really describe your end goal.
If the end goal is to be able to compare the value of a column in the current row with the value of the same column in the previous row, then you might be much better off using a windowing query:
SELECT actual, previous
FROM (
SELECT mycolumn AS actual,
lag(mycolumn) OVER () AS previous
FROM mytable
ORDER BY somecriteria
) as q
WHERE previous IS NOT NULL
AND actual IS DISTINCT FROM previous
This example prints the rows where the current row is different from the previous row.
Note that I added an ORDER BY clause - it does not make sense to talk about "the previous row" without specifying ordering, otherwise you would get random results.
This is plain SQL, not PlPgSQL, but if you can wrap it in a function if you want to dynamically generate the query.
I am pretty sure, there is a better solution for your actual problem. But to answer the question asked, here is a solution with polymorphic types:
The main problem is that you need well known composite types to work with. the structure of anonymous records is undefined until assigned.
CREATE OR REPLACE FUNCTION public.test (actual anyelement, _col text
, OUT previous anyelement) AS
$func$
DECLARE
isdistinct bool;
BEGIN
FOR actual IN
EXECUTE format('SELECT * FROM %s LIMIT 3', pg_typeof(actual))
LOOP
EXECUTE format('SELECT ($1).%1$I IS DISTINCT FROM ($2).%1$I', _col)
INTO isdistinct
USING previous, actual;
RAISE NOTICE 'previous: %; actual: %; isdistinct: %'
, previous, actual, isdistinct;
previous := actual;
END LOOP;
previous := NULL; -- reset dummy output (optional)
END
$func$ LANGUAGE plpgsql;
Call:
SELECT public.test(NULL::naplo.esemeny, 'esemeny_id')
I am abusing an OUT parameter, since it's not possible to declare additional variables with a polymorphic composite type (at least I have failed repeatedly).
If your column name is stable you can replace the second EXECUTE with a simple expression.
I am running out of time, explanation in these related answers:
Declare variable of composite type in PostgreSQL using %TYPE
Refactor a PL/pgSQL function to return the output of various SELECT queries
Asides:
Don't quote the language name, it's an identifier, not a string.
Do you really need WITH (oids = true) in your table? This is still allowed, but largely deprecated in modern Postgres.