Oracle masking data using a trigger - sql

Im trying to create a process that masks data. When I create the trigger I'm getting the error.
ORA-04072: invalid trigger type
I'm unsure why and was hoping someone can explain what the problem is and how to fix it.
The end result is when a user queries cards they should see the masked data and WHEN they query CARDS_TBL they should see all the data (unmasked)
Original implementation
CREATE TABLE CARDS (
CARD_ID NUMBER
GENERATED BY DEFAULT AS IDENTITY,
CARD_STR VARCHAR2(16) NOT NULL,
PRIMARY KEY (CARD_ID)
);
INSERT INTO CARDS(CARD_STR) VALUES('4024007187788590');
INSERT INTO CARDS(CARD_STR) VALUES('5432223398564536');
INSERT INTO CARDS(CARD_STR) VALUES('5430445512530934');
INSERT INTO CARDS(CARD_STR) VALUES('4020156755227854');
INSERT INTO CARDS(CARD_STR) VALUES('5431248766892318');
CREATE OR REPLACE VIEW CARDS_V AS
SELECT
CARD_ID,
REGEXP_REPLACE(CARD_STR, '(^\d{3})(.*)(\d{4}$)', '\1**********\3') AS CARD_STR
FROM CARDS;
CREATE OR REPLACE TRIGGER CARDS_TRG_INSERT INSTEAD OF
INSERT ON CARDS_V
FOR EACH ROW
BEGIN
INSERT INTO CARDS (CARD_STR) VALUES (:NEW.CARD_STR);
END;
INSERT INTO CARDS_V (CARD_STR) VALUES ('4011589733550908');
CREATE OR REPLACE TRIGGER CARDS_TRG_UPDATE INSTEAD OF
UPDATE ON CARDS_V
FOR EACH ROW
BEGIN
UPDATE CARDS
SET CARD_STR = :NEW.CARD_STR
WHERE CARD_ID = :OLD.CARD_ID;
END;
CREATE TABLE CARDS_TBL (
CARD_ID NUMBER
GENERATED BY DEFAULT AS IDENTITY,
CARD_STR VARCHAR2(16) NOT NULL,
PRIMARY KEY (CARD_ID)
);
INSERT INTO CARDS_TBL(CARD_STR) VALUES('4024007187788590');
INSERT INTO CARDS_TBL(CARD_STR) VALUES('5432223398564536');
INSERT INTO CARDS_TBL(CARD_STR) VALUES('5430445512530934');
INSERT INTO CARDS_TBL(CARD_STR) VALUES('4020156755227854');
INSERT INTO CARDS_TBL(CARD_STR) VALUES('5431248766892318');
CREATE OR REPLACE VIEW CARDS AS
SELECT
CARD_ID,
REGEXP_REPLACE(CARD_STR, '(^\d{3})(.*)(\d{4}$)', '\1**********\3') AS CARD_STR
FROM CARDS_TBL;
CREATE OR REPLACE TRIGGER CARDS_TBL_TRG_UPDATE BEFORE UPDATE ON CARDS_TBL
FOR EACH ROW
BEGIN
UPDATE CARDS_TBL
SET CARD_STR = :NEW.CARD_STR
WHERE CARD_ID = :OLD.CARD_ID;
END;
/
CREATE OR REPLACE TRIGGER CARDS_TBL_TRG_INSERT BEFORE INSERT ON CARDS
FOR EACH ROW
BEGIN
INSERT INTO CARDS_TBL (CARD_STR) VALUES (:NEW.CARD_STR);
END;
INSERT INTO CARDS_TBL (CARD_STR) VALUES ('2222333344445555');
SELECT * FROM CARDS_TBL;
UPDATE CARDS_TBL
SET CARD_STR = '2222333344445566'
WHERE CARD_ID = 6;
/
SELECT * FROM CARDS;

In this particular example, at least, the trigger CARDS_TBL_TRG_UPDATE doesn't do anything (except raise a MUTATING TABLE exception) and can be dispensed with. Get rid of it and your example runs as expected. See this db<>fiddle

Related

Create a Trigger with merge to monitor tables

