Storing date & time and updater/user of row data in Oracle Apex - sql

Looking for an ideal way to store (1) date & time of update (2) updater/user per row of table data using Oracle Apex. I am thinking of adding 2 extra columns to store the info and trying to come up with a good as to how changes per row can be tracked.

If you want create logs of insert, update , delete on your table, adding 2 columns not enough. Each new update will erase previous and delete couldn't be logged. So you need to store log table separately from data table, and fill it on before and after triggers created on your data table. If you want sample I can provide some.
Here simplified example, of course in real life data will be more complex and I guess a trigger should be more smarter, but this is a simple start point to create your own. After executing codes below, try to insert, update, delete delete records in table TEST_DATA, see what happens in TEST_LOG
Create data table
create table TEST_DATA (
UNID number,
COL_B varchar2(50)
);
-- Create/Recreate primary, unique and foreign key constraints
alter table TEST_DATA
add constraint PK_TEST_DATA_UNID primary key (UNID);
Create log table for it
create table TEST_LOG (
UNID number,
OPERATION varchar2(1),
COL_OLD varchar2(50),
COL_NEW varchar2(50),
CHNGUSER varchar2(50),
CHNGDATE date
);
and finally create trigger which tracks changes
create or replace trigger TR_LOG_TEST_DATA
after update or insert or delete on TEST_DATA
referencing new as new old as old
for each row
begin
if Inserting then
insert into TEST_LOG
(UNID, OPERATION, COL_OLD, COL_NEW, CHNGUSER, CHNGDATE)
values
(:new.unid, 'I', null, :new.col_b, user, sysdate);
end if;
if Updating then
insert into TEST_LOG
(UNID, OPERATION, COL_OLD, COL_NEW, CHNGUSER, CHNGDATE)
values
(:new.unid, 'U', :old.col_b, :new.col_b, user, sysdate);
end if;
if Deleting then
insert into TEST_LOG
(UNID, OPERATION, COL_OLD, COL_NEW, CHNGUSER, CHNGDATE)
values
(:old.unid, 'D', :old.col_b, null, user, sysdate);
end if;
end;

Related

Trigger after insert to check and compare records between table

In an Oracle Database, I need to create some trigger or procedure to treat this case in the most performative way possible (is an extremely large amount of data).
I have a table called ORDER_A that every day receives a full load (its truncated, and all records are inserted again).
I have a table called ORDER_B which is a copy of ORDER_A, containing the same data and some additional control dates.
Each insertion on ORDER_A must trigger a process that looks for a record with the same identifier (primary key: order_id) in table B.
If a record exists with the same order_id, and any of the other columns have changed, an update must be performed on table B
If a record exists with the same order_id, and no values ​​in the other columns have been modified, nothing should be performed, the record must remain the same in table B.
If there is no record with the same order_id, it must be inserted in table B.
My tables are like this
CREATE TABLE ORDER_A
(
ORDER_ID NUMBER NOT NULL,
ORDER_CODE VARCHAR2(50),
ORDER_STATUS VARCHAR2(20),
ORDER_USER_ID NUMBER,
ORDER_DATE TIMESTAMP(6),
PRIMARY KEY (ORDER_ID)
);
CREATE TABLE ORDER_B
(
ORDER_ID NUMBER NOT NULL,
ORDER_CODE VARCHAR2(50),
ORDER_STATUS VARCHAR2(20),
ORDER_USER_ID NUMBER,
ORDER_DATE TIMESTAMP(6)
INSERT_AT TIMESTAMP(6),
UPDATED_AT TIMESTAMP(6),
PRIMARY KEY (ORDER_ID)
);
I have no idea how to do this and what is the best way (with a trigger, procedure, using merge, etc.)
Can someone give me a direction, please?
Here is some pseudo-code to show you a potential trigger based solution that does not fall back into slow row-by-row processing.
create or replace trigger mytrg
for insert or update delete on ordera
compound trigger
pklist sys.odcinumberlist;
before statement is
begin
pklist := sys.odcinumberlist();
end before statement ;
after each row is
begin
pklist.extend;
pklist(pklist.count) := :new.order_id;
end before each row;
after statement is
begin
merge into orderb b
using (
select a.*
from ordera a,
table(pklist) t
where a.order_id = t.column_value
) m
when matched then
update
set b.order_code = m.order_code,
b.order_status = m.order_status,
...
where decode(b.order_code,m.order_code,0,1)=1
or decode(b.order_status,m.order_status,0,1)=1
....
when not matched then
insert (b.order_id,b.order_code,....)
values (m.order_id,m.order_code,....);
end after statement ;
end;
We hold the impacted primary keys, and then build a single merge later, with an WHERE embed to minimise update activities.
If your application allows the update of primary keys, you'd need some additions, but this should get you started

