plpgsql trigger shows value as exists when is not - postgresql

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.

Related

Error Message: Control reached end of procedure without RETURN

I have a nice table in POSTGRES:
CREATE TABLE public.pasajeros
(
direccion_residencia character varying COLLATE pg_catalog."default",
nombre character varying COLLATE pg_catalog."default",
pasajero_id integer NOT NULL DEFAULT nextval('pasajeros_pasajero_id_seq'::regclass),
fecha_nacimiento date,
CONSTRAINT pasajeros_pkey PRIMARY KEY (pasajero_id)
)
I tried to add a trigger every time a new pasajero is inserted, a table stores the new value of total registers, so I created a new table:
CREATE TABLE public.cont_pasajeros
(
total integer,
tiempo time with time zone,
id_t integer NOT NULL DEFAULT nextval('cont_pasajeros_id_t_seq'::regclass),
CONSTRAINT cont_pasajeros_pkey PRIMARY KEY (id_t)
)
Then I created a new function to be includen in the trigger:
DECLARE
count_ integer :=0;
BEGIN
count_ := (SELECT count (*) FROM pasajeros);
RAISE NOTICE 'number of registers %', count_;
INSERT INTO cont_pasajeros (total,tiempo) VALUES (count_, now());
END
Then I created the trigger 'in perse':
CREATE TRIGGER trigger_
AFTER INSERT ON pasajeros
FOR EACH ROW
EXECUTE PROCEDURE my_func();
The problem occured when I tried to add a new tuple into the table 'pasajeros':
INSERT INTO public.pasajeros(
direccion_residencia, nombre, fecha_nacimiento)
VALUES ('calle 1 a', 'benito', '2000-05-01');
a error occurred:
Error Message: Control reached end of procedure without RETURN
What I did wrong? apparently everything is normal. I am using pgAdmin4
This is the solution I found: I modified the code of the function to include the sstatement RETURN NULL.
I drop the function:
DROP FUNCTION public.my_func();
execute the new code, to include the sentence RETURN NULL
CREATE FUNCTION public.my_func()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
DECLARE
count_ integer :=0;
BEGIN
count_ := (SELECT count (*) FROM pasajeros);
RAISE NOTICE 'number of registers %', count_;
INSERT INTO cont_pasajeros (total,tiempo) VALUES (count_, now());
RETURN NULL; -- added to fix the error
END
$BODY$;
ALTER FUNCTION public.my_func()
OWNER TO postgres;

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;

inserting record from one table to another using record in postgres for loop

I'm trying to insert data from one table to another in postgres using for...loop. The approach is given below.
DO LANGUAGE PLPGSQL $$
DECLARE
data record;
BEGIN
FOR data IN SELECT * FROM forall_data
LOOP
INSERT INTO for_loop values data;<br>
END LOOP;
END;
$$
I've used record for the row iteration but couldn't find out how to insert that 'data' into 'for_loop' table. When I run this code it gives me the following error:
ERROR: syntax error at or near "data"
LINE 9: INSERT INTO for_loop values data;
^
Here are my two tables.
create table forall_data(
nid numeric(15,0)not null,
name varchar(15) not null,
city varchar(10) not null,
contact numeric(11,0) not null
);
create table for_loop(
nid numeric(15,0)not null,
name varchar(15) not null,
city varchar(10) not null,
contact numeric(11,0) not null
);
What should I try here to insert that 'data' record into 'for_loop' table? Thanks in advance.
'data' is untyped record, so I have to mention the column name to retrieve the value of this record.
DO LANGUAGE PLPGSQL $$
DECLARE
data record;
BEGIN
FOR data IN SELECT * FROM forall_data
LOOP
INSERT INTO for_loop values (data.nid,data.name,data.city,data.contact);
END LOOP;
END;
$$
But using %rowtype or table type is more flexible and no need to mention the column names to retrieve column value from the variable
DO LANGUAGE PLPGSQL $$
DECLARE
data forall_data; --- or data forall_data%rowtype
BEGIN
FOR data IN SELECT * FROM forall_data
LOOP
INSERT INTO for_loop select (data).*;
END LOOP;
END;
$$
cheers :)
use this code:
DO LANGUAGE PLPGSQL $$
DECLARE
rec record;
BEGIN
FOR rec IN SELECT * FROM budzet.forall_data
LOOP
INSERT INTO budzet.for_loop(nid, name , city , contact)
VALUES (rec.nid, rec.name , rec.city , rec.contact);
END LOOP;
END;
$$
You can try Loop with some exit condition.
DO LANGUAGE PLPGSQL $$
DECLARE
rec CURSOR FOR SELECT * FROM forall_data;
V_nid numeric;
V_name varchar(15);
V_city varchar(10);
V_contact numeric;
BEGIN
OPEN rec;
LOOP
FETCH rec INTO V_nid ,V_name ,V_city,V_contact;
EXIT WHEN(rec IS NULL);
INSERT INTO for_loop(nid, name , city , contact)
VALUES (V_nid , V_name , V_city , V_contact);
END LOOP;
CLOSE rec;
END;
$$
Hope it work for you.
EDIT: Alternately you can try this without using loop insert statement from one table and select statement from another table.
INSERT INTO for_loop(nid, name , city , contact)
select nid, name , city , contact FROM forall_data;

