Create a history trigger? - postgresql

I create 2 tables with postgresql:
afp
(
id bigint NOT NULL DEFAULT nextval('afp_id_seq'::regclass),
perm_number character varying(90),
type bigint,
start_date character varying(50),
CONSTRAINT afp_pkey PRIMARY KEY (id)
)
history_afp
(
id bigint NOT NULL DEFAULT nextval('history_afp_id_seq'::regclass),
reason bigint,
type bigint,
ask_date character varying(50),
CONSTRAINT history_afp_pkey PRIMARY KEY (id)
)
I wanted to have an history trigger so when I will delete a row in afp the row will go in history_afp
CREATE FUNCTION public.before_update()
RETURNS TRIGGER
SET SCHEMA 'public'
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO public.history_afp (
type,
ask_date
)
VALUES (
OLD.type,
OLD.start_date);
RETURN NEW;
END;
$$;
CREATE TRIGGER before_update_trigger
BEFORE DELETE
ON public.afp
FOR EACH ROW
EXECUTE PROCEDURE public.before_update();
When I delete the row in table afp, it creates my new row in history_afp but it creates again the row in my table afp.
Someone have any idea why I have this error ?

You should
RETURN OLD;
rather than
RETURN NEW;
The problem is that NEW is undefined in delete triggers, and returning NULL will abort the deletion.

Related

Conditionally set 'GENERATED ALWAYS AS IDENTITY' if ID is not provided on insert

I have a the following DDL and would like to generate an ID on insert to the table, but ONLY if the ID has not been provided already.
drop table if exists table1;
create table table1 (
pk_id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
other_id INT GENERATED ALWAYS AS IDENTITY -- DO THIS ONLY IF NULL
)
I was thinking of using a stored procedure to do this and have attempted a start with the below code, but have been unable to get it working:
create or replace function conditionally_generated_identity(other_id integer)
returns table (id integer) as $$
begin
if other_id is null then
return query other_id generated always as identity;
elsif other_id is not null then
return query other_id;
end if;
end;
$$ language plpgsql;

CREATE TRIGGER for two tables in Postgres