How to implement AFTER INSERT Trigger in Oracle PL/SQL?

I am trying to implement after insert trigger in PLSQL. The goal is to check if there are multiple (>1) rows having specific status for each client. If so I'd like to rise an exception and roll the insertion back.
I am struggling with implementing warning-free query, which causes error during insertion. How could I manage this?
Here is my implemented trigger which I guess needs some changes.
CREATE TRIGGER blatrigger
AFTER INSERT
ON BLATABLE
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
exception_name EXCEPTION;
PRAGMA EXCEPTION_INIT (exception_name, -20999);
BEGIN
if (select count(*) as counter from BLATABLE where CLIENTID = :NEW.CLIENTID and STATUS='PENDING').counter > 1
THEN
raise exception_name;
END IF;
END;
Here is the table itself:
create table BLATABLE
(
ID NUMBER(19) not null primary key,
CLIENTID NUMBER(10),
CREATED TIMESTAMP(6),
STATUS VARCHAR2(255 char)
);
The goal is to check if there are multiple (>1) rows having specific status for each client. If so I'd like to rise an exception and roll the insertion back.
No need for a trigger. It looks like a simple unique constraint should get the job done here:
create table blatable (
id number(19) not null primary key,
clientid number(10),
created timestamp(6),
status varchar2(255 char),
constraint blaconstraint unique (clientid, status)
);
The unique constraint prevents duplicates on (clientid, status) across the whole table. If a DML operation (insert, update) attempts to generate a duplicate, an error is raised and the operation is rolled back.
If, on the other end, you want to allow only one "PENDING" status per user, then you can use a unique index as follows:
create unique index bla_index
on blatable( (case when status = 'PENDING' then clientid end) );
Use a Statement Level Trigger, rather than a Row Level by removing FOR EACH ROW, and converting to your code as below :
CREATE OR REPLACE TRIGGER blatrigger
AFTER INSERT ON BLATABLE
REFERENCING NEW AS NEW OLD AS OLD
DECLARE
counter INT;
exception_name EXCEPTION;
PRAGMA EXCEPTION_INIT(exception_name, -20999);
BEGIN
SELECT MAX(COUNT(*))
INTO counter
FROM BLATABLE
WHERE STATUS = 'PENDING'
GROUP BY CLIENTID;
IF counter > 1 THEN
RAISE exception_name;
END IF;
END;
/
where
the SELECT statement need to be removed from IF .. THEN conditional
Most probably, the mutating table error would raise for Row Level Trigger case
Demo

oracle trigger synthetic key

I'm trying to create a synthetic key with a sequence (attribute_1). Im not sure my code is working right as it's the first time i've done anything like this. I'm just looking for general pointers or tips about what im doing.
CREATE TABLE entity
(
attribute_1 INT NOT NULL PRIMARY KEY,
attribute_2 VARCHAR2(5),
attribute_3 NOT NULL VARCHAR2(5)
);
CREATE SEQUENCE attribute_1_seq
START WITH 1
INCREMENT BY 1
NOCACHE;
CREATE TRIGGER attribute_1_trig
BEFORE INSERT ON entity
FOR EACH ROW BEGIN
SELECT attribute_1_seq.NEXTVAL INTO :new.attribute_1 FROM dual;
END;
/
Your trigger will work but what if you want to supply an ID number? Some apps will insert into a parent and use the id as a foreign key to child tables. In this case you might want to call the sequence directly so you can reuse it.
This trigger will allow you to either insert null as the primary key or supply one
CREATE OR REPLACE TRIGGER entity_Id_TRG BEFORE INSERT OR UPDATE ON entity
FOR EACH ROW
BEGIN
if inserting and :new.attribute_1 is NULL then
SELECT attribute_1_SEQ.nextval into :new.attribute_1 FROM DUAL;
end if;
END;

