Postgres trigger function: substitute value before insert or update - postgresql

I've looked up pretty much everything I could find regarding this issue, but I still don't understand what is wrong with this trigger:
CREATE OR REPLACE FUNCTION func_SubstitutePostLatLng_Upt()
RETURNS trigger AS
$BODY$
BEGIN
IF OLD.post_latlng IS NULL AND NEW.post_latlng IS NULL AND NEW.place_guid IS NOT NULL THEN
raise notice 'SELECT';
SELECT place.geom_center, place.city_guid
INTO NEW.post_latlng, NEW.city_guid
FROM public.place
WHERE (place.origin_id, place.place_guid) IN (VALUES (NEW.origin_id,NEW.place_guid));
raise notice 'Value db_geom: %', NEW.post_latlng;
raise notice 'Value db_city_guid: %', NEW.city_guid;
IF NEW.post_latlng IS NOT NULL THEN
NEW.post_geoaccuracy = 'place';
IF NEW.city_guid IS NOT NULL THEN
SELECT country_guid INTO NEW.country_guid
FROM public.city WHERE (origin_id, city_guid) IN (VALUES (NEW.origin_id,NEW.city_guid));
END IF;
END IF;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
DROP TRIGGER IF EXISTS trig_SubstitutePostLatLng_Upd on public.post;
CREATE TRIGGER trig_SubstitutePostLatLng_Upd
BEFORE UPDATE
ON public.post
FOR EACH ROW
WHEN (pg_trigger_depth() < 1)
EXECUTE PROCEDURE func_SubstitutePostLatLng_Upt()
(I have a second similar trigger for insert)
The code is supposed to do the following:
On Update on table "post", check if no post_latlng is submitted (=NULL), and if yes, substitute post_latlng from table place (geom_center), if available.
However, no matter what I do, I get the following when updating an entry in table "post" (=triggering the above trigger):
NOTICE: SELECT
NOTICE: Value db_geom: <NULL>
NOTICE: Value db_city_guid: <NULL>
INSERT 0 1
Query returned successfully in 47 msec.
The test-data for place_guid, geom_center etc. is definitely available and both
raise notice 'Value db_geom: %', NEW.post_latlng;
raise notice 'Value db_city_guid: %', NEW.city_guid;
should not output NULL.

There were several smaller issues, it now works. Here is a more cleaner code that uses variables in between:
CREATE OR REPLACE FUNCTION func_SubstitutePostLatLng_Upt()
RETURNS trigger AS
$BODY$
DECLARE
db_geom_center text;
db_city_guid text;
db_country_guid text;
BEGIN
IF OLD.post_latlng IS NULL AND NEW.post_latlng IS NULL AND NEW.place_guid IS NOT NULL THEN
SELECT place.geom_center, place.city_guid
INTO db_geom_center, db_city_guid
FROM public.place
WHERE (place.origin_id, place.place_guid) IN (VALUES (NEW.origin_id,NEW.place_guid));
IF db_geom_center IS NOT NULL THEN
NEW.post_latlng = db_geom_center;
NEW.post_geoaccuracy = 'place';
END IF;
IF db_city_guid IS NOT NULL THEN
NEW.city_guid = db_city_guid;
SELECT city.country_guid
INTO db_country_guid
FROM public.city
WHERE (city.origin_id, city.city_guid) IN (VALUES (NEW.origin_id,db_city_guid));
NEW.country_guid = db_country_guid;
END IF;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

Related

plpgsql trigger shows value as exists when is not

