query has no destination for result data in a function that has a set of instructions in postgresql - sql

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.

Related

Dynamic query that uses CTE gets "syntax error at end of input"

I have a table that looks like this:
CREATE TABLE label (
hid UUID PRIMARY KEY DEFAULT UUID_GENERATE_V4(),
name TEXT NOT NULL UNIQUE
);
I want to create a function that takes a list of names and inserts multiple rows into the table, ignoring duplicate names, and returns an array of the IDs generated for the rows it inserted.
This works:
CREATE OR REPLACE FUNCTION insert_label(nms TEXT[])
RETURNS UUID[]
AS $$
DECLARE
ids UUID[];
BEGIN
CREATE TEMP TABLE tmp_names(name TEXT);
INSERT INTO tmp_names SELECT UNNEST(nms);
WITH new_names AS (
INSERT INTO label(name)
SELECT tn.name
FROM tmp_names tn
WHERE NOT EXISTS(SELECT 1 FROM label h WHERE h.name = tn.name)
RETURNING hid
)
SELECT ARRAY_AGG(hid) INTO ids
FROM new_names;
DROP TABLE tmp_names;
RETURN ids;
END;
$$ LANGUAGE PLPGSQL;
I have many tables with the exact same columns as the label table, so I would like to have a function that can insert into any of them. I'd like to create a dynamic query to do that. I tried that, but this does not work:
CREATE OR REPLACE FUNCTION insert_label(h_tbl REGCLASS, nms TEXT[])
RETURNS UUID[]
AS $$
DECLARE
ids UUID[];
query_str TEXT;
BEGIN
CREATE TEMP TABLE tmp_names(name TEXT);
INSERT INTO tmp_names SELECT UNNEST(nms);
query_str := FORMAT('WITH new_names AS ( INSERT INTO %1$I(name) SELECT tn.name FROM tmp_names tn WHERE NOT EXISTS(SELECT 1 FROM %1$I h WHERE h.name = tn.name) RETURNING hid)', h_tbl);
EXECUTE query_str;
SELECT ARRAY_AGG(hid) INTO ids FROM new_names;
DROP TABLE tmp_names;
RETURN ids;
END;
$$ LANGUAGE PLPGSQL;
This is the output I get when I run that function:
psql=# select insert_label('label', array['how', 'now', 'brown', 'cow']);
ERROR: syntax error at end of input
LINE 1: ...SELECT 1 FROM label h WHERE h.name = tn.name) RETURNING hid)
^
QUERY: WITH new_names AS ( INSERT INTO label(name) SELECT tn.name FROM tmp_names tn WHERE NOT EXISTS(SELECT 1 FROM label h WHERE h.name = tn.name) RETURNING hid)
CONTEXT: PL/pgSQL function insert_label(regclass,text[]) line 19 at EXECUTE
The query generated by the dynamic SQL looks like it should be exactly the same as the query from static SQL.
I got the function to work by changing the return value from an array of UUIDs to a table of UUIDs and not using CTE:
CREATE OR REPLACE FUNCTION insert_label(h_tbl REGCLASS, nms TEXT[])
RETURNS TABLE (hid UUID)
AS $$
DECLARE
query_str TEXT;
BEGIN
CREATE TEMP TABLE tmp_names(name TEXT);
INSERT INTO tmp_names SELECT UNNEST(nms);
query_str := FORMAT('INSERT INTO %1$I(name) SELECT tn.name FROM tmp_names tn WHERE NOT EXISTS(SELECT 1 FROM %1$I h WHERE h.name = tn.name) RETURNING hid', h_tbl);
RETURN QUERY EXECUTE query_str;
DROP TABLE tmp_names;
RETURN;
END;
$$ LANGUAGE PLPGSQL;
I don't know if one way is better than the other, returning an array of UUIDs or a table of UUIDs, but at least I got it to work one of those ways. Plus, possibly not using a CTE is more efficient, so it may be better to stick with the version that returns a table of UUIDs.
What I would like to know is why the dynamic query did not work when using a CTE. The query it produced looked like it should have worked.
If anyone can let me know what I did wrong, I would appreciate it.
... why the dynamic query did not work when using a CTE. The query it produced looked like it should have worked.
No, it was only the CTE without (required) outer query. (You had SELECT ARRAY_AGG(hid) INTO ids FROM new_names in the static version.)
There are more problems, but just use this query instead:
INSERT INTO label(name)
SELECT unnest(nms)
ON CONFLICT DO NOTHING
RETURNING hid;
label.name is defined UNIQUE NOT NULL, so this simple UPSERT can replace your function insert_label() completely.
It's much simpler and faster. It also defends against possible duplicates from within your input array that you didn't cover, yet. And it's safe under concurrent write load - as opposed to your original, which might run into race conditions. Related:
How to use RETURNING with ON CONFLICT in PostgreSQL?
I would just use the simple query and replace the table name.
But if you still want a dynamic function:
CREATE OR REPLACE FUNCTION insert_label(_tbl regclass, _nms text[])
RETURNS TABLE (hid uuid)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE format(
$$
INSERT INTO %s(name)
SELECT unnest($1)
ON CONFLICT DO NOTHING
RETURNING hid
$$, _tbl)
USING _nms;
END
$func$;
If you don't need an array as result, stick with the set (RETURNS TABLE ...). Simpler.
Pass values (_nms) to EXECUTE in a USING clause.
The tablename (_tbl) is type regclass, so the format specifier %I for format() would be wrong. Use %s instead. See:
Table name as a PostgreSQL function parameter

