Function returns bad value inside of a trigger - sql

I have two functions that return the good value. But when I call those functions inside of a trigger they always returns 0 instead of the good value.
The return type of those functions is real. The direct and dramatic consequence is that the trigger inserts wrong values in tables when it is called.
The function:
create or replace function get_remaining_hour(id_user_v integer,id_absence_v_type integer,id_year_v integer) returns real as
$BODY$
BEGIN
return (select sum(number_hour)
from remaining_absence_day
where id_user= $1
and id_absence_type=$2
and id_year=$3 );
END;
$BODY$
LANGUAGE 'plpgsql' ;
The trigger function (modified for testing!):
create OR REPLACE function update_absence() returns TRIGGER AS
$BODY$
DECLARE
old_number_hour real;
BEGIN
old_number_hour:=get_remaining_hour(3,2,8);
insert into debugging(col,val) values('old_number_hour', old_number_hour);
return null;
END;
$BODY$
LANGUAGE 'plpgsql' ;
The trigger definition:
drop trigger if exists update_absence on absence;
CREATE TRIGGER update_absence
after update of type,duration_hour,duration_day on absence
for each ROW
execute procedure update_absence();

The presented code should work.
It is particularly odd that you see 0 as result. If no matching row is found in remaining_absence_day, you would see NULL, not 0. But if you call the function with the same parameters in the same environment you should see the same result to begin with.
The remaining possible explanation I can think of: confusion with the schema search path. Like: you have a second instance of the function get_remaining_hour() or the table remaining_absence_day in a different schema. And you call the function with a different setting for search_path.
Did you run your comparison in the same session?
How does the search_path influence identifier resolution and the "current schema"
Or, since you work with an AFTER trigger: there might be other triggers on table absence that modify the table remaining_absence_day, which are fired before your trigger.
All other modifications I made are of cosmetic nature or minor simplifications.
CREATE OR REPLACE FUNCTION get_remaining_hour(id_user_v int
, id_absence_v_type int
, id_year_v int)
RETURNS real AS
$func$
BEGIN
RETURN (
SELECT sum(number_hour)
FROM remaining_absence_day -- referencing the right table? see search_path
WHERE id_user = $1
AND id_absence_type = $2
AND id_year = $3
);
END
$func$ LANGUAGE plpgsql STABLE; -- don't quote the language name
CREATE OR REPLACE FUNCTION update_absence()
RETURNS TRIGGER AS
$func$
BEGIN
INSERT INTO debugging(col, val)
VALUES('old_number_hour', get_remaining_hour(3,2,8)); -- hard coded only for testing?
RETURN null; -- only good for AFTER trigger
END
$func$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS update_absence ON absence;
CREATE TRIGGER update_absence
AFTER UPDATE OF type, duration_hour, duration_day ON absence
FOR EACH ROW EXECUTE PROCEDURE update_absence();

Related

PostgreSQL: LISTEN with insert

I have one notify function and trigger as below
Here I am inserting records before notification listerner_to_insert_code(resVal)
CREATE OR REPLACE FUNCTION public.codes_notify_trigger()
RETURNS trigger AS
$BODY$
DECLARE
resVal text;
BEGIN
resVal := row_to_json(NEW);
select listerner_to_insert_code(resVal);
PERFORM pg_notify('code_channel', resVal::text);
RETURN new;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
CREATE TRIGGER codeInsertTrigger
AFTER INSERT OR UPDATE
ON public.codings
FOR EACH ROW
EXECUTE PROCEDURE public.codes_notify_trigger();
Then I can't see the payloads on
When I tried
LISTEN code_channel
But I can see the payloads if I remove select listerner_to_insert_code(resVal)
Here in below it will show the payloads
CREATE OR REPLACE FUNCTION public.codes_notify_trigger()
RETURNS trigger AS
$BODY$
DECLARE
resVal text;
BEGIN
resVal := row_to_json(NEW);
PERFORM pg_notify('code_channel', resVal::text);
RETURN new;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
But I want to insert record after LISTEN.
How can I achieve this?
You are getting error query has no destination for result data. Check your logs to make sure.
When using SELECT in PL/pgSQL code, you need to do something with returned data, like for example INSERT it into table or variable(s).
If you don't want to do anything with returned data, then use PERFORM instead.
PERFORM listerner_to_insert_code(resVal);