My tables are
CREATE TABLE historia_alumno(
period varchar(5) NOT NULL,
no_control varchar(9) NOT NULL,
mater varchar(7) NOT NULL,
calif int
primary key(no_control,mater)
);
as unique condition; also
CREATE TABLE seleccion_mater(
period varchar(5) NOT NULL,
no_control varchar(9) NOT NULL,
mater varchar(7) NOT NULL
primary key(period,no_control,mater)
);
with unique condition.
If ever looks similar, has different scenarios; my trigger function was created with pgadmin4 and is as follows:
CREATE OR REPLACE FUNCTION alta_materia()
RETURNS trigger
LANGUAGE 'plpgsql'
VOLATILE
COST 100
AS $BODY$
BEGIN
IF EXISTS(SELECT 1 FROM historia_alumno HA WHERE HA.no_control=NEW.no_control AND HA.mater=NEW.mater AND HA.calif>=70)THEN
RAISE EXCEPTION 'Student has approved previously assignment';
ROLLBACK;
RETURN NULL;
ELSE
IF EXISTS(SELECT 1 FROM seleccion_mater SM WHERE SM.period=NEW.period AND SM.no_control=NEW.no_control AND SM.mater=NEW.mater)THEN
RAISE EXCEPTION 'Student has assignment in their list of to_do ';
ROLLBACK;
RETURN NULL;
END IF;
RETURN NEW;
END IF;
END;
$BODY$;
This function is activated before insert in table seleccion_mater.
WITHOUT insert any value, and as a check point, i've done the follow (in pgadmin4)
do $$
BEGIN
IF EXISTS(SELECT 1 FROM seleccion_mater SM WHERE SM.period='20221' and SM.no_control='17760218' AND SM.materi='05ISC04') THEN
RAISE NOTICE 'Exists';
ELSE
RAISE NOTICE 'Not exists';
END IF;
END $$;
Output is "Not exists", and that is correct because the table is empty; but, if i now do
INSERT INTO seleccion_mater(period,no_control,mater) VALUES('20221','17760218','05ISC04');
Result is
Error: Student has assignment in their list of to_do
When value is not present in the table.
I've also change the trigger function as follow
DECLARE
yeap integer:=0;
SELECT COUNT(*) INTO yeap FROM seleccion_mater SM WHERE SM.period=NEW.period AND SM.no_control=NEW.no_control AND SM.mater=NEW.mater;
IF yeap>0 THEN
RAISE EXCEPTION 'Student has assignment in their to_do list %',yeap;
And then, trying to do insert again with previously values as before, the output is now
Student has assignment in their to_do list 1
I don't known why is counting a value the trigger function, when is not present.

function not returning any value and not raising exception