Return a table when a trigger function is called

I need some_fun() to be executed before a trigger. It will return a table.
I ran this:
INSERT INTO SomeTable(some_bool) VALUES (true);
I expected this:
returnColHeader
------------------
12
23
23
(3 row)
But I got this:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
I've read documentation on TRIGGERS https://www.postgresql.org/docs/current/plpgsql-trigger.html
and also RETURNING CLAUSE https://www.postgresql.org/docs/9.5/dml-returning.html
and a few other postgres related readings but I'm still unable to solve my problem.
DROP TRIGGER IF EXISTS run_some_fun on SomeTable CASCADE;
CREATE TRIGGER run_some_fun
BEFORE INSERT ON SomeTable
FOR EACH ROW WHEN (NEW.some_bool = TRUE)
EXECUTE FUNCTION run_some_fun();
CREATE OR REPLACE FUNCTION run_some_fun()
RETURNS TRIGGER AS $$
BEGIN
SELECT some_fun(NEW.eid); -- This is wrong and throws error
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION some_fun(eID INT)
RETURNS TABLE (returnColHeader INT) AS $$
BEGIN
RETURN QUERY
SELECT eid FROM Joins j1;
END;
$$ LANGUAGE plpgsql;
You cannot to return anything from after trigger. The trigger functions can returns value of composite type, but the returned value from after trigger is ignored. There is not any chance for what you want. And it looks little bit scary.

postgresql: is it possible to run an operation that returns rows without creating a function?

I have some sql structured as follows:
DO $$
DECLARE
foo text := 'thisisfoo';
myId bigint;
BEGIN
myId = (select id from blah.things);
insert into bar.widgets(...)
values (myId, foo, ...);
select * from bar.widgets;
END $$ language plpgsql;
When I run this, I get:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function inline_code_block line 9 at SQL statement
SQL state: 42601
Things I've tried:
- changed the query to to insert into ... returning *; and return select * from bar.widgets. Both yield errors, and it looks like creating a function with a bar.widget return type is the only way forward.
Question: Is there not a way to have a non-function use variables AND return table rows? Reason is I don't want to add this as a function to the schema. It will only be used a few times and I don't want to clutter up the schema.
You could run the select outside the do block:
...
END $$ language plpgsql;
select * from bar.widgets;
Or even rewrite it without a do block:
insert into bar.widgets(...)
values ((select id from blah.things), 'thisisfoo', ...)
returning *;

Input table for PL/pgSQL function

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

PostgreSQL unexpected trigger behavior

