I created a PL/pgSQL function in PostgreSQL 10.4 using pgAdmin. It returns a query and updates entries in the same table. Called in pgAdmin, it runs as expected. When run from external code, the table is returned, but the updates never run. Wondering if this has to do with how I wrote the function?
CREATE OR REPLACE FUNCTION report()
RETURNS TABLE(id text, t1 text, t2 text)
LANGUAGE 'plpsql'
AS $BODY$
DECLARE
rec RECORD;
BEGIN
RETURN QUERY
SELECT id,
name AS t1,
data AS t2
FROM table1
WHERE status IS NULL;
IF FOUND THEN
FOR rec IN SELECT id
FROM table1
WHERE status IS NULL
LOOP
UPDATE table1 SET val=val+1
WHERE id=rec.id;
END IF;
RETURN;
END
$BODY$;
EDIT:
Thanks for the replies. Don't have anyone else to help take a look, so this was very helpful with troubleshooting what was going on.
Was so fixated on my PL/pgSQL function as having the issue, I overlooked my external program running the function. User error on my part. After moving my UPDATE above my SELECT, I noticed my program was seeing the UPDATE. I was forgetting to COMMIT the updates back to the database in my external program, so the table was never updating. Added a commit and good to.
It seems like you can replace the whole function with this plain, much cheaper UPDATE query and a RETURNING clause:
UPDATE table1
SET val = val + 1
WHERE status IS NULL
RETURNING id, name AS t1, data AS t2;
If there can be race conditions, consider:
Postgres UPDATE … LIMIT 1
You can run this query as is, without function wrapper. If you want a function wrapper, prepend RETURN QUERY to return results directly. Code example:
PostgreSQL: ERROR: 42601: a column definition list is required for functions returning "record"
Related
I am trying to automate a set of sentences that I execute several times a day. For this I want to put them in a postgres function and just call the function to execute the sentences consecutively. If everything runs OK then in the end return the SUCCESS value. The following function replicates my idea and the error I am getting when executing the function:
CREATE OR REPLACE FUNCTION createTable() RETURNS int AS $$
BEGIN
DROP TABLE IF EXISTS MY_TABLE;
CREATE TABLE MY_TABLE
(
ID integer
)
WITH (
OIDS=FALSE
);
insert into MY_TABLE values(1);
select * from MY_TABLE;
RETURN 'SUCCESS';
END;
$$ LANGUAGE plpgsql;
Invocation:
select * from createTable();
With my ignorance of postgresql I would expect to obtain the SUCCESS value as a return (If everything runs without errors). But the returned message causes me confusion, isn't it the same as a function in any other programming language? When executing the function I get the following message:
query has no destination for result data Hint: If you want to
discard the results of a SELECT, use PERFORM instead.
query has no destination for result data Hint: If you want to discard the results of a SELECT, use PERFORM instead.
You are getting this error because you do not assign the results to any variable in the function. In a function, you would typically do something like this instead:
select * into var1 from MY_TABLE;
Therefore, your function would look something like this:
CREATE OR REPLACE FUNCTION createTable() RETURNS int AS $$
DECLARE
var1 my_table%ROWTYPE;
BEGIN
DROP TABLE IF EXISTS MY_TABLE;
CREATE TABLE MY_TABLE
(
ID integer
)
WITH (
OIDS=FALSE
);
insert into MY_TABLE values(1);
select * into var1 from MY_TABLE;
<do something with var1>
RETURN 'SUCCESS';
END;
$$ LANGUAGE plpgsql;
Otherwise, if you don't put the results into a variable, then you're likely hoping to achieve some side effect (like advancing a sequence or firing a trigger somehow). In that case, plpgsql expects you to use PERFORM instead of SELECT
Also, BTW your function RETURNS int but at the bottom of your definition you RETURN 'SUCCESS'. SUCCESS is a text type, not an int, so you will eventually get this error once you get past that first error message -- be sure to change it as necessary.
I discovered a mysterious table named num in my database which has one column named count. I had no idea how it got there, then I realized it might be caused by a misbehaving trigger.
I have a trigger function:
DECLARE num integer := 0;
BEGIN
IF ... THEN
SELECT COUNT(*) INTO num FROM ...
END IF;
IF num > 1 THEN
DELETE FROM ...
END IF;
RETURN NEW;
END;
As you can see my purpose is to count the rows returned by a query and perform some operation if it is greater than one.
Can this faulty code be responsible for the unwanted table created? If so, how to fix this?
SELECT ... INTO foo in PL/pgSQL stores the result of the SELECT in a PL/pgSQL variable foo. Whereas SELECT ... INTO foo run as an ordinary SQL statement creates a table foo to store the result.
This is what caused the confusion, the table was created when I was testing the SQL statements from the trigger function manually against the DB.
I have found this very interesting article: Refactor a PL/pgSQL function to return the output of various SELECT queries
from Erwin Brandstetter which describes how to return all columns of various tables with only one function:
CREATE OR REPLACE FUNCTION data_of(_table_name anyelement, _where_part text)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT * FROM ' || pg_typeof(_table_name)::text || ' WHERE ' || _where_part;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM data_of(NULL::tablename,'1=1 LIMIT 1');
This works pretty well. I need a very similar solution but for getting data from a table on a different database via dblink. That means the call NULL::tablename will fail since the table does not exists on the database where the call is made. I wonder how to make this work. Any try to connect inside of the function via dblink to a different database failed to get the result of NULL::tablename. It seems the polymorph function needs a polymorph parameter which creates the return type of the function implicit.
I would appreciate very much if anybody could help me.
Thanks a lot
Kind regards
Brian
it seems this request is more difficult to explain than I thought it is. Here is a second try with a test setup:
Database 1
First we create a test table with some data on database 1:
CREATE TABLE db1_test
(
id integer NOT NULL,
txt text
)
WITH (
OIDS=TRUE
);
INSERT INTO db1_test (id, txt) VALUES(1,'one');
INSERT INTO db1_test (id, txt) VALUES(2,'two');
INSERT INTO db1_test (id, txt) VALUES(3,'three');
Now we create the polymorph function on database 1:
-- create a polymorph function with a polymorph parameter "_table_name" on database 1
-- the return type is set implicit by calling the function "data_of" with the parameter "NULL::[tablename]" and a where part
CREATE OR REPLACE FUNCTION data_of(_table_name anyelement, _where_part text)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT * FROM ' || pg_typeof(_table_name)::text || ' WHERE ' || _where_part;
END
$func$ LANGUAGE plpgsql;
Now we make test call if everything works as aspected on database 1
SELECT * FROM data_of(NULL::db1_test, 'id=2');
It works. Please notice I do NOT specify any columns of the table db1_test. Now we switch over to database 2.
Database 2
Here I need to make exactly the same call to data_of from database 1 as before and although WITHOUT knowing the columns of the selected table at call time. Unfortunatly this is not gonna work, the only call which works is something like that:
SELECT
*
FROM dblink('dbname=[database1] port=[port] user=[user] password=[password]'::text, 'SELECT * FROM data_of(NULL::db1_test, \'id=2\')'::text)
t1(id integer, txt text);
Conclusion
This call works, but as you can see, I need to specify at least once how all the columns look like from the table I want to select. I am looking for any way to bypass this and make it possible to make a call WITHOUT knowing all of the columns from the table on database 1.
Final goal
My final goal is to create a function in database 2 which looks like
SELECT * from data_of_dblink('table_name','where_part')
and which calls internaly data_of() on database1 to make it possible to select a table on a different database with a where part as parameter. It should work like a static view but with the possiblity to pass a where part as parameter.
I am extremly open for suggestions.
Thanks a lot
Brian
I would like to use a plpgsql function with a table and several columns as input parameter. The idea is to split the table in chunks and do something with each part.
I tried the following function:
CREATE OR REPLACE FUNCTION my_func(Integer)
RETURNS SETOF my_part
AS $$
DECLARE
out my_part;
BEGIN
FOR i IN 0..$1 LOOP
FOR out IN
SELECT * FROM my_func2(SELECT * FROM table1 WHERE id = i)
LOOP
RETURN NEXT out;
END LOOP;
END LOOP;
RETURN;
END;
$$
LANGUAGE plpgsql;
my_func2() is the function that does some work on each smaller part.
CREATE or REPLACE FUNCTION my_func2(table1)
RETURNS SETOF my_part2 AS
$$
BEGIN
RETURN QUERY
SELECT * FROM table1;
END
$$
LANGUAGE plpgsql;
If I run:
SELECT * FROM my_func(99);
I guess I should receive the first 99 IDs processed for each id.
But it says there is an error for the following line:
SELECT * FROM my_func2(select * from table1 where id = i)
The error is:
The subquery is only allowed to return one column
Why does this happen? Is there an easy way to fix this?
There are multiple misconceptions here. Study the basics before you try advanced magic.
Postgres does not have "table variables". You can only pass 1 column or row at a time to a function. Use a temporary table or a refcursor (like commented by #Daniel) to pass a whole table. The syntax is invalid in multiple places, so it's unclear whether that's what you are actually trying.
Even if it is: it would probably be better to process one row at a time or rethink your approach and use a set-based operation (plain SQL) instead of passing cursors.
The data types my_part and my_part2 are undefined in your question. May be a shortcoming of the question or a problem in the test case.
You seem to expect that the table name table1 in the function body of my_func2() refers to the function parameter of the same (type!) name, but this is fundamentally wrong in at least two ways:
You can only pass values. A table name is an identifier, not a value. You would need to build a query string dynamically and execute it with EXECUTE in a plpgsql function. Try a search, many related answers her on SO. Then again, that may also not be what you wanted.
table1 in CREATE or REPLACE FUNCTION my_func2(table1) is a type name, not a parameter name. It means your function expects a value of the type table1. Obviously, you have a table of the same name, so it's supposed to be the associated row type.
The RETURN type of my_func2() must match what you actually return. Since you are returning SELECT * FROM table1, make that RETURNS SETOF table1.
It can just be a simple SQL function.
All of that put together:
CREATE or REPLACE FUNCTION my_func2(_row table1)
RETURNS SETOF table1 AS
'SELECT ($1).*' LANGUAGE sql;
Note the parentheses, which are essential for decomposing a row type. Per documentation:
The parentheses are required here to show that compositecol is a column name not a table name
But there is more ...
Don't use out as variable name, it's a keyword of the CREATE FUNCTION statement.
The syntax of your main query my_func() is more like psudo-code. Too much doesn't add up.
Proof of concept
Demo table:
CREATE TABLE table1(table1_id serial PRIMARY KEY, txt text);
INSERT INTO table1(txt) VALUES ('a'),('b'),('c'),('d'),('e'),('f'),('g');
Helper function:
CREATE or REPLACE FUNCTION my_func2(_row table1)
RETURNS SETOF table1 AS
'SELECT ($1).*' LANGUAGE sql;
Main function:
CREATE OR REPLACE FUNCTION my_func(int)
RETURNS SETOF table1 AS
$func$
DECLARE
rec table1;
BEGIN
FOR i IN 0..$1 LOOP
FOR rec IN
SELECT * FROM table1 WHERE table1_id = i
LOOP
RETURN QUERY
SELECT * FROM my_func2(rec);
END LOOP;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM my_func(99);
SQL Fiddle.
But it's really just a a proof of concept. Nothing useful, yet.
As the error log is telling you.. you can return only one column in a subquery, so you have to change it to
SELECT my_func2(SELECT Specific_column_you_need FROM hasval WHERE wid = i)
a possible solution can be that you pass to funct2 the primary key of the table your funct2 needs and then you can obtain the whole table by making the SELECT * inside the function
I am relatively new to postgresql and battling my way to get familiarized with it. I had run in to an error while writing a new pl/sql function. ERROR: type "ordered_parts" does not exist
CREATE OR REPLACE FUNCTION get_ordered_parts(var_bill_to integer)
RETURNS SETOF ordered_parts AS
$BODY$
declare
var_ordered_id record;
var_part ordered_parts;
begin
for var_ordered in select order_id from view_orders where bill_to = var_bill_to
loop
for var_part select os.po_num,os.received,os.customer_note,orders.part_num,orders.description,orders.order_id,orders.remaining_quantity from (select vli.part_num,vli.description,vli.order_id,vli.quantity - vli.quantity_shipped as remaining_quantity from view_line_items as vli where vli.order_id in (select order_id from view_orders where bill_to = var_bill_to and order_id = var_ordered.order_id) and vli.quantity - vli.quantity_shipped > 0)as orders left join order_sales as os on orders.order_id = os.order_id
then
-- Then we've found a leaf part
return next var_part;
end if;
end loop;
end;
$BODY$
LANGUAGE 'plpgsql' VOLATILE
COST 100
ROWS 1000;
ALTER FUNCTION get_ordered_parts(integer) OWNER TO postgres;
just note - your code is perfect example how don't write stored procedure ever. For some longer results it can be extremely slow. Minimally two cycles can be joined to one, or better, you can use just only one RETURN QUERY statement. Next issue is zero formatting of embedded SQL - good length of line is between 70 and 100 chars - writing long SQL statement to one line going to zero readability and maintainability code.
Relation database is not array, and any query has some cost, so don't use nested FOR if you really don't need it. I am sorry for offtopic.
The error message is telling you that you have declared the return type of your function to be SETOF ordered_parts, but it doesn't know what kind of thing ordered_parts is. Within your Declare block you also have a variable declared as this same type (var_part ordered_parts).
If you had a table or view called ordered_parts, then its "row type" would be automatically created as a type, but this is not the case here. if you just want to use an arbitrary row from a result set, you can just use the generic type record.
So in this case your function should say RETURNS SETOF record, and your Declare block var_part record.
Bonus tip: rather than looping over the result of your query and running RETURN NEXT on each row, you can use RETURN QUERY to throw the whole result set into the returned set in one go. See this Postgres manual page.