I need to create a Trigger to monitor this table :
CREATE TABLE "REFERENCE"
( "NUM_CONTRACT" VARCHAR2(20 CHAR),
"NATURE" VARCHAR2(20 CHAR),
"PR" VARCHAR2(14 CHAR),
)
I just want to store the date of the last modification and his "PR" in this table :
CREATE TABLE EVENT_REFERENCE (
ID NUMBER GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) NOT NULL,
reference VARCHAR(14) NOT NULL UNIQUE,
date_modification TIMESTAMP(6),
PRIMARY KEY (ID)
)
I use a merge in order to avoid to have duplicate rows with the same reference and different date . I just want to keep the date of last modification for each reference rows
For that i created this trigger but idk what's wrong with it . Can anyone help me ?
CREATE OR REPLACE TRIGGER TRG_REFERENCE
AFTER INSERT OR UPDATE ON REFERENCE
FOR EACH ROW
DECLARE
PRAGMA autonomous_transaction;
BEGIN
IF INSERTING
THEN
MERGE INTO EVENT_REFERENCE hist
USING (select :new.pr
from dual) t1
ON (t1.pr=hist.reference)
WHEN MATCHED THEN
UPDATE SET hist.date_modification=systimestamp
WHEN NOT MATCHED THEN
INSERT INTO EVENT_REFERENCE (REFERENCE, DATE_MODIFICATION)
VALUES (:NEW.prm, systimestamp);
END IF;
COMMIT;
END;
You have:
:NEW.prm when it should be :NEW.pr
INSERT INTO EVENT_REFERENCE (REFERENCE, DATE_MODIFICATION) when you just need INSERT (REFERENCE, DATE_MODIFICATION)
You also use AFTER INSERT OR UPDATE and then check IF INSERTING why not just use AFTER INSERT?
The fixed code:
CREATE OR REPLACE TRIGGER TRG_REFERENCE
AFTER INSERT OR UPDATE ON REFERENCE
FOR EACH ROW
DECLARE
PRAGMA autonomous_transaction;
BEGIN
IF INSERTING
THEN
MERGE INTO EVENT_REFERENCE hist
USING DUAL t1
ON (:new.pr=hist.reference)
WHEN MATCHED THEN
UPDATE SET hist.date_modification=systimestamp
WHEN NOT MATCHED THEN
INSERT (REFERENCE, DATE_MODIFICATION)
VALUES (:NEW.pr, systimestamp);
END IF;
COMMIT;
END;
/
db<>fiddle here

postgres updatable view and unique constraints

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 :-)

How to insert a newly generated id into another table with a trigger in postgresql?

Basically, users when they create a new record in mytable1, there is an id field that needs to be the same across multiple tables. I achieve this by having mytable2 with the s_id as primary key
My current function looks like
CREATE OR REPLACE FUNCTION test.new_record()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
case when new.s_id in (select s_id from mytable1) then
insert into mytable2 (sprn, date_created) select max(s_id) +1, now() from mytable2 ;
update mytable1 set new.s_id = (select max(b.s_id) from mytable2 b);
end case;
RETURN new;
END;
$function$;
Intended was when the s_id is replicated then it would create a new entry on mytable2. This new entry would then be updated onto mytable1
Problem with this function is that right now it does not recognise the new on the update part of the function.
How to keep the s_id take the value on every new insert ?
If you want to have one "generator" across multiple tables, create one sequence that is used across all those tables for the default value:
create sequence the_id_sequence;
create table one
(
id integer primary key default nextval('the_id_sequence')
.... other columns
);
create table two
(
id integer primary key default nextval('the_id_sequence')
.... other columns ...
);
If you want to replicate an ID from one table to another during insert, you only need one sequence:
create table one
(
-- using identity is the preferred over "serial" to auto-generate PK values
id integer primary key generated always as identity
);
create table two
(
id integer primary key
);
create or replace function insert_two()
returns trigger
as
$$
begin
insert into two (id) values (new.id);
return new;
end;
$$
language plpgsql;
create trigger replicate_id
before insert on one
for each row
execute procedure insert_two();
Then if you run:
insert into one (id) values (default);
A row with exactly the same id value will be inserted into table two.
If you don't have a generated ID column so far, use the following syntax:
alter table one
add testidcolumn bigint generated always as identity;

TRIGGER BEFORE DELETE, doesn't delete data in table

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.

Getting the last value from a sequence in SQL and insert it into another table (Oracle)

I have 2 tables, which were created by executing the following (keep in mind that every query is executed through on node.js, i.e., connection.execute('select....') ):
create table User(userid int not null, name varchar(50) not null, primary key(userid));
create table Random(userid int not null, random int, primary key (userid), foreign key (userid) references User(userid) );
I also added a sequence to auto increment the userid:
create sequence userid_seq;
create or replace trigger userid_bir
before insert on User
for each row
begin
select userid_seq.nextval
into :new.userid
from dual;
end;
/
Now, I have a process whereby after I insert a new user, that user's id is inserted immediately into Random. I browsed through Stackoverflow and came up with:
insert into User(name) values('John');
SET #last_id = LAST_INSERT_ID();
insert into Random(userid, random) values( last_id, 2);
However, I got the following error:
SP2-0735: unknown SET option beginning "#last_id"
Any ideas?
My other concern is that if there are 2 computers trying to insert 2 users (in total) at the same time, the last_id value (or MAX userid value) might mess up. That is, the server might be executing:
insert into User(name) values('John'); ## from Person A
insert into User(name) values('Brian'); ## from Person B
SET #last_id = LAST_INSERT_ID(); ## from Person A, say it's set to 1
SET #last_id = LAST_INSERT_ID(); ## from Person B, say it's set to 2
insert into Random(userid, random) values( last_id, 2); ## from Person A
insert into Random(userid, random) values( last_id, 2); ## from Person B, error: duplicate values?
That's probably MySQL syntax. In Oracle you can use RETURNING INTO clause.
declare
last_id number;
begin
insert into user(name) values('Abcd')
returning userid into last_id;
insert into Random(userid, random) values( last_id, 2);
commit;
end;
/
userid_seq.currval will return the most recent value returned for the sequence in the current session. So you can
INSERT INTO random( userid, random )
VALUES( user_id_seq.currval, 2 );
If you want to have the value in a local variable
DECLARE
l_userid user.user_id%type;
BEGIN
INSERT INTO user(name)
VALUES( 'John' )
RETURNING userid
INTO l_userid;
INSERT INTO random( userid, random )
VALUES( l_userid, 2 );
END;