I have 2 tables ("reports" and "RA_log"), one for the user to record points with agglomeration and another that records the logs. I created a function to audit every time any change is made to the "reports" table.
CREATE TABLE Reports
(
report_id SERIAL PRIMARY KEY,
report_user_name VARCHAR(20) NOT NULL,
report_location_name VARCHAR(50) NOT NULL,
report_number_people INT NOT NULL,
report_mask VARCHAR(20) NOT NULL,
report_distance BOOLEAN NOT NULL,
report_observations VARCHAR(255),
report_date_time timestamp NOT NULL,
report_latitude VARCHAR(100) NOT NULL,
report_longitude VARCHAR(100) NOT NULL
);
CREATE TABLE RA_log
(
log_change_type CHAR(1) NOT NULL,
log_user VARCHAR NOT NULL,
log_date_occurrence TIMESTAMP NOT NULL,
report_username VARCHAR NOT NULL,
report_quantity_people INTEGER
);
CREATE OR REPLACE FUNCTION audita_RA_log() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
INSERT INTO RA_log VALUES ('D', USER, now(), OLD.*);
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO RA_log VALUES ('U', USER, now(), NEW.*);
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO RA_log VALUES ('I', USER, now(), NEW.*);
RETURN NEW;
END IF;
RETURN NULL; --the result is ignored because the trigger is AFTER
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trig_log_RA
AFTER INSERT OR UPDATE OR DELETE ON reports
FOR EACH ROW EXECUTE PROCEDURE audita_RA_log();
INSERT INTO reports (report_username, report_location_name, report_quantity_people, report_mask, report_distance, report_observacoes, report_date_time, report_latitude, report_longitude)
VALUES ('Scooby', 'Lucky Lottery', 20, 'No', '1', 'Lots of people', '2021-06-22 4:10:25-07','-6.47926', '-35.4348' );
but when I'm going to enter some data. is returning the following error:
ERROR: INSERT has more expressions than target columns
LINE 1: INSERT INTO RA_log VALUES ('D', USER, now(), OLD.)
^
QUERY: INSERT INTO RA_log VALUES ('D', USER, now(), OLD.)
CONTEXT: função PL/pgSQL audita_ra_log() linha 4 em comando SQL
SQL state: 42601
Your table ra_log has 5 columns, but your inserts statements are attempting to insert 13 columns: Type, User, date and the 10 columns from Reports. That is because old.* and new.* actually mean each column from the table the trigger is fired on. In this case all 10 columns from Reports. You have 2 options.
Refer directly to the columns defined in reports. So
old.report_user_name, old.report_number_people (or new. as
appropriate) that you want in ra_log.
create or replace function audita_ra_log()
returns trigger
language plpgsql
as $$
begin
if (tg_op = 'delete') then
insert into ra_log values ('d', user, now(), old.report_id, old.report_user_name);
else
insert into ra_log values (lower(substr(tg_op,1,1)), user, now(), new.report_id, new.report_user_name);
end if;
end;
$$;
If you want all columns from Reports then discontinue the columns
report_username, report_quantity_people and replace them by a single column of hstore or json. Change the trigger accordingly.
CREATE TABLE ra_log(
log_change_type CHAR(1) NOT NULL,
, log_user VARCHAR NOT NULL,
, log_date_occurrence TIMESTAMP NOT NULL,
, reports_value json
);
create or replace function audita_ra_log()
returns trigger
language plpgsql
as $$
begin
if (tg_op = 'delete') then
insert into ra_log values ('d', user, now(), row_to_json(old.*));
else
insert into ra_log values (lower(substr(tg_op,1,1)), user, now(), row_to_json(new.*));
end if;
end;
$$;
If keeping the complete row I prefer [hstore][1] as it keeps actual column names, but json is probably the more common.
it was like this, I modified some columns in the table RA_log.
CREATE TABLE RA_log
(
change VARCHAR NOT NULL,
changed_table VARCHAR NOT NULL,
date_time_change TIMESTAMP,
user_change VARCHAR NOT NULL
);
CREATE FUNCTION log_reports() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO sys_log(change, table_changed, date_time_change, user_change)
VALUES (TG_OP, TG_TABLE_NAME, now(), CURRENT_USER );
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER change_reports
AFTER INSERT OR DELETE OR UPDATE ON reports
FOR EACH ROW
EXECUTE PROCEDURE log_reports();
Related
in my simple application I would like to create a view in order to allow users filling data of my db.
Here a little example of my data
CREATE TABLE specie
(
specie_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_comune TEXT UNIQUE,
nome_scientifico TEXT UNIQUE
);
CREATE TABLE rilevatore
(
rilevatore_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_cognome TEXT UNIQUE,
telefono INTEGER,
email TEXT,
ente_appartenenza TEXT
);
CREATE TABLE evento_investimento
(
evento_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
data DATE,
ora TIME WITHOUT TIME ZONE,
rilevatore_id INT REFERENCES rilevatore (rilevatore_id),
specie_id INT REFERENCES specie(specie_id),
);
This is the VIEW I created
CREATE VIEW investimenti_vista AS
SELECT
evento_investimento.evento_id,
evento_investimento.ora,
evento_investimento.data,
rilevatore.nome_cognome,
rilevatore.telefono,
rilevatore.email,
rilevatore.ente_appartenenza,
specie.nome_comune,
specie.nome_scientifico
from
evento_investimento
JOIN specie ON evento_investimento.specie_id = specie.specie_id
JOIN rilevatore ON evento_investimento.rilevatore_id = rilevatore.rilevatore_id;
When I attempt to fill the data I receive an error from postgres since view generated from different tables aren't updatable by default.
Thus, I implemetend the following trigger to overcome this issue.
CREATE OR REPLACE FUNCTION inserimento_vista() RETURNS trigger AS $$
BEGIN
INSERT INTO evento_investimento (data,ora)
VALUES (NEW.data,NEW.ora);
INSERT INTO rilevatore (nome_cognome, telefono, email, ente_appartenenza)
VALUES (NEW.nome_cognome, NEW.telefono, NEW.email, NEW.ente_appartenenza);
INSERT INTO specie (nome_comune, nome_scientifico)
VALUES (NEW.nome_comune, NEW.nome_scientifico);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create trigger inserimento_vista_trg
instead of insert on investimenti_vista for each row EXECUTE procedure inserimento_vista();
However this is not working due to unique contraints I have in the rilevatore and specie tables. How I can solve this?
Thanks
You might try to check for the existence of the conflicting values like this:
CREATE OR REPLACE FUNCTION inserimento_vista() RETURNS trigger AS $$
BEGIN
INSERT INTO evento_investimento (data,ora)
VALUES (NEW.data,NEW.ora);
if not exists(select * from rilevatore where rilevatore.nome_cognome=new.nome_cognome) then
INSERT INTO rilevatore (nome_cognome, telefono, email, ente_appartenenza)
VALUES (NEW.nome_cognome, NEW.telefono, NEW.email, NEW.ente_appartenenza);
end if;
if not exists(select * from specie where specie.nome_comune=new.nome_comune) then
INSERT INTO specie (nome_comune, nome_scientifico)
VALUES (NEW.nome_comune, NEW.nome_scientifico);
end if;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
You might want to add to this an update the specie and/or rilevatore tables with the non-conflicting values but that's up to you :-)
I'm working on history of my database when a row is modify/delete.
My main table is "bati" and history table "bati_history", when a row is delete or modify, the trigger is suppose to insert into bati_history all the old data, then delete in the main table (bati). But with my code, the row is insert into the history but not delete in the main table and I don't know why.
I'm on PostgreSQL 11.2 64-bit
The code :
Main table:
CREATE TABLE IF NOT EXISTS bati(
id_bati BIGSERIAL NOT NULL UNIQUE,
code_bati VARCHAR(25) NOT NULL,
code_parcelle CHAR(50) NOT NULL,
...);
History table:
CREATE TABLE IF NOT EXISTS bati_history(
id_history BIGSERIAL NOT NULL PRIMARY KEY,
event CHAR(10) NOT NULL,
date_save_history TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
id_bati BIGINT NOT NULL,
code_bati VARCHAR(25) NOT NULL,
code_parcelle CHAR(50) NOT NULL,
...);
The function
CREATE FUNCTION archive_bati() RETURNS TRIGGER AS $BODY$
BEGIN
IF (TG_OP = 'DELETE') THEN
INSERT INTO bati_history (event,id_bati,code_bati,code_parcelle,...)
VALUES ('DELETE',OLD.id_bati,OLD.code_bati,OLD.code_parcelle,OLD....);
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO bati_history (event,id_bati,code_bati,code_parcelle,...)
VALUES ('UPDATE',OLD.id_bati,OLD.code_bati,OLD.code_parcelle,OLD....);
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
TRIGGERS:
CREATE TRIGGER bati_trigger_before_delete BEFORE DELETE
ON bati FOR EACH ROW
EXECUTE PROCEDURE archive_bati();
CREATE TRIGGER bati_trigger_before_update BEFORE UPDATE
ON bati FOR EACH ROW
EXECUTE PROCEDURE archive_bati();
When I try DELETE FROM bati;, I expect to copy every row in bati_history, then delete them from bati, but they are not delete from bati, and I have this output without error :
test=# INSERT INTO bati (id_bati,code_bati,code_parcelle,id_interne) VALUES (5,'CODEBATI001','CODEPARC001',02);
INSERT 0 1
test=# INSERT INTO bati (id_bati,code_bati,code_parcelle,id_interne) VALUES (6,'CODEBATI002','CODEPARC002',02);
INSERT 0 1
test=# DELETE FROM bati;
DELETE 0
(sorry for my english I'm french)
You should use the if-else branching to either return NEW OR OLD depending on the trigger operation. The variable TG_OP has a text type & could be used in the insert query directly.
So, the function definition becomes:
CREATE FUNCTION archive_bati()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO bati_history (event,id_bati,code_bati,code_parcelle)
VALUES (TG_OP, OLD.id_bati, OLD.code_bati, OLD.code_parcelle);
IF TG_OP = 'DELETE'
THEN RETURN OLD;
ELSE RETURN NEW;
END IF;
END;
$$ LANGUAGE PLPGSQL;
Also, it seems unnecessary to me to define two triggers when 1 will suffice:
CREATE TRIGGER bati_trigger_before_update BEFORE UPDATE OR DELETE
ON bati FOR EACH ROW
EXECUTE PROCEDURE archive_bati();
When you delete a row, NEW is null. If the before trigger returns a null, it means the operation should be cancelled. You should then return OLD for deletions, and NEW for updates.
From the doc:
In the case of a before-trigger on DELETE, the returned value has no
direct effect, but it has to be nonnull to allow the trigger action to
proceed. Note that NEW is null in DELETE triggers, so returning that
is usually not sensible. The usual idiom in DELETE triggers is to
return OLD.
I have a situation where users are not allowed to enter a duplicate value. If user tries to add duplicate value, the system saves a details of user in a audit table. Trigger is used for that. My code is below
create or replace trigger tr_add_on_audit_table
before insert on lds_consultant
for each row
declare
uname varchar2(30);
begin
select username into uname from lds_consultant where username = :NEW.USERNAME;
if uname <> '' or uname <> null then
insert into audit_table values(null, null, 'nishan', 'insert', null, null, 'cmd', null, 'LDS_CONSULTANT', 'CONSULTANT_ID',null, null, null);
end if;
end;
but this code doesn't insert data into audit table.
How can I achieve that?
NULL isn't equal to nor different from anything. You should use IS NULL or IS NOT NULL, not <> nor =.
Something like this:
create or replace trigger tr_add_on_audit_table
before insert on lds_consultant
for each row
declare
uname varchar2(30);
begin
select username
into uname
from lds_consultant
where username = :NEW.USERNAME;
if uname is not null then --> this!
insert into audit_table
values(null, null, 'nishan', 'insert', null, null, 'cmd', null, 'LDS_CONSULTANT', 'CONSULTANT_ID',null, null, null);
end if;
exception
when no_data_found then
null;
end;
I included exception handler section in case that SELECT doesn't return anything; if it isn't probable, remove it (or handle it properly; I'm doing nothing (NULL;). Also, handle other exceptions, if necessary.
Also, I'd suggest you to name all columns you're inserting into. Today, you know what value goes where, but in a matter of a month or two you'll forget what is the third NULL value supposed to mean.
Furthermore, you said that user isn't allowed to enter a duplicate value - well, this code won't make it happen.
The simplest option is to create a unique key constraint on the USERNAME column and let Oracle handle duplicates.
If you want to do that yourself, you should e.g.
raise_application_error(-20000, 'Duplicate username is not allowed);
However, that won't save your INSERT into the table as everything will be rolled back. In order to fix that, create a procedure that uses pragma autonomous_transaction and commits insert into the audit table.
Everything would look like this:
create or replace procedure p_audit as
pragma autonomous_transaction;
begin
insert into audit_table
values(null, null, 'nishan', 'insert', null, null, 'cmd', null, 'LDS_CONSULTANT', 'CONSULTANT_ID',null, null, null);
commit;
end;
/
create or replace trigger tr_add_on_audit_table
before insert on lds_consultant
for each row
declare
uname varchar2(30);
begin
select username
into uname
from lds_consultant
where username = :NEW.USERNAME;
if uname is not null then
p_audit;
raise_application_error(-20000, 'Duplicates are not allowed')
end if;
exception
when no_data_found then
null;
end;
/
But, once again, why bother? Uniqueness is the keyword here.
I've these two tables (Encomenda and Informacaofaturacao) and I'm trying to create a trigger to insert a new line on Informacaofaturacao before insert on Encomenda and put the ID of new line of Informacaofaturacao on new line of Encomenda.
What am I doing wrong?
Thanks
CREATE TABLE Encomenda
(
EncomendaID SERIAL,
ClienteID integer NOT NULL,
MoradaFaturacaoID integer NOT NULL,
MoradaEnvioID integer NOT NULL,
InformacaofaturacaoID integer NULL,
Data timestamp NOT NULL,
Estado EstadoEncomenda NOT NULL DEFAULT 'Em processamento',
CONSTRAINT PK_Encomenda PRIMARY KEY (EncomendaID)
)
;
CREATE TABLE Informacaofaturacao
(
InformacaofaturacaoID SERIAL,
MetodopagamentoID integer NULL,
Portes real NULL,
Iva real NULL,
Total real NULL,
CONSTRAINT PK_Informacaofaturacao PRIMARY KEY (InformacaofaturacaoID)
)
;
CREATE OR REPLACE FUNCTION insert_encomenda()
RETURNS TRIGGER
AS $$
BEGIN
NEW.InformacaofaturacaoID := (INSERT INTO Informacaofaturacao RETURNING InformacaofaturacaoID);
RETURN NEW;
END $$ LANGUAGE plpgsql;
CREATE TRIGGER insert_encomenda_trigger
BEFORE INSERT OR UPDATE ON Encomenda
FOR EACH ROW
EXECUTE PROCEDURE insert_encomenda();
Postgres doesn't accept a data modifying SQL statement (INSERT, UPDATE or DELETE) in assignment. The documentation states:
... the expression in such a statement is evaluated by means of an SQL SELECT command sent to the main database engine.
You should use this form of executing a query with a single-row result instead.
Also, INSERT command must have VALUES part, it can be: DEFAULT VALUES, VALUES(...) or query see the syntax in the documentation.
CREATE OR REPLACE FUNCTION insert_encomenda()
RETURNS TRIGGER
AS $$
BEGIN
INSERT INTO Informacaofaturacao(InformacaofaturacaoID)
VALUES(DEFAULT)
RETURNING InformacaofaturacaoID
INTO NEW.InformacaofaturacaoID;
RETURN NEW;
END $$ LANGUAGE plpgsql;
This is the trigger from plpgsql -
CREATE TABLE emp (
empname text NOT NULL,
salary integer
);
CREATE TABLE emp_audit(
operation char(1) NOT NULL,
stamp timestamp NOT NULL,
userid text NOT NULL,
empname text NOT NULL,
salary integer
);
CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
BEGIN
--
-- Create a row in emp_audit to reflect the operation performed on emp,
-- make use of the special variable TG_OP to work out the operation.
--
IF (TG_OP = 'DELETE') THEN
INSERT INTO emp_audit SELECT 'D', now(), user, OLD.*;
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO emp_audit SELECT 'U', now(), user, NEW.*;
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp_audit SELECT 'I', now(), user, NEW.*;
RETURN NEW;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$emp_audit$ LANGUAGE plpgsql;
CREATE TRIGGER emp_audit
AFTER INSERT OR UPDATE OR DELETE ON emp
FOR EACH ROW EXECUTE PROCEDURE process_emp_audit();
Now I want to add one more column in the emp_audit table which is a primary key, say emp_audit_id-
so now what should come here in the above SELECT query so that it can take care of PRIMARY KEY emp_audit_id as well.
Since you do not want to write out the field list on the INSERT statements (other readers: see comments below), you need to use a regular integer as PRIMARY KEY and a manually created sequence.
First alter the emp_audit table definition:
ALTER TABLE emp_audit ADD COLUMN emp_audit_id integer PRIMARY KEY
Create a sequence:
CREATE SEQUENCE seq_empaudit_pk;
In the INSERT statements, add the next value from the sequence:
IF (TG_OP = 'DELETE') THEN
INSERT INTO emp_audit SELECT 'D', now(), user, OLD.*, nextval(seq_empaudit_pk);
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO emp_audit SELECT 'U', now(), user, NEW.*, nextval(seq_empaudit_pk);
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp_audit SELECT 'I', now(), user, NEW.*, nextval(seq_empaudit_pk);
RETURN NEW;
END IF;
Make sure to verify the order of the fields after you alter the table and list the fields in the SELECT query in exactly that order.