How to store values in trigger PostgreSQL - 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;

Related

Why does function i PostgreSQL not raise exception?

I have a before insert or update trigger which is supposed to validate values of two columns in the inserted/updated data, raise an exception if the data are not valid or - if valid - add values to two other columns in the insert/update.
However, when I test with invalid data I get "ERROR: query has no destination for result data. HINT: If you want to discard the results of a SELECT, user PERFORM instead. CONTEXT: PL/pgSQL function before_insert_update() line 3 at SQL statement."
I have tried to read the PostgreSQL documentation and I have googled for explanations, but I just have to admin that my understanding of SQL is not sufficient to make it work.
My function is
CREATE or replace FUNCTION before_insert_update()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
begin
select * from addresses
where addresses.roadname = NEW.roadname and
addresses.housenumber = NEW.housenumber;
if ##ROWCOUNT > 0 then
begin
select addresses.roadcode,
addresses.geom
into NEW.roadcode, NEW.geom
from addresses
where addresses.roadname = NEW.roadname and
addresses.housenumber = NEW.housenumber;
return NEW;
end;
else
declare _adresse text;
begin
_address := concat(NEW.roadname, ' ', NEW.housenumber);
raise exception 'The address does not exist: %', _address
using hint = 'Check the address here: https://danmarksadresser.dk/adresser-i-danmark/';
end;
end if;
END
$BODY$;
The trigger is pretty straight forward
BEFORE INSERT OR UPDATE
ON table
FOR EACH ROW
EXECUTE PROCEDURE before_insert_update();
What is it that I am doing wrong?
There is no need to run a test if the row is present. Just select it, and check the status afterwards:
CREATE or replace FUNCTION before_insert_update()
RETURNS trigger
LANGUAGE plpgsql
COST 100
VOLATILE NOT LEAKPROOF
AS
$BODY$
begin
select addresses.roadcode, addresses.geom
into NEW.roadcode, NEW.geom
from addresses
where addresses.roadname = NEW.roadname and
addresses.housenumber = NEW.housenumber;
if found then
return new;
end if;
raise exception 'The address does not exist: %', concat(NEW.roadname, ' ', NEW.housenumber)
using hint = 'Check the address here: https://danmarksadresser.dk/adresser-i-danmark/';
END
$BODY$;
Looks like you're trying to use SQL Server specific code in Postgres, which isn't going to work of course.
PL/pgSQL doesn't allow SELECTs just anywhere, when the result isn't passed to somehwere, like variables, etc.. And there is no ##ROWCOUNT. From what I can guess what you want to do, you can use EXISTS and your query for the condition of the IF.
CREATE
OR REPLACE FUNCTION before_insert_update()
RETURNS trigger
AS
$$
BEGIN
IF EXISTS(SELECT *
FROM addresses
WHERE addresses.roadname = new.roadname
AND addresses.housenumber = new.housenumber) THEN
SELECT addresses.roadcode,
addresses.geom
INTO new.roadcode,
new.geom
FROM addresses
WHERE addresses.roadname = new.roadname
AND addresses.housenumber = new.housenumber;
RETURN new;
ELSE
RAISE EXCEPTION 'The address does not exist: %', concat(new.roadname, ' ', new.housenumber)
USING HINT = 'Check the address here: https://danmarksadresser.dk/adresser-i-danmark/';
END IF;
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.

Postgres trigger function: substitute value before insert or update

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;

Postgresql Trigger example

I created a trigger when a row updated trigger will change that row. When I update a column I get an error. How can I solve?
ERROR: stack depth limit exceeded
HINT: Increase the configuration parameter "max_stack_depth" (currently 2048kB), after ensuring the platform's stack depth limit is adequate.
CREATE OR REPLACE FUNCTION ynt_call()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'UPDATE' THEN
UPDATE ynt.a_test SET date_time = now();
END IF;
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_call AFTER UPDATE ON ynt.a_test FOR EACH ROW EXECUTE PROCEDURE ynt_call();
Of course you're looping until the end of time, because you are updating row when updating row. You should set the field date_time using the special variable new as following:
CREATE OR REPLACE FUNCTION ynt_call()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'UPDATE' THEN
new.date_time = now();
END IF;
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_call AFTER UPDATE ON ynt.a_test FOR EACH ROW EXECUTE PROCEDURE ynt_call();

Postgresql before update/insert trigger doesn't appear to work

I have the following trigger function:
CREATE OR REPLACE FUNCTION update_modelname_function()
RETURNS trigger AS
$BODY$
BEGIN
IF tg_op = 'INSERT' THEN
new.model_name := upper(new.model_name);
RETURN new;
END IF;
IF tg_op = 'UPDATE' THEN
old.model_name := upper(old.model_name);
RETURN new;
END IF;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
What I'm trying to achieve is for the value of the column model_name to always be uppercased when it's persisted in the table. However nothing seems to happen. Any ideas?
You accidentally updated OLD instead of NEW. Try:
CREATE OR REPLACE FUNCTION update_modelname_function()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
IF TG_OP = 'INSERT' THEN
NEW.model_name := upper(NEW.model_name);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
NEW.model_name := upper(NEW.model_name); -- !
RETURN NEW;
END IF;
END
$func$;
If the example shows the whole code, and the actual trigger(s) only fires on INSERT and/or UPDATE, further simplify:
CREATE OR REPLACE FUNCTION update_modelname_function()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
NEW.model_name := upper(NEW.model_name);
RETURN NEW;
END
$func$;