How to send current schema and affected table with notification function/trigger?

When a row is inserted or updated in a specific table (in this example it's the table called 'fpl'). How can I include the affected table and schema in the notification?
SQL as follows:
CREATE TRIGGER fpl_event
AFTER INSERT OR UPDATE ON fpl
FOR EACH ROW
EXECUTE PROCEDURE fpl_notify();
CREATE OR REPLACE FUNCTION fpl_notify()
RETURNS trigger AS $$
BEGIN
NOTIFY dbNotification, 'something got insereted in fpl!';
RETURN NULL;
END;
$$ LANGUAGE PLPGSQL;
Update:
CREATE OR REPLACE FUNCTION fpl_notify() RETURNS trigger
AS
$$
BEGIN
EXECUTE format('notify dbNotification, ''%s''', TG_TABLE_SCHEMA);
RETURN NULL;
END;
$$ LANGUAGE PLPGSQL;
read trigger special variables

How can this approach to solving circular dependencies in triggers (PostgreSQL) be improved?

Let's say I have two tables, for the simplicity, let them be authors and pages. Listing only the interesting attributes like this:
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
active BOOLEAN NOT NULL DEFAULT true, -- set by the author himself
banned BOOLEAN NOT NULL DEFAULT false, -- set to true by the site administrator
visible BOOLEAN, -- set automatically in a trigger and used by the platform
-- when deciding whether to show this author or not.
-- Equals to (active and not banned).
visible_pages INT -- counter also updated automatically
);
CREATE TABLE pages (
id SERIAL PRIMARY KEY,
ref_author INT NOT NULL,
draft BOOLEAN DEFAULT true,
archived BOOLEAN DEFAULT false, -- not draft but not published anymore
visible BOOLEAN -- (authors.visible and not (draft or archived))
);
Now, when author publishes a page or archives it, the counter needs to be updated. When an author gets banned or suspends their account, the pages need to turn invisible. All that is done so that the frontend only cares for the visible flag and doesn't need to waste cycles to calculate it on the fly.
The straightforward (and somewhat naïve) way to make these triggers is as follows (the function names say it all, and trigger creation is omitted for brevity):
CREATE OR REPLACE FUNCTION before_update_authors() RETURNS TRIGGER AS $$
BEGIN
NEW.visible := (NEW.enabled AND NOT NEW.banned);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION after_update_authors() RETURNS TRIGGER AS $$
BEGIN
IF NEW.visible != OLD.visible THEN
UPDATE pages SET id = id WHERE ref_author = NEW.id; -- dummy call just to trigger the trigger
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION before_update_pages() RETURNS TRIGGER AS $$
DECLARE author_visible BOOLEAN;
BEGIN
SELECT visible INTO author_visible FROM authors WHERE id = NEW.ref_author;
NEW.visible := author_visible AND NOT (draft OR archived);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION after_update_pages() RETURNS TRIGGER AS $$
DECLARE counter INT;
BEGIN
IF OLD.visible <> NEW.visible THEN
IF NEW.visible THEN
counter := 1;
ELSE
counter := -1;
END IF;
UPDATE authors SET visible_pages = visible_pages + counter WHERE id = NEW.ref_author;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Looks straightforward, but in the best case it is slow, and in the worst case it would smash the stack and fail.
In MySQL we can get around this unfortunate situation by using #session_variables, so triggers can detect being called from triggers. PostgreSQL does not have such concept.
The best solution I have come up with so far is to use auxiliary columns and WHEN conditions. So my pages table grows an additional column, _updating_author of type BOOLEAN (default whatever but non-NULL).
Now the code for *_update_pages stays the same. The code to create trigger differs, though:
CREATE TRIGGER tg_before_update_pages BEFORE UPDATE ON pages WHEN (OLD._updating_author IS DISTINCT FROM NEW._updating_author) FOR EACH ROW EXECUTE PROCEDURE before_update_pages();
CREATE TRIGGER tg_after_update_pages AFTER UPDATE ON pages WHEN (OLD._updating_author IS DISTINCT FROM NEW._updating_author) FOR EACH ROW EXECUTE PROCEDURE after_update_pages();
And so is trigger function after_update_authors():
CREATE OR REPLACE FUNCTION after_update_authors() RETURNS TRIGGER AS $$
BEGIN
IF NEW.visible != OLD.visible THEN
UPDATE pages SET visible = (NEW.visible AND NOT (draft OR archived)), _updating_authors = NOT _updating_authors WHERE ref_author = NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
In this case, pages visibility is being set in batch and very fast, because no trigger will be called for individual rows in pages.
However, the need for auxiliary columns bothers me a bit in this approach, and WHEN conditions seem a bit clunky. So is the need to state the logic of what pages.visible is twice.
Can you please suggest an alternative, maybe more elegant, approach to solving this and similar problems?

