Return a table when a trigger function is called - sql

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.

Related

Move Data from One table to other table by using trigger in postgreSQL

I have the requirement to move data from one table to another table when the value of one of the columns is updated. And I just want to move the updated row to the new table.
Below is my trigger that I have written. The issue with my code is, that it is moving all the data and not just the row which was updated. Can anyone give a suggestion?
create or replace function moveToAC1ControlHist()
returns trigger as $$
begin if NEW.file_status='CO'
then
insert into ac1_control_hist (file_status,identitifier)
(
select file_status,identitifier
from
ac1_control where new.FILE_STATUS = 'CO'
);
end if;
return new;
end;
$$ language plpgsql;
create TRIGGER AC1_CONTROL_TRIGGER AFTER update of file_status ON AC1_CONTROL
FOR EACH ROW when (new.file_status ='CO')EXECUTE PROCEDURE moveToAC1ControlHist();
I think the logic you want is:
create or replace function moveToAC1ControlHist()
returns trigger as
$$
begin
insert into ac1_control_hist (file_status,identitifier)
values (new.file_status, new.identitifier);
return null;
end;
$$ language plpgsql;
create trigger ac1_control_trigger
after update of file_status on ac1_control
for each row
when (new.file_status ='co')
execute function movetoac1controlhist()
;
Rationale:
you just want to copy (part of) the row being updated, so there is no need to select; you can access the values of the current row with new in a row-level trigger
the trigger definition filters on new file_status that is equal to 'CO', so there is no need for a if construct in the function
this is an after trigger, so you can just return null - the result is discarded anyway

syntax Error in PostgreSQL when I try to create Trigger

I want to create trigger in PostgreSQL.
Logic is very simple.
I need trigger, if published_at updated and written_at is null, set published_at to written_at.
I wrote this one, but it failed. Does anybody have an idea?
CREATE function setWrittenAt() RETURNS trigger;
AS
DECLARE old_id INTEGER;
BEGIN ;
old_id = OLD.id
IF NEW.published_at IS NOT and NEW.written_at IS null
THEN
UPDATE review SET NEW.written_at = NEW.published_at where id = old_id;
END IF ;
RETURN NEW;
END;
LANGUAGE plpgsql;
CREATE TRIGGER update_written_at
AFTER UPDATE OF published_at ON review
WHEN (OLD.published_at IS DISTINCT FROM NEW.published_at)
EXECUTE PROCEDURE setWrittenAt();
Error:
Syntax error: 7 ERROR: syntax error at or near "DECLARE"
LINE 3: DECLARE old_id INTEGER;
There are multiple errors in your code:
IS NOT is not a valid expression you need IS NOT NULL.
After BEGIN and the returns clause there must be no ;
you forgot to enclose the function body as a string (which is easier to write if you use dollar quoting
you also don't need an unnecessary (additional) UPDATE if you make it a before trigger
CREATE function setwrittenat()
RETURNS trigger
AS
$$
BEGIN
IF NEW.published_at IS NOT NULL and NEW.written_at IS null THEN
NEW.written_at := = NEW.published_at; --<< simply assign the value
END IF;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Then use a BEFORE trigger:
CREATE TRIGGER update_written_at
BEFORE UPDATE OF published_at ON review
WHEN (OLD.published_at IS DISTINCT FROM NEW.published_at)
FOR EACH ROW
EXECUTE PROCEDURE setWrittenAt();
this is based on a_horse_with_no_names answer, since it'll throw an error.
ERROR: statement trigger's WHEN condition cannot reference column values
You need to add FOR EACH ROW, else conditional triggers will not function.
If neither is specified, FOR EACH STATEMENT is the default.
Statement-level triggers can also have WHEN conditions, although the feature is not so useful for them since the condition cannot refer to any values in the table.
See here
CREATE TRIGGER update_written_at
BEFORE UPDATE OF published_at ON review
FOR EACH ROW
WHEN (OLD.published_at IS DISTINCT FROM NEW.published_at)
EXECUTE PROCEDURE setWrittenAt();
I can not comment yet, which is why I've posted this as an answer.

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

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.

PostgreSQL Create Trigger which runs a function on every insert or update of a table

I have defined two tables, scores and analyzed_avg_score, in my postgres database. I also have a function which i declaired like that:
CREATE FUNCTION updateAvgScore() RETURNS void AS $$
INSERT into analyzed_avg_score
(SELECT
user,
avg(score_value)
FROM
scores
group by user) on conflict do nothing;
$$ LANGUAGE SQL;
Now, I want to have a trigger or something similar that runs this function every time I insert or update something in score. I don't have a lot of experience with SQL, yet. So, does anyone have an idea how the trigger should look like?
CREATE TRIGGER SCORE_INSERT AFTER INSERT ON SCORE
FOR EACH ROW EXECUTE PROCEDURE updateAvgScore();
/*Have it return a trigger like this */
CREATE OR REPLACE FUNCTION updateAvgScore() RETURNS TRIGGER AS $example_table$
BEGIN
/*YOUR lOGIC HERE*/
END;
$example_table$ LANGUAGE plpgsql;

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