Restoring a Truncated Table from a Backup

I am restoring the data of a truncated table in an Oracle Database from an exported csv file. However, I find that the primary key auto-increments and does not insert the actual values of the primary key constrained column from the backed up file.
I intend to do the following:
1. drop the primary key
2. import the table data
3. add primary key constraints on the required column
Is this a good approach? If not, what is recommended? Thanks.
EDIT: After more investigation, I observed there's a trigger to generate nextval on a sequence to be inserted into the primary key column. This is the source of the predicament. Hence, following the procedure above would not solve the problem. It lies in the trigger (and/or sequence) on the table. This is solved!
easier to use your .csv as an external table and then go
create table your_table_temp as select * from external table
examine the data in the new temp table to ensure you know what range of primary keys is present
do a merge into the new table
samples from here and here
CREATE TABLE countries_ext (
country_code VARCHAR2(5),
country_name VARCHAR2(50),
country_language VARCHAR2(50)
)
ORGANIZATION EXTERNAL (
TYPE ORACLE_LOADER
DEFAULT DIRECTORY ext_tab_data
ACCESS PARAMETERS (
RECORDS DELIMITED BY NEWLINE
FIELDS TERMINATED BY ','
MISSING FIELD VALUES ARE NULL
(
country_code CHAR(5),
country_name CHAR(50),
country_language CHAR(50)
)
)
LOCATION ('Countries1.txt','Countries2.txt')
)
PARALLEL 5
REJECT LIMIT UNLIMITED;
and the merge
MERGE INTO employees e
USING hr_records h
ON (e.id = h.emp_id)
WHEN MATCHED THEN
UPDATE SET e.address = h.address
WHEN NOT MATCHED THEN
INSERT (id, address)
VALUES (h.emp_id, h.address);
Edit: after you have merged the data you can drop the temp table and the result is your previous table with the old data and the new data together
Edit you mention " During imports, the primary key column does not insert from the file, but auto-increments". This can only happen when there is a trigger on the table, likely, Before insert on each row. Disable the trigger and then do your import. Re-enable the trigger after committing your inserts.
I used the following procedure to solve it:
drop trigger trigger_name
Imported the table data into target table
drop sequence sequence_name
CREATE SEQUENCE SEQ_NAME INCREMENT BY 1 START WITH start_index_for_next_val MAXVALUE max_val MINVALUE 1 NOCYCLECACHE 20 NOORDER
CREATE OR REPLACE TRIGGER "schema_name"."trigger_name"
before insert on target_table
for each row
begin
select seq_name.nextval
into :new.unique_column_name
from dual;
end;

sql triggers - want to compare existing row value with value being inserted

I am creating a trigger that should compare of the values being inserted with one that already exists in the table. The old referencer doesn't work here because I am inserting, but how can I reference something that already exists?
Here is my tables and trigger:
create table events(eid char(2) primary key, cid char(2));
create table activities(mid char(2), eid char(2),
primary key (mid, eid),
constraint activities_fk foreign key (eid) references events(eid));
create or replace trigger check_valid
before insert or update on activities
for each row when (old.mid=new.mid)
declare
v_eid char(2);
v_cid char(2);
n_cid char(2);
begin
select eid into v_eid from activities
where mid=:new.mid;
select cid into v_cid from events
where eid=v_eid;
select cid into n_cid from events
where eid=:new.eid;
if v_cid=n_cid then
raise_application_error(-20000, 'Error');
end if;
end check_valid;
/
show errors;
You can't generally select from the table you're inserting into in a trigger. This is the mutating table problem, or as I often call it, the "damn mutating table problem".
Basically, don't do this. It's a bad idea. What happens if you have two sessions operating on the table at once? The trigger fires and neither session sees what the other has done until the commit, which is after the trigger. Then you've got unexpected data in your database.
Tom Kyte says, "when I hit a mutating table error, I've got a serious fatal flaw
in my logic."