The below function neither returning the value nor raising the exception.
create or replace function get_custid(p_customerNum varchar2)
RETURNS text AS $$
DECLARE
cust_id customer.customer_num%TYPE;
begin
raise notice '%', message_text;
select customer_num into cust_id
from customer
where customer_num = p_customerNum;
return cust_id;
exception
when OTHERS then
raise notice '%', message_text;
raise;
end $$ language plpgsql;
select get_custid('Ab12345') from dual;
-- the customer number is existed but not returning any rows.
select get_custid('DDDDDDD') from dual;
-- the customer number is not existed but not going to exception block
I think that is you really use postgresql this code is more likely what you need (or at list it's running...)
create table customer (cust_id int, customer_num varchar);
insert into customer values (1, 'Ab12345');
drop function get_custid(varchar);
create or replace function get_custid(p_customerNum varchar)
RETURNS int AS $$
DECLARE
out_cust_id int;
begin
--raise notice '%', message_text;
select cust_id into out_cust_id
from customer
where customer_num = p_customerNum;
if out_cust_id is null
then raise exception 'your exception';
end if;
return out_cust_id;
end $$ language plpgsql;
select get_custid('Ab12345');
In PL/pgSQL, SELECT INTO only throws an exception on the wrong number of rows if STRICT is specified.
create or replace function get_custid(p_customerNum varchar)
RETURNS text AS $$
DECLARE
cust_id customer.customer_num%TYPE;
begin
raise notice '%', 'message_text';
select customer_num into strict cust_id
from customer
where customer_num = p_customerNum;
return cust_id;
exception
when OTHERS then
raise notice '%', 'message_text';
raise;
end $$ language plpgsql;

Check if a `RECORD` variable is initialized

I'm attempting to declare a variable with the data type record but am having troubles when there are no results to populate it with. Below is a simplified model of the function:
CREATE OR REPLACE FUNCTION do_something()
RETURNS TRIGGER AS $$
DECLARE
engagement record;
BEGIN
SELECT code
INTO engagement
FROM "mainEngagement" me
WHERE me.value = NEW.id;
IF TG_OP = 'INSERT'
THEN
INSERT INTO engagement_details (code)
values (
case
when engagement is not null
then exists(select code from engagement where "value" = 'expected')
else false
end
);
ELSE
UPDATE engagement_details
SET
code = case
when engagement is not null
then exists(select 1 from engagement where "value" = 'expected')
else false
end
WHERE engagement_details.assessment_id = NEW.id;
END IF;
RAISE NOTICE 'Updated engagement_details: [%]', NEW;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
When the trigger runs and there is no data in "mainEngagement" I'm getting an error in the INSERT:
ERROR: relation "engagement" does not exist
I cannot find a way to test if engagement is populated or not. I've searched through many stack overflow questions but most people just recommend doing engagement := row(null) but then when engagement is not null still doesn't work for me.
Use IF NOT FOUND:
SELECT code
INTO engagement
FROM "mainEngagement" me
WHERE me.value = NEW.id;
IF NOT FOUND
THEN
RAISE WARNING '|W| do_something(): engagement undefined'; -- report warning
RETURN; -- exit early, function cannot proceed
END IF;
The issue I was having here was that a RECORD entry can only have one row as the result. I was attempting to return a value with multiple rows and then run the CASE statement over the record, which will never work. Instead do the following:
CREATE OR REPLACE FUNCTION do_something()
RETURNS TRIGGER AS $$
DECLARE
engagement boolean;
BEGIN
SELECT EXISTS (
SELECT 1 FROM engagement WHERE "value" = 'expected'
) INTO engagement;
IF TG_OP = 'INSERT'
THEN
INSERT INTO engagement_details (code)
values (engagement);
ELSE
UPDATE engagement_details
SET
code = engagement
WHERE engagement_details.assessment_id = NEW.id;
END IF;
RAISE NOTICE 'Updated engagement_details: [%]', NEW;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Much simpler, cleaner, and works.

how to create triggers function before insert show test message

i have a table
CREATE TABLE test.emp (
empname text,
salary integer,
last_date timestamp,
last_user text
);
and function
CREATE FUNCTION test.emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname;
END IF;
-- Who works for us when she must pay for it?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
END IF;
-- Remember who changed the payroll when
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
how to show message no need to update the values how can i modify my function
trigger is
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON test.emp
FOR EACH ROW EXECUTE PROCEDURE test.emp_stamp();
I think all you need to do is 1) make sure it's an update and not an insert (otherwise the reference to old won't make any sense and 2) compare to the existing record. Since Employee name seems to be the PK and the last two fields are only to capture changes, my guess is you only want to test the salary:
-- Who works for us when she must pay for it?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
elsif TG_OP = 'UPDATE' and new.salary = old.salary then
RAISE EXCEPTION 'salary is already %', NEW.salary;
END IF;
But of course you could also use this for any field. If you wanted to make sure at least one changed, it would be something like this:
elsif TG_OP = 'UPDATE' and
new.salary = old.salary and
new.last_date = old.last_date and
new.last_user = old.last_user then
RAISE EXCEPTION 'Update does not alter any data';
END IF;

How to store values in trigger PostgreSQL

I have a trigger that looks something like this:
CREATE OR REPLACE FUNCTION CHECK_SCHEDULE()
RETURNS TRIGGER AS
$BODY$
BEGIN
IF EXISTS(
SELECT DAY, TIME FROM MEETING
WHERE NEW.DAY = MEETING.DAY AND NEW.TIME > MEETING.TIME
) THEN
RAISE EXCEPTION 'THERE IS A MEETING HAPPENING ON % % ', NEW.DAY, NEW.TIME;
ELSE
RETURN NEW;
END IF;
END;
$BODY$ LANGUAGE PLPGSQL;
This works fine except I want the message to be the time it's conflicting with: There is a meeting happening on MEETING.DAY and MEETING.TIME.
However I cannot do this because it doesn't know what these variables are. Is it possible to store the values in my select clause so I can use them later?
You can move the day and time into a declared variable (e.g. a RECORD) for reference later.
CREATE OR REPLACE FUNCTION CHECK_SCHEDULE()
RETURNS TRIGGER AS
$BODY$
DECLARE
meetinginfo RECORD;
BEGIN
SELECT meeting.day, meeting.time
INTO meetinginfo
FROM meeting
WHERE new.day = meeting.day
AND new.time > meeting.time
ORDER BY new.time
LIMIT 1;
IF FOUND THEN
RAISE EXCEPTION 'THERE IS A MEETING HAPPENING ON % %', meetinginfo.day, meetinginfo.time;
END IF;
RETURN NEW;
END;
$BODY$ LANGUAGE plpgsql;