multi table auditing trigger function

I want to keep all changes of my tables. I have a working solution for making a trigger per table, but it seems silly to copy the code foreach table. Is there any way to create a single trigger function that does this?
Example of my working per-table trigger (including table definitions):
CREATE TABLE departments (
id bigserial Primary Key,
name varchar not null,
created bigint not null default date_part('epoch', NOW()),
created_by bigint references Employees (id) not null
);
create table Departments_hist ("action" varchar not null, change_date bigint not null, rev bigserial not null, like Departments);
CREATE OR REPLACE FUNCTION add_to_history_Departments() RETURNS TRIGGER AS $$
BEGIN
IF(TG_OP='INSERT' OR TG_OP='UPDATE') THEN
INSERT INTO Departments_hist values (TG_OP,date_part('epoch', NOW()),DEFAULT,NEW.*);
END IF;
IF (TG_OP='DELETE') THEN
INSERT INTO Departments_hist values (TG_OP,date_part('epoch', NOW()),DEFAULT,OLD.*);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_history_Departments AFTER INSERT OR UPDATE OR DELETE ON Departments FOR EACH ROW EXECUTE PROCEDURE add_to_history_Departments();
I've tried to make it multi-table by concatenating '_hist' to TG_TABLE_NAME:
CREATE OR REPLACE FUNCTION add_to_hist_table() RETURNS TRIGGER AS $$
DECLARE
histTable text :=TG_TABLE_NAME || '_hist';
BEGIN
IF (TG_OP='INSERT' OR TG_OP='UPDATE') THEN
INSERT INTO histTable values (TG_OP,date_part('epoch', NOW()),DEFAULT,NEW.*);
ELSIF TG_OP='DELETE' THEN
INSERT INTO histTable values (TG_OP,date_part('epoch', NOW()),DEFAULT,OLD.*);
END IF;
RETURN null; --ignored since it is an AFTER triggger.
END;
$$ LANGUAGE plpgsql;
But I get an error:
ERROR: syntax error at or near "$1"
LINE 1: INSERT INTO $1 values ( $2 ,date_part('epoch', NOW()),DEFA...
^
QUERY: INSERT INTO $1 values ( $2 ,date_part('epoch', NOW()),DEFAULT, $3 .*)
CONTEXT: SQL statement in PL/PgSQL function "add_to_hist_table" near line 5
I guess it is a problem with variable substitution ( http://www.postgresql.org/docs/8.4/static/plpgsql-implementation.html ).
How can this functionality be achieved?
PS. I'm using postgresql 8.4 but will likely upgrade to 9.3 soon.
I found a solution on this "related question" https://stackoverflow.com/a/1997417/844731
I didn't think of doing 'EXECUTE USING' with NEW and OLD. So now a working solution is:
CREATE OR REPLACE FUNCTION add_to_hist_table() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP='INSERT' OR TG_OP='UPDATE') THEN
execute 'INSERT INTO '|| TG_TABLE_NAME ||'_hist values (''' || TG_OP || ''',date_part(''epoch'', NOW()),DEFAULT,$1.*)' using NEW;
ELSIF TG_OP='DELETE' THEN
execute 'INSERT INTO '|| TG_TABLE_NAME ||'_hist values (''' || TG_OP || ''',date_part(''epoch'', NOW()),DEFAULT,$1.*)' using OLD;
END IF;
RETURN null; --ignored since it is an AFTER triggger.
END;
$$ LANGUAGE plpgsql;
#Pascal_dher, somebody can create table with name with containing attacker code. Due Postgresql this probably dosnt do some really bad, only failed queries. But if your trigger will be more complex, then impacts can be much more worse.

PostgreSQL Inheritanced Table Always Return Row With Null Value

I am referring to http://www.if-not-true-then-false.com/2009/11/howto-create-postgresql-table-partitioning-part-1/
To reproduce the problem, here is some simple steps to follow :
(1) create database named "tutorial"
(2) perform the following SQL query :
CREATE TABLE impressions_by_day (
advertiser_id SERIAL NOT NULL,
day DATE NOT NULL DEFAULT CURRENT_DATE,
impressions INTEGER NOT NULL,
PRIMARY KEY (advertiser_id, day)
);
CREATE OR REPLACE FUNCTION insert_table()
RETURNS void AS
$BODY$DECLARE
_impressions_by_day impressions_by_day;
BEGIN
INSERT INTO impressions_by_day(impressions ) VALUES(888) RETURNING * INTO _impressions_by_day;
RAISE NOTICE 'After insert, the returned advertiser_id is %', _impressions_by_day.advertiser_id;
END;$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION insert_table() OWNER TO postgres;
(3) create database named "tutorial_partition"
(4) perform the following SQL query :
CREATE TABLE impressions_by_day (
advertiser_id SERIAL NOT NULL,
day DATE NOT NULL DEFAULT CURRENT_DATE,
impressions INTEGER NOT NULL,
PRIMARY KEY (advertiser_id, day)
);
CREATE OR REPLACE FUNCTION insert_table()
RETURNS void AS
$BODY$DECLARE
_impressions_by_day impressions_by_day;
BEGIN
INSERT INTO impressions_by_day(impressions ) VALUES(888) RETURNING * INTO _impressions_by_day;
RAISE NOTICE 'After insert, the returned advertiser_id is %', _impressions_by_day.advertiser_id;
END;$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION insert_table() OWNER TO postgres;
CREATE TABLE impressions_by_day_y2010m1ms2 (
PRIMARY KEY (advertiser_id, day),
CHECK ( day >= DATE '2010-01-01' AND day < DATE '2010-03-01' )
) INHERITS (impressions_by_day);
CREATE INDEX impressions_by_day_y2010m1ms2_index ON impressions_by_day_y2010m1ms2 (day);
CREATE OR REPLACE FUNCTION impressions_by_day_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF ( NEW.day >= DATE '2010-01-01' AND NEW.day < DATE '2010-03-01' ) THEN
INSERT INTO impressions_by_day_y2010m1ms2 VALUES (NEW.*);
ELSE
RAISE EXCEPTION 'Date out of range. Something wrong with the impressions_by_day_insert_trigger() function!';
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER insert_impressions_by_day_trigger
BEFORE INSERT ON impressions_by_day
FOR EACH ROW EXECUTE PROCEDURE impressions_by_day_insert_trigger();
(5) execute
SELECT * FROM insert_table() on tutorial
We get
NOTICE: After insert, the returned advertiser_id is 1
(6) execute
SELECT * FROM insert_table() on tutorial_partition
We get
NOTICE: After insert, the returned advertiser_id is
How is it possible to get advertiser_id is 1 too, in tutorial_partition?
The trigger is passing back NULL, which indicates to the INSERT that no action is to be performed. Since no action is performed, the RETURNING * clause returns nothing. You're not going to be able to intercept (and override) the INSERT and use RETURNING in the same operation and get anything meaningful.