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.
Related
I am using PostgreSQL 14.4 . My script
-- 0.
DROP TABLE IF EXISTS tenant;
CREATE TABLE tenant
(
id smallint primary key,
company_tax_code character varying(14),
period character varying(16), -- 2021070420220705
created timestamp with time zone
);
CREATE OR REPLACE FUNCTION set_id_tenant()
RETURNS trigger AS
$$
DECLARE
BEGIN
new.id = (select coalesce(max(id), -32769) from tenant) + 1;
RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER trigger_insert_without_id_tenant
BEFORE INSERT
ON tenant
FOR EACH ROW
EXECUTE PROCEDURE set_id_tenant();
CREATE INDEX tenant_idx ON tenant (id);
COMMIT;
--------------------------------------------------------------------------------
-- 4.
DROP TABLE IF EXISTS account_object_bank_account;
CREATE TABLE account_object_bank_account
(
id smallint,
account_object_id smallint not null,
bank_account character varying(64),
bank_name character varying(128),
bank_id smallint,
sort_order smallint,
bank_branch_name character varying(256),
province character varying(128),
tenant_id smallint,
PRIMARY KEY (id, tenant_id),
CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES tenant (id)
);
CREATE OR REPLACE FUNCTION account_object_bank_account_setId()
RETURNS trigger AS
$$
DECLARE
BEGIN
new.id = (select coalesce(max(id), -32769) from account_object_bank_account where tenant_id = new.tenant_id) + 1;
RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER account_object_bank_account_trig_insertWithoutId
BEFORE INSERT
ON account_object_bank_account
FOR EACH ROW
EXECUTE PROCEDURE account_object_bank_account_setId();
CREATE INDEX account_object_bank_account_idx ON account_object_bank_account (id, tenant_id);
COMMENT ON TABLE public.account_object_bank_account IS 'Bảng lưu tài khoản ngân hàng của khách hàng';
COMMENT ON COLUMN public.account_object_bank_account.id IS 'PK';
COMMENT ON COLUMN public.account_object_bank_account.account_object_id IS 'FK';
COMMENT ON COLUMN public.account_object_bank_account.bank_account IS 'Số TK ngân hàng';
COMMENT ON COLUMN public.account_object_bank_account.bank_name IS 'Tên ngân hàng';
COMMIT;
--------------------------------------------------------------------------------
-- 5.
DROP TABLE IF EXISTS account_object_belong_to_group;
CREATE TABLE account_object_belong_to_group
(
id smallint,
account_object_id smallint,
account_object_group_id smallint,
tenant_id smallint,
PRIMARY KEY (id, tenant_id),
CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES tenant (id)
);
CREATE OR REPLACE FUNCTION account_object_belong_to_group_setId()
RETURNS trigger AS
$$
DECLARE
BEGIN
new.id = (select coalesce(max(id), -32769) from account_object_belong_to_group where tenant_id = new.tenant_id) + 1;
RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER account_object_belong_to_group_trig_insertWithoutId
BEFORE INSERT
ON account_object_belong_to_group
FOR EACH ROW
EXECUTE PROCEDURE account_object_belong_to_group_setId();
CREATE INDEX account_object_belong_to_group_idx ON account_object_belong_to_group (id, tenant_id);
COMMIT;
--------------------------------------------------------------------------------
I want re-use this custom function, then put argument of function is table name:
account_object_bank_account_setId()
account_object_belong_to_group_setId()
to
set_id( table_name )
argument sample: account_object_bank_account, account_object_belong_to_group.
How to do?
update your 2 triggers with set_id('table_name')
and use TG_ARGV[] to retrieve the parameter in the trigger function
https://www.postgresql.org/docs/14/plpgsql-trigger.html
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();
create or replace TRIGGER dept_id_trig
BEFORE INSERT ON DEPARTMENTS
FOR EACH ROW
DECLARE
BEGIN
IF(:NEW.DEPARTMENT_ID IS NULL )
THEN
select DEPT_DEPTID_SEQ.nextval INTO :new.DEPARTMENT_ID FROM dual;
END IF;
END;
Please check below trigger.
CREATE OR REPLACE TRIGGER dept_id_trig
BEFORE INSERT ON DEPARTMENTS
FOR EACH ROW
DECLARE
BEGIN
:new.DEPARTMENT_ID := DEPT_DEPTID_SEQ.nextval;
END;
If you are on Oracle 12c or later, use an IDENTITY clause on the column rather than a trigger:
CREATE TABLE departments(
department_id NUMBER(10,0)
GENERATED ALWAYS AS IDENTITY
CONSTRAINTS departments__department_id__pk PRIMARY KEY
)
This is a trigger generated from Oracle itself when creating a new table:
create or replace TRIGGER dept_id_trig
BEFORE INSERT ON DEPARTMENTS
FOR EACH ROW
BEGIN
<<COLUMN_SEQUENCES>>
BEGIN
IF INSERTING AND :NEW.DEPARTMENT_ID IS NULL THEN
SELECT DEPT_DEPTID_SEQ.NEXTVAL INTO :NEW.DEPARTMENT_ID FROM SYS.DUAL;
END IF;
END COLUMN_SEQUENCES;
END;
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 want to implement history of changes of PostgreSQL table. The table is defined the following way:
CREATE TABLE "ps_counters"
(
"psid" integer NOT NULL,
"counter" bigint[] NOT NULL
);
I want the history table to look like:
CREATE TABLE "ps_counters_history"
(
"timestamp" timestamp NOT NULL,
"psid" integer NOT NULL,
"counter" bigint[] NOT NULL
);
I need a trigger and a stored procedure which on every change (insertion or update) in ps_couneters to insert in ps_counters_history, but in addition to prevent ps_counters_history to became too big I want partitioning of ps_counters_history table on every month.
I managed to implement it.
CREATE TABLE "ps_counters_history"
(
"id" serial PRIMARY KEY,
"timestamp" timestamp NOT NULL DEFAULT clock_timestamp(),
"psid" integer NOT NULL,
"counter" bigint[] NOT NULL
);
CREATE OR REPLACE FUNCTION ps_counters_history_trigger()
RETURNS trigger AS
$BODY$
DECLARE
table_name text;
BEGIN
table_name := 'ps_counters_history_' || to_char(CURRENT_DATE, 'yyyy_mm');
IF NOT EXISTS (SELECT 1 FROM pg_class WHERE relname = table_name)
THEN
EXECUTE 'CREATE TABLE IF NOT EXISTS ' || table_name ||
' () INHERITS (ps_counters_history);';
END IF;
EXECUTE 'INSERT INTO ' || table_name ||
'(psid, counter) VALUES ($1.psid, $1.counter);' USING NEW;
RETURN NEW;
END
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER ps_counters_history_trigger
AFTER INSERT OR UPDATE ON ps_counters FOR EACH ROW
EXECUTE PROCEDURE ps_counters_history_trigger();