CREATE TRIGGER for two tables in Postgres
I am using Postgres. I have two tables here in my database, payroll and staff_absences. staff_absences is a link table to a payroll table that logs what absence the staff has and to calculate in payroll table.
The staff absent is calculated from start_date - end_date by a trigger in the staff_absences table. I'm trying to input absences_no into payroll by a trigger.
The trigger is accepted by the schema and I can insert data into staff_absences table. However, it gives me this error when I try to update payroll table.
{ERROR: missing FROM-clause entry for table "staff_absences"
LINE 1: SELECT NEW.staff_absences.start_date - NEW.staff_absences.en...
^
QUERY: SELECT NEW.staff_absences.start_date - NEW.staff_absences.end_date
CONTEXT: PL/pgSQL function lowlands_db.pay_trgr() line 4 at assignment}
These are the create scripts for payroll and staff_absences:
CREATE TABLE lowlands_db.payroll
(
payroll_id integer NOT NULL,
annual_salary numeric NOT NULL,
appointment_bonus numeric,
absences_id integer NOT NULL,
staff_id integer NOT NULL,
total_monthly_salary numeric,
monthly_salary numeric,
absence_no numeric,
tax numeric,
CONSTRAINT payroll_pkey PRIMARY KEY (payroll_id),
CONSTRAINT payroll_staff_id_fkey FOREIGN KEY (staff_id)
REFERENCES lowlands_db.staff (staff_id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE NO ACTION
CREATE TABLE lowlands_db.staff_absences
(
absences_id integer NOT NULL,
start_date date NOT NULL,
end_date date,
cause_of_absences text COLLATE pg_catalog."default" NOT NULL,
staff_id integer NOT NULL,
absen_no numeric,
CONSTRAINT staff_absences_pkey PRIMARY KEY (absences_id),
CONSTRAINT staff_absences_staff_id_fkey FOREIGN KEY (staff_id)
REFERENCES lowlands_db.staff (staff_id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE NO ACTION
CREATE FUNCTION lowlands_db.asb_pay_trg()
RETURNS trigger
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
UPDATE lowlands_db.payroll
SET lowlands_db.payroll.absence_no = new.staff_absences.absen_no
WHERE lowlands_db.staff_absences.absences_id = NEW.lowlands_db.payroll.absences_id;
RETURN NEW;
END;
$BODY$;
CREATE FUNCTION lowlands_db.pay_trgr()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
BEGIN
NEW.absences_no = NEW.staff_absences.start_date - NEW.staff_absences.end_date;
NEW.monthly_salary = ROUND(NEW.annual_salary /12 );
NEW.tax = ROUND(NEW.monthly_salary * .2);
NEW.total_monthly_salary = ROUND((NEW.monthly_salary - NEW.tax)+ NEW.appointment_bonus);
RETURN NEW;
END;
$BODY$;
your help in advance.

ERROR: record "old" is not assigned yet

I'm having difficulties in this simple trigger. My purpose is to verify before inserting a new register if is there's a register with the same field content which is "tag_id". If NEW tag_id is the same tag_id of any register on my table "coordenadas", then it updates, if not, it inserts a new one. When I try to insert sth, I get the error:
ERROR: record "old" is not assigned yet
DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
CONTEXT: PL/pgSQL function verifica_coo() line 7 at IF
I have this table:
CREATE TABLE public.coordenadas
(
id bigint NOT NULL,
pos_data timestamp without time zone,
pos_latitude double precision,
pos_longitude double precision,
tag_id bigint NOT NULL,
gado_id bigint NOT NULL,
CONSTRAINT coordenadas_pkey PRIMARY KEY (id),
CONSTRAINT coordenadas_gado_id_fkey FOREIGN KEY (gado_id)
REFERENCES public.gado (gado_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fkj14dwmpa6g037ardymqc2q4lj FOREIGN KEY (tag_id)
REFERENCES public.tag (tag_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fktawrw6tlliq4ace5p7io87c5p FOREIGN KEY (gado_id)
REFERENCES public.gado (gado_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
This trigger:
CREATE TRIGGER verifica_coo
BEFORE INSERT OR UPDATE ON coordenadas
FOR EACH ROW EXECUTE PROCEDURE verifica_coo();
This function:
CREATE OR REPLACE FUNCTION public.verifica_coo()
RETURNS trigger AS $verifica_coo$
BEGIN
--
-- Verifica se é para inserir ou atualizar os dados na tabela.
--
IF (NEW.tag_id != OLD.tag_id ) THEN
INSERT INTO coordenadas (pos_data,pos_latitude,pos_longitude,tag_id,gado_id)
VALUES (NEW.pos_data,NEW.pos_latitude,NEW.pos_longitude,NEW.tag_id,NEW.gado_id);
ELSE
UPDATE coordenadas SET pos_data = NEW.pos_data, pos_latitude = NEW.pos_latitude, pos_longitude = NEW.pos_longitude WHERE tag_id = NEW.tag_id;
END IF;
RETURN NEW;
END;
$verifica_coo$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION public.verifica_coo()
OWNER TO postgres;
My insert:
INSERT INTO coordenadas (pos_data,pos_latitude,pos_longitude,tag_id,gado_id) VALUES ('21/08/2016', '-23.563844' ,'-46.322525', '2','2');
This is because:
OLD
Data type RECORD; variable holding the old database row for
UPDATE/DELETE operations in row-level triggers. This variable is
unassigned in statement-level triggers and for INSERT operations.
So you first need to check if you are doing an insert or update. That information is available in TG_OP
IF TG_OP = 'UPDATE' THEN
-- some code involving OLD
ELSE
-- other code

Trigger insert into another table only if unique value

I have a trigger function that copy row of unique values to another table on update or insert that ALMOST work.
The trigger should only insert a new row to the sample table if the number don't exist in it before. Atm. it insert a new row to the sample table with the value NULL if the number already exist in the table. I dont want it to do anything if maintbl.number = sample.nb_main
EDIT: sample table and sample data
CREATE TABLE schema.main(
sid SERIAL NOT NULL,
number INTEGER,
CONSTRAINT sid_pk PRIMARY KEY (sid)
)
CREATE TABLE schema.sample(
gid SERIAL NOT NULL,
nb_main INTEGER,
CONSTRAINT gid_pk PRIMARY KEY (gid)
Example and desired result
schema.main schema.sample
number nb_main
234233 234233
234234 555555
234234
555555
555555
CREATE OR REPLACE FUNCTION schema.update_number()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO schema.sample(
nb_main)
SELECT DISTINCT(maintbl.number)
FROM schema.maintbl
WHERE NOT EXISTS (
SELECT nb_main FROM schema.sample WHERE maintbl.number = sample.nb_main);
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION schema.update_number()
OWNER TO postgres;
CREATE TRIGGER update_number
AFTER INSERT OR UPDATE
ON schema.maintbl
FOR EACH ROW
EXECUTE PROCEDURE schema.update_number();
I just found out that my select query is probably wrong, if I run SELECT query by itself it return one row 'NULL' but i should not?
SELECT DISTINCT(maintbl.number)
FROM schema.maintbl
WHERE NOT EXISTS (
SELECT nb_main FROM schema.sample WHERE maintbl.number = sample.nb_main);
Any good advice?
Best
If I understood correctly, you wish to append to schema.sample a number that has been inserted or updated in schema.maintbl, right?
CREATE OR REPLACE FUNCTION schema.update_number()
RETURNS trigger AS
$BODY$
BEGIN
IF (SELECT COUNT(*) FROM schema.sample WHERE number = NEW.number) = 0 THEN
INSERT INTO schema.sample(nb_main) VALUES (NEW.number);
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;

CONSTRAINT to check values from a remotely related table (via join etc.)

I would like to add a constraint that will check values from related table.
I have 3 tables:
CREATE TABLE somethink_usr_rel (
user_id BIGINT NOT NULL,
stomethink_id BIGINT NOT NULL
);
CREATE TABLE usr (
id BIGINT NOT NULL,
role_id BIGINT NOT NULL
);
CREATE TABLE role (
id BIGINT NOT NULL,
type BIGINT NOT NULL
);
(If you want me to put constraint with FK let me know.)
I want to add a constraint to somethink_usr_rel that checks type in role ("two tables away"), e.g.:
ALTER TABLE somethink_usr_rel
ADD CONSTRAINT CH_sm_usr_type_check
CHECK (usr.role.type = 'SOME_ENUM');
I tried to do this with JOINs but didn't succeed. Any idea how to achieve it?
CHECK constraints cannot currently reference other tables. The manual:
Currently, CHECK expressions cannot contain subqueries nor refer to
variables other than columns of the current row.
One way is to use a trigger like demonstrated by #Wolph.
A clean solution without triggers: add redundant columns and include them in FOREIGN KEY constraints, which are the first choice to enforce referential integrity. Related answer on dba.SE with detailed instructions:
Enforcing constraints “two tables away”
Another option would be to "fake" an IMMUTABLE function doing the check and use that in a CHECK constraint. Postgres will allow this, but be aware of possible caveats. Best make that a NOT VALID constraint. See:
Disable all constraints and table checks while restoring a dump
A CHECK constraint is not an option if you need joins. You can create a trigger which raises an error instead.
Have a look at this example: http://www.postgresql.org/docs/9.1/static/plpgsql-trigger.html#PLPGSQL-TRIGGER-EXAMPLE
CREATE TABLE emp (
empname text,
salary integer,
last_date timestamp,
last_user text
);
CREATE FUNCTION 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;
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
...i did it so (nazwa=user name, firma = company name) :
CREATE TABLE users
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
nazwa character varying(20),
firma character varying(50)
);
CREATE TABLE test
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
firma character varying(50),
towar character varying(20),
nazwisko character varying(20)
);
ALTER TABLE public.test ENABLE ROW LEVEL SECURITY;
CREATE OR REPLACE FUNCTION whoIAM3() RETURNS varchar(50) as $$
declare
result varchar(50);
BEGIN
select into result users.firma from users where users.nazwa = current_user;
return result;
END;
$$ LANGUAGE plpgsql;
CREATE POLICY user_policy ON public.test
USING (firma = whoIAM3());
CREATE FUNCTION test_trigger_function()
RETURNS trigger AS $$
BEGIN
NEW.firma:=whoIam3();
return NEW;
END
$$ LANGUAGE 'plpgsql'
CREATE TRIGGER test_trigger_insert BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE test_trigger_function();