The Situation:
I have a function fn_SetFoo() that inserts records into table TableFoo.
I also have a trigger function that runs after each insert into TableFoo. It takes the new primary key TableFooID from the newly inserted row and inserts it into a second table TableFooBar (with a foreign key constraint).
I created a trigger that runs AFTER INSERT ON TableFoo FOR EACH ROW EXECUTE PROCEDURE fn_SetFooBar();
If I call fn_SetFoo() directly then everything works as expected.
However, I have a separate function fn_NormalizeFoo() that processes some data and then calls fn_SetFoo() for each record it has processed.
If I call fn_NormalizeFoo() then the only the first record is processed and the function stops.
The Question:
Why would the process stop after the first record when called from fn_NormalizeFoo() when the entire process runs when the contents of fn_NormalizeFoo() is run directly?
Some Code:
--------------------------------------------------------
-- Insert Into TableFoo --
--------------------------------------------------------
CREATE OR REPLACE FUNCTION "example"."fn_SetFoo" (
IN "Foo1" INTEGER,
IN "Foo2" INTEGER,
IN "Foo3" INTEGER
) RETURNS "void" AS
$$
BEGIN
INSERT INTO
"example"."TableFoo"(
"Foo1",
"Foo2",
"Foo3"
)
VALUES
(
$1,
$2,
$3
);
RETURN;
EXCEPTION WHEN "unique_violation" THEN
-- DO NOTHING
END;
$$
LANGUAGE plpgsql;
--------------------------------------------------------
-- Insert Into TableFooBar --
--------------------------------------------------------
CREATE OR REPLACE FUNCTION "example"."fn_SetFooBar" (
IN "FooPK" INTEGER,
IN "BarPK" INTEGER
) RETURNS "void" AS
$$
BEGIN
INSERT INTO
"example"."TableFooBar"(
"FooPK",
"BarPK"
)
VALUES
(
$1,
$2
);
RETURN;
EXCEPTION WHEN "unique_violation" THEN
-- DO NOTHING
END;
$$
LANGUAGE plpgsql;
--------------------------------------------------------
-- Trigger Function --
--------------------------------------------------------
CREATE OR REPLACE FUNCTION "example"."tr_SetFooBar"() RETURNS TRIGGER AS
$$
BEGIN
PERFORM
"example"."fn_SetFooBar"(
"TableFoo"."FooPK",
"TableBar"."BarPK"
)
FROM
"example"."TableFoo" JOIN
"example"."TableBar" ON [SOMETHING TRUE]
WHERE
NEW.SOMECOLUMN = SOMETHING AND
[MORE STUFF IS TRUE];
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
--------------------------------------------------------
-- Trigger --
--------------------------------------------------------
CREATE TRIGGER "SetFooBar" AFTER INSERT ON "example"."Foo" FOR EACH ROW EXECUTE PROCEDURE "example"."tr_SetFooBar"();
--------------------------------------------------------
-- Normalize Foo --
--------------------------------------------------------
CREATE OR REPLACE FUNCTION "example"."fn_NormaliseFoo" (
IN "Param1" VARCHAR,
IN "Param2" VARCHAR,
IN "Param3" VARCHAR
) RETURNS "void" AS
$$
SELECT
"example"."fn_SetFoo" (
"Foo1",
"Foo2",
"Foo3"
)
FROM
[TABLES]
WHERE
[STUFF IS TRUE]
$$
LANGUAGE SQL;
As you can see, this is a bit more complex then I originally posted. The general idea is to create a many to many relationship as each record is added.
Just to reiterate, running "example"."fn_NormaliseFoo" fails after the first row; however, manually running the contents works as expected.
fn_NormalizeFoo() is described as:
a separate function fn_NormalizeFoo() that processes
some data and then calls fn_SetFoo() for each record it has processed.
However, the code shown for fn_NormaliseFoo is declared in the SQL language so it's not procedural, and therefore it can't do what is claimed. It can just run one query and returns its results (or nothing if no results). Such code has to be compatible with inlining into the calling query.
So the first problem is that fn_NormaliseFoo is declared as returning void but it's not compatible with the fact that it's a SELECT. Normally the function creation shouldn't even be accepted by the interpreter of the sql language.
Example:
CREATE FUNCTION f(int) returns void as 'select $1;' language sql;
This fails with:
ERROR: return type mismatch in function declared to return void
DETAIL: Actual return type is integer.
A second problem would be that fn_SetFoo is likely to be called only once, which is what I understand from the question's "the process stop after the first record". Despite the call being in the select list of a query with some joined tables that presumably produce N rows, there's no reason that can be seen here for the SQL engine to call it N times. Better for it call it only once and affect the same result to every row formed as the output of the query.
It looks a bit like you're using sql in spite of plpgsql. To make sure that a function is call N times, loop N times in procedural code and that will be guaranteed to work.