Trigger Not Executing Yet It's Created

I have a trigger function I'm trying to have execute in Postgres.
It compiles and adds the trigger, however it does not insert the value into the table as I had hoped.
The function it uses looks like this:
CREATE OR REPLACE FUNCTION
calc_gnpDifference(n integer, o integer)
RETURNS NUMERIC AS $$
SELECT $1 ::numeric - $2::numeric AS gnpDifference;
$$ LANGUAGE SQL;
And the Trigger part:
CREATE OR REPLACE FUNCTION autoCalculate() RETURNS TRIGGER AS $$
BEGIN
IF NEW.gnp_old > NEW.gnp_old THEN
NEW.gnpDifference := calc_gnpDifference(NEW.gnp_old, NEW.gnp);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER insertDifference ON country;
CREATE TRIGGER insertDifference BEFORE INSERT ON country
FOR EACH ROW EXECUTE PROCEDURE autoCalculate();
However, when I insert data, the trigger does not update the gnpDifference field as I had hoped. Thoughts on why this might be happening?
Obviously this condition: IF NEW.gnp_old > NEW.gnp_old will never be true so the trigger will never have any effect.

Write a PL/pgSQL function so that FOUND is not set when "nothing" is found?

I am just starting out on functions in PostgreSQL, and this is probably pretty basic, but how is this done?
I would like to be able to use the following in a function:
PERFORM id_exists();
IF FOUND THEN
-- Do something
END IF;
where the id_exists() function (to be used with SELECT and PERFORM) is:
CREATE OR REPLACE FUNCTION id_exists() RETURNS int AS $$
DECLARE
my_id int;
BEGIN
SELECT id INTO my_id
FROM tablename LIMIT 1;
RETURN my_id;
END;
$$ LANGUAGE plpgsql;
Currently, even when my_id does not exist in the table, FOUND is true, presumably because a row is still being returned (a null integer)? How can this be re-written so that an integer is returned if found, otherwise nothing at all is?
Your assumption is correct, FOUND is set to TRUE if the last statement returned a row, regardless of the value (may be NULL in your case). Details in the manual here.
Rewrite to, for instance:
IF id_exists() IS NOT NULL THEN
-- Do something
END IF;
Or rewrite the return value of your function with SETOF so it can return multiple rows - or no row! Use RETURN QUERY like I demonstrate. You can use this function in your original setting.
CREATE OR REPLACE FUNCTION id_exists()
RETURNS SETOF int LANGUAGE plpgsql AS
$BODY$
BEGIN
RETURN QUERY
SELECT id
FROM tablename
LIMIT 1;
END;
$BODY$;
Or, even simpler with a language SQL function:
CREATE OR REPLACE FUNCTION id_exists()
RETURNS SETOF int LANGUAGE sql AS
$BODY$
SELECT id
FROM tablename
LIMIT 1;
$BODY$;