Oracle - Audit Trail Generator? - oracle

I am looking for a generic procedure that will generate audit trails for Oracle databases. We are currently using a similar procedure on SQL Server and wondering if an Oracle equivalent exists. We are hoping the audit table will be a separate table than the original table and include user/date time info.
Here is the SQL Server equivalent we are using: https://www.codeproject.com/Articles/21068/Audit-Trail-Generator-for-Microsoft-SQL
Any advice is greatly appreciated.

If you don't want to use Oracle native mechanism, you could have your very own framework that generates and reads your own auditing table (I know you can, we had similar thing where I once worked).
Here are the main components:
a_sqnc is the sequence you will use in TrackTable to keep track of the order of actions in column NO_ORD (even though there is also a D_UPD column with the modification time).
create sequence a_sqnc
minvalue 1
maxvalue 99999999
start with 1
increment by 1
nocache;
TrackTable will have a TABLE_NAME column in order to track changes from different tables. It also have a PK_VALUE and ROW_VALUE where we store the data that changed. Here is the table creation with useful indexes:
create table TrackTable (
table_name VARCHAR2(50) not null,
action VARCHAR2(240) not null,
no_ord NUMBER(12) not null,
nature VARCHAR2(3) not null,
pk_value VARCHAR2(4000),
row_value VARCHAR2(4000),
ori VARCHAR2(250),
c_user VARCHAR2(20),
d_upd DATE
);
create index AP_D_UPD on TrackTable (D_UPD);
create index AP_NO_ORD on TrackTable (NO_ORD);
create index AP_TABLE_NAME on TrackTable (TABLE_NAME);
Say you have a simple table BANK with two columns PK_val (the primary key) and val:
create table BANK (
pk_val VARCHAR2(50) not null,
val VARCHAR2(240) not null
);
alter table BANK
add constraint BK_PK primary key (pk_val)
using index ;
Use DBMS_APPLICATION_INFO.READ_MODULE(w_sess_mod, w_sess_act) to know what module and what action operates: I concatenate both in column ORI in TrackTable;
user Oracle session variable will allow you tracking who did the change in column c_user;
Here is how to create trigger TRCK_BNK to track changes in table BANK; it will categorize in 3 actions: DELETE, UPDATE, INSERT (you can remove the INSERT case if needed).
CREATE OR REPLACE TRIGGER "TRCK_BNK"
AFTER DELETE OR INSERT OR UPDATE
ON BANK
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
w_a VARCHAR2(10);
W_ERRM VARCHAR2(1000);
W_CODE VARCHAR2(1000);
w_n VARCHAR2(200) := 'BANK';
w_id NUMBER := a_sqnc.nextval;
w_act v$session.action%type;
w_mod v$session.module%type;
w_ori TrackTable.ORI%TYPE;
BEGIN
DBMS_APPLICATION_INFO.READ_MODULE(w_mod, w_act);
w_ori := 'Module : '||w_mod ||' ; Action : '||w_act;
----------------------------------
-- test which action is for change
----------------------------------
IF UPDATING
THEN
w_a := 'UPDATE';
ELSIF DELETING
THEN
w_a := 'DELETE';
ELSIF INSERTING
THEN
w_a := 'INSERT';
END IF;
----------------------------------
-- Insert into TrackTable
----------------------------------
If w_a in ('UPDATE', 'DELETE') then
Insert into TrackTable
Select w_n, w_a, w_id, 'OLD', :OLD.pk_val, :OLD.val
, w_ori, user, sysdate
From Dual;
End if;
-- if you update, there is a new value and an old value
If w_a in ('UPDATE', 'INSERT') then
Insert into TrackTable
Select w_n, w_a, w_id, 'NEW', :NEW.pk_val, :NEW.val
, w_ori, user, sysdate
From Dual;
End if;
Exception
When others then
Begin
W_ERRM := SQLERRM;
W_CODE := SQLCODE;
-- try inserting in case of error anyway
Insert into TrackTable
Select w_n, w_a, -1, 'ERR', 'Grrr: '||W_CODE, W_ERRM
, w_ori, user, sysdate
From Dual;
End;
End;
/
Then add functions to your framework that generates the triggers given a table, retrieves changes, reverts table to a given date...
NB:
This way of tracking every change on the table impairs performances if table changes a lot. But it is great for parameter tables that scarcely change.

Have a look at Oracles Flashback Data Archive which bases upon the UNDO Data. It can be configured to track any change to your data. It is available in any edition of oracle since 11g2 (11.2.0.4).
In the Oracle documentation it says that optimazation is limited but basic functionality is available in any edition.

Related

How to conitnue a consecutive row number in pyspark(AWS glue) [duplicate]

It appears that there is no concept of AUTO_INCREMENT in Oracle, up until and including version 11g.
How can I create a column that behaves like auto increment in Oracle 11g?
There is no such thing as "auto_increment" or "identity" columns in Oracle as of Oracle 11g. However, you can model it easily with a sequence and a trigger:
Table definition:
CREATE TABLE departments (
ID NUMBER(10) NOT NULL,
DESCRIPTION VARCHAR2(50) NOT NULL);
ALTER TABLE departments ADD (
CONSTRAINT dept_pk PRIMARY KEY (ID));
CREATE SEQUENCE dept_seq START WITH 1;
Trigger definition:
CREATE OR REPLACE TRIGGER dept_bir
BEFORE INSERT ON departments
FOR EACH ROW
BEGIN
SELECT dept_seq.NEXTVAL
INTO :new.id
FROM dual;
END;
/
UPDATE:
IDENTITY column is now available on Oracle 12c:
create table t1 (
c1 NUMBER GENERATED by default on null as IDENTITY,
c2 VARCHAR2(10)
);
or specify starting and increment values, also preventing any insert into the identity column (GENERATED ALWAYS) (again, Oracle 12c+ only)
create table t1 (
c1 NUMBER GENERATED ALWAYS as IDENTITY(START with 1 INCREMENT by 1),
c2 VARCHAR2(10)
);
Alternatively, Oracle 12 also allows to use a sequence as a default value:
CREATE SEQUENCE dept_seq START WITH 1;
CREATE TABLE departments (
ID NUMBER(10) DEFAULT dept_seq.nextval NOT NULL,
DESCRIPTION VARCHAR2(50) NOT NULL);
ALTER TABLE departments ADD (
CONSTRAINT dept_pk PRIMARY KEY (ID));
SYS_GUID returns a GUID-- a globally unique ID. A SYS_GUID is a RAW(16). It does not generate an incrementing numeric value.
If you want to create an incrementing numeric key, you'll want to create a sequence.
CREATE SEQUENCE name_of_sequence
START WITH 1
INCREMENT BY 1
CACHE 100;
You would then either use that sequence in your INSERT statement
INSERT INTO name_of_table( primary_key_column, <<other columns>> )
VALUES( name_of_sequence.nextval, <<other values>> );
Or you can define a trigger that automatically populates the primary key value using the sequence
CREATE OR REPLACE TRIGGER trigger_name
BEFORE INSERT ON table_name
FOR EACH ROW
BEGIN
SELECT name_of_sequence.nextval
INTO :new.primary_key_column
FROM dual;
END;
If you are using Oracle 11.1 or later, you can simplify the trigger a bit
CREATE OR REPLACE TRIGGER trigger_name
BEFORE INSERT ON table_name
FOR EACH ROW
BEGIN
:new.primary_key_column := name_of_sequence.nextval;
END;
If you really want to use SYS_GUID
CREATE TABLE table_name (
primary_key_column raw(16) default sys_guid() primary key,
<<other columns>>
)
In Oracle 12c onward you could do something like,
CREATE TABLE MAPS
(
MAP_ID INTEGER GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) NOT NULL,
MAP_NAME VARCHAR(24) NOT NULL,
UNIQUE (MAP_ID, MAP_NAME)
);
And in Oracle (Pre 12c).
-- create table
CREATE TABLE MAPS
(
MAP_ID INTEGER NOT NULL ,
MAP_NAME VARCHAR(24) NOT NULL,
UNIQUE (MAP_ID, MAP_NAME)
);
-- create sequence
CREATE SEQUENCE MAPS_SEQ;
-- create tigger using the sequence
CREATE OR REPLACE TRIGGER MAPS_TRG
BEFORE INSERT ON MAPS
FOR EACH ROW
WHEN (new.MAP_ID IS NULL)
BEGIN
SELECT MAPS_SEQ.NEXTVAL
INTO :new.MAP_ID
FROM dual;
END;
/
Here are three flavors:
numeric. Simple increasing numeric value, e.g. 1,2,3,....
GUID. globally univeral identifier, as a RAW datatype.
GUID (string). Same as above, but as a string which might be easier to handle in some languages.
x is the identity column. Substitute FOO with your table name in each of the examples.
-- numerical identity, e.g. 1,2,3...
create table FOO (
x number primary key
);
create sequence FOO_seq;
create or replace trigger FOO_trg
before insert on FOO
for each row
begin
select FOO_seq.nextval into :new.x from dual;
end;
/
-- GUID identity, e.g. 7CFF0C304187716EE040488AA1F9749A
-- use the commented out lines if you prefer RAW over VARCHAR2.
create table FOO (
x varchar(32) primary key -- string version
-- x raw(32) primary key -- raw version
);
create or replace trigger FOO_trg
before insert on FOO
for each row
begin
select cast(sys_guid() as varchar2(32)) into :new.x from dual; -- string version
-- select sys_guid() into :new.x from dual; -- raw version
end;
/
update:
Oracle 12c introduces these two variants that don't depend on triggers:
create table mytable(id number default mysequence.nextval);
create table mytable(id number generated as identity);
The first one uses a sequence in the traditional way; the second manages the value internally.
Oracle Database 12c introduced Identity, an auto-incremental (system-generated) column.
In the previous database versions (until 11g), you usually implement an Identity by creating a Sequence and a Trigger.
From 12c onward, you can create your own Table and define the column that has to be generated as an Identity.
Assuming you mean a column like the SQL Server identity column?
In Oracle, you use a SEQUENCE to achieve the same functionality. I'll see if I can find a good link and post it here.
Update: looks like you found it yourself. Here is the link anyway:
http://www.techonthenet.com/oracle/sequences.php
Trigger and Sequence can be used when you want serialized number that anyone can easily read/remember/understand. But if you don't want to manage ID Column (like emp_id) by this way, and value of this column is not much considerable, you can use SYS_GUID() at Table Creation to get Auto Increment like this.
CREATE TABLE <table_name>
(emp_id RAW(16) DEFAULT SYS_GUID() PRIMARY KEY,
name VARCHAR2(30));
Now your emp_id column will accept "globally unique identifier value".
you can insert value in table by ignoring emp_id column like this.
INSERT INTO <table_name> (name) VALUES ('name value');
So, it will insert unique value to your emp_id Column.
Starting with Oracle 12c there is support for Identity columns in one of two ways:
Sequence + Table - In this solution you still create a sequence as you normally would, then you use the following DDL:
CREATE TABLE MyTable
(ID NUMBER DEFAULT MyTable_Seq.NEXTVAL,
...)
Table Only - In this solution no sequence is explicitly specified. You would use the following DDL:
CREATE TABLE MyTable (ID NUMBER GENERATED AS IDENTITY, ...)
If you use the first way it is backward compatible with the existing way of doing things. The second is a little more straightforward and is more inline with the rest of the RDMS systems out there.
it is called Identity Columns and it is available only from oracle Oracle 12c
CREATE TABLE identity_test_tab
(
id NUMBER GENERATED ALWAYS AS IDENTITY,
description VARCHAR2 (30)
);
example of insert into Identity Columns as below
INSERT INTO identity_test_tab (description) VALUES ('Just DESCRIPTION');
1 row created.
you can NOT do insert like below
INSERT INTO identity_test_tab (id, description) VALUES (NULL, 'ID=NULL and DESCRIPTION');
ERROR at line 1: ORA-32795: cannot insert into a generated always
identity column
INSERT INTO identity_test_tab (id, description) VALUES (999, 'ID=999 and DESCRIPTION');
ERROR at line 1: ORA-32795: cannot insert into a generated always
identity column
useful link
Here is complete solution w.r.t exception/error handling for auto increment, this solution is backward compatible and will work on 11g & 12c, specifically if application is in production.
Please replace 'TABLE_NAME' with your appropriate table name
--checking if table already exisits
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE TABLE_NAME';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
--creating table
CREATE TABLE TABLE_NAME (
ID NUMBER(10) PRIMARY KEY NOT NULL,
.
.
.
);
--checking if sequence already exists
BEGIN
EXECUTE IMMEDIATE 'DROP SEQUENCE TABLE_NAME_SEQ';
EXCEPTION WHEN OTHERS THEN NULL;
END;
--creating sequence
/
CREATE SEQUENCE TABLE_NAME_SEQ START WITH 1 INCREMENT BY 1 MINVALUE 1 NOMAXVALUE NOCYCLE CACHE 2;
--granting rights as per required user group
/
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE_NAME TO USER_GROUP;
-- creating trigger
/
CREATE OR REPLACE TRIGGER TABLE_NAME_TS BEFORE INSERT OR UPDATE ON TABLE_NAME FOR EACH ROW
BEGIN
-- auto increment column
SELECT TABLE_NAME_SEQ.NextVal INTO :New.ID FROM dual;
-- You can also put some other required default data as per need of your columns, for example
SELECT SYS_CONTEXT('USERENV', 'SESSIONID') INTO :New.SessionID FROM dual;
SELECT SYS_CONTEXT('USERENV','SERVER_HOST') INTO :New.HostName FROM dual;
SELECT SYS_CONTEXT('USERENV','OS_USER') INTO :New.LoginID FROM dual;
.
.
.
END;
/
Query to create auto increment in oracle. In below query incrmnt column value will be auto incremented wheneever a new row is inserted
CREATE TABLE table1(
id RAW(16) NOT NULL ENABLE,
incrmnt NUMBER(10,0) GENERATED ALWAYS AS IDENTITY
MINVALUE 1 MAXVALUE 999999999999999999999999999 INCREMENT BY 1 START WITH 1 NOORDER NOCYCLE NOT NULL ENABLE,
CONSTRAINT PK_table1 PRIMARY KEY (id) ENABLE);
This is how I did this on an existing table and column (named id):
UPDATE table SET id=ROWNUM;
DECLARE
maxval NUMBER;
BEGIN
SELECT MAX(id) INTO maxval FROM table;
EXECUTE IMMEDIATE 'DROP SEQUENCE table_seq';
EXECUTE IMMEDIATE 'CREATE SEQUENCE table_seq START WITH '|| TO_CHAR(TO_NUMBER(maxval)+1) ||' INCREMENT BY 1 NOMAXVALUE';
END;
CREATE TRIGGER table_trigger
BEFORE INSERT ON table
FOR EACH ROW
BEGIN
:new.id := table_seq.NEXTVAL;
END;
FUNCTION GETUNIQUEID_2 RETURN VARCHAR2
AS
v_curr_id NUMBER;
v_inc NUMBER;
v_next_val NUMBER;
pragma autonomous_transaction;
begin
CREATE SEQUENCE sequnce
START WITH YYMMDD0000000001
INCREMENT BY 1
NOCACHE
select sequence.nextval into v_curr_id from dual;
if(substr(v_curr_id,0,6)= to_char(sysdate,'yymmdd')) then
v_next_val := to_number(to_char(SYSDATE+1, 'yymmdd') || '0000000000');
v_inc := v_next_val - v_curr_id;
execute immediate ' alter sequence sequence increment by ' || v_inc ;
select sequence.nextval into v_curr_id from dual;
execute immediate ' alter sequence sequence increment by 1';
else
dbms_output.put_line('exception : file not found');
end if;
RETURN 'ID'||v_curr_id;
END;
FUNCTION UNIQUE2(
seq IN NUMBER
) RETURN VARCHAR2
AS
i NUMBER := seq;
s VARCHAR2(9);
r NUMBER(2,0);
BEGIN
WHILE i > 0 LOOP
r := MOD( i, 36 );
i := ( i - r ) / 36;
IF ( r < 10 ) THEN
s := TO_CHAR(r) || s;
ELSE
s := CHR( 55 + r ) || s;
END IF;
END LOOP;
RETURN 'ID'||LPAD( s, 14, '0' );
END;
Creating a Sequence:
CREATE SEQUENCE SEQ_CM_LC_FINAL_STATUS
MINVALUE 1 MAXVALUE 999999999999999999999999999
INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE;
Adding a Trigger
CREATE OR REPLACE TRIGGER CM_LC_FINAL_STATUS_TRIGGER
BEFORE INSERT
ON CM_LC_FINAL_STATUS
FOR EACH ROW
BEGIN
:NEW.LC_FINAL_STATUS_NO := SEQ_CM_LC_FINAL_STATUS.NEXTVAL;
END;
The first step is to create a SEQUENCE in your database, which is a data object that multiple users can access to automatically generate incremented values. As discussed in the documentation, a sequence in Oracle prevents duplicate values from being created simultaneously because multiple users are effectively forced to “take turns” before each sequential item is generated. –
Finally, we’ll create our SEQUENCE that will be utilized later to actually generate the unique, auto incremented value. –
While we have our table created and ready to go, our sequence is thus far just sitting there but never being put to use. This is where TRIGGERS come in. Similar to an event in modern programming languages, a TRIGGER in Oracle is a stored procedure that is executed when a particular event occurs. Typically a TRIGGER will be configured to fire when a table is updated or a record is deleted, providing a bit of cleanup when necessary. –
In our case, we want to execute our TRIGGER prior to INSERT into our CM_LC_FINAL_STATUS table, ensuring our SEQUENCE is incremented and that new value is passed onto our primary key column.
create trigger t1_trigger
before insert on AUDITLOGS
for each row
begin
select t1_seq.nextval into :new.id from dual;
end;
only I have to just change the table name (AUDITLOGS) with your table name and new.id with new.column_name
oracle has sequences AND identity columns in 12c
http://www.oracle-base.com/articles/12c/identity-columns-in-oracle-12cr1.php#identity-columns
I found this but not sure what rdb 7 is
http://www.oracle.com/technetwork/products/rdb/0307-identity-columns-128126.pdf
Maybe just try this simple script:
http://www.hlavaj.sk/ai.php
Result is:
CREATE SEQUENCE TABLE_PK_SEQ;
CREATE OR REPLACE TRIGGER TR_SEQ_TABLE BEFORE INSERT ON TABLE FOR EACH ROW
BEGIN
SELECT TABLE_PK_SEQ.NEXTVAL
INTO :new.PK
FROM dual;
END;

Accessing old and new values without :OLD and :NEW in a trigger

As discussed here, I'm unable to use :OLD and :NEW on columns with collation other than USING_NLS_COMP. I'm trying to find a way around this but haven't been successful so far.
This is the original trigger:
CREATE OR REPLACE TRIGGER SYS$PERSONSSALUTATIONAU
AFTER UPDATE ON PERSONS
FOR EACH ROW
begin
State_00.Salutations_ToDelete(State_00.Salutations_ToDelete.Count + 1) := :old.SalutationTitle;
State_00.Salutations_ToInsert(State_00.Salutations_ToInsert.Count + 1) := :new.SalutationTitle;
end;
This is what I've tried:
CREATE OR REPLACE TRIGGER SYS$PERSONSSALUTATIONAU
FOR UPDATE ON Persons
COMPOUND TRIGGER
TYPE Persons_Record IS RECORD (
SalutationTitle NVARCHAR2(30)
);
TYPE Persons_Table IS TABLE OF Persons_Record INDEX BY PLS_INTEGER;
gOLD Persons_Table;
gNEW Persons_Table;
BEFORE EACH ROW IS BEGIN
SELECT SalutationTitle
BULK COLLECT INTO gOLD
FROM Persons
WHERE ID = :OLD.ID;
END BEFORE EACH ROW;
AFTER EACH ROW IS BEGIN
SELECT SalutationTitle
BULK COLLECT INTO gNEW
FROM Persons
WHERE ID = :NEW.ID;
END AFTER EACH ROW;
AFTER STATEMENT IS BEGIN
FOR i IN 1 .. gNEW.COUNT LOOP
State_00.Salutations_ToDelete(State_00.Salutations_ToDelete.Count + 1) := gOLD(i).SalutationTitle;
State_00.Salutations_ToInsert(State_00.Salutations_ToInsert.Count + 1) := gNEW(i).SalutationTitle;
END LOOP;
END AFTER STATEMENT;
END;
This results in error ORA-04091. I've also tried moving the select into the AFTER STATEMENT section which works, but there is no way to access the old values. If somebody has a solution for this it would be most appreciated.
EDIT:
I created a minimal reproducible example:
CREATE TABLE example_table (
id VARCHAR2(10),
name NVARCHAR2(100)
);
CREATE TABLE log_table (
id VARCHAR2(10),
new_name NVARCHAR2(100),
old_name NVARCHAR2(100)
);
CREATE OR REPLACE TRIGGER example_trigger
AFTER UPDATE ON example_table
FOR EACH ROW BEGIN
INSERT INTO log_table VALUES(:old.id, :new.name, :old.name);
END;
INSERT INTO example_table VALUES('01', 'Daniel');
-- this works as expected
UPDATE example_table SET name = ' John' WHERE id = '01';
SELECT * FROM log_table;
DROP TABLE example_table;
CREATE TABLE example_table (
id VARCHAR2(10),
-- this is the problematic part
name NVARCHAR2(100) COLLATE XCZECH_PUNCTUATION_CI
);
INSERT INTO example_table VALUES('01', 'Daniel');
-- here nothing is inserted into log_example, if you try to
-- recompile the trigger you'll get error PLS-00049
UPDATE example_table SET name = ' John' WHERE id = '01';
SELECT * FROM log_table;
DROP TABLE example_table;
DROP TABLE log_table;
DROP TRIGGER example_trigger;
In the discussion you reference a document concerning USING_NLS_COMP. That has nothing to do with the error you are getting. The error ORA-04091 is a reference to the table that fired the trigger (mutating). More to come on this. I am not saying you do not have USING_NLS_COMP issues, just that they are NOT causing the current error.
There are misconceptions shown in your trigger. Beginning with the name itself; you should avoid the prefix SYS. This prefix is used by Oracle for internal objects. While SYS prefix is not specifically prohibited at best it causes confusion. If this is actually created in the SYS schema then that in itself is a problem. Never use SYS schema for anything.
There is no reason to create a record type containing a single variable, then create a collection of that type, and finally define variables of the collection. Just create a collection to the variable directly, and define variables of the collection.
The bulk collect in the select statements is apparently misunderstood as used. I assume you want to collect all the new and old values in the collections. Bulk collect however will not do this. Each time bulk collect runs the collection used is cleared and repopulated. Result being the collection contains only the only the LAST population. Assuming id is unique the each collection would contain only 1 record. And now that brings us to the heart of the problem.
The error ORA-04091: <table name> is mutating, trigger/function may not see it results from attempting to SELECT from the table that fired the trigger; this is invalid. In this case the trigger fired due to a DML action on the persons table as a result you cannot select from persons in a row level trigger (stand alone or row level part of a compound trigger. But it is not needed. The pseudo rows :old and :new contain the complete image of the row. To get a value just reference the appropriate row and column name. Assign that to your collection.
Taking all into account we arrive at:
create or replace trigger personssalutation
for update
on persons
compound trigger
type persons_table is table of
persons.salutationtitle%type;
gold persons_table := persons_table();
gnew persons_table := persons_table();
before each row is
begin
gold.extend;
gold(gold.count) := :old.salutationtitle;
end before each row;
after each row is
begin
gnew.extend;
gold(gold.count) := :new.salutationtitle;
end after each row;
after statement is
begin
for i in 1 .. gnew.count loop
state_00.salutations_todelete(state_00.salutations_todelete.count + 1) := gold(i);
state_00.salutations_toinsert(state_00.salutations_toinsert.count + 1) := gnew(i);
end loop;
end after statement;
end personssalutation;
NOTE: Unfortunately you did not provide sample data, nor description of the functions in the AFTER STATEMENT section. Therefore the above is not tested.

How to get exact updated column name in oracle trigger?

I'm going to create a trigger to audit a table. Suppose my table define as below.
COUNTRY_ID NOT NULL CHAR(2)
COUNTRY_NAME VARCHAR2(40)
REGION_ID NUMBER
and my log table created as below.
create table country_log(
username varchar2(10),
transaction_date date,
new_value varchar(20),
old_value varchar(20)
)
My half completed trigger would be like below.
CREATE OR REPLACE TRIGGER tr_countryTable
AFTER UPDATE ON COUNTRIES
FOR EACH ROW
BEGIN
insert into country_log (username,transaction_date,new_value,old_value ) values (USER, sysdate,**:New, :Old** );
END;
/
I need to know instead of comparing each column value in :old and :new, how to get exactly updated column's new and old values.
In an UPDATE trigger, a column name can be specified with an UPDATING predicate to determine if the named column is being updated. Let's define a trigger as the following:
CREATE OR REPLACE TRIGGER tr_countryTable
AFTER UPDATE ON COUNTRIES
FOR EACH ROW
BEGIN
IF UPDATING ('COUNTRY_NAME') THEN
-- :new.COUNTRY_NAME is new value
-- :old.COUNTRY_NAME is old value
END IF;
IF UPDATING ('REGION_ID') THEN
-- :new.REGION_ID is new value
-- :old.REGION_ID is old value
END IF;
END;
/
Oracle 11g triggers documentation

use the same auto increment trigger in oracle for many tables

I created a database in MySQL with ~10 tables, each starting with the column
SN INT NOT NULL AUTO_INCREMENT
SN doesn't mean anything, just the primary to differentiate between possibly repeating/similar names/titles, etc
I'm moving it to Oracle now, and found this post here on stackoverflow to make the trigger to auto increment the SN field. Basically,
CREATE SEQUENCE user_seq;
CREATE OR REPLACE TRIGGER user_inc
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
SELECT user_seq.NEXTVAL
INTO :new.SN
FROM dual;
END;
/
Now, how can I rewrite that trigger once to apply to all the other tables? Because otherwise I have to rewrite it for many tables, just changing the trigger name and sequence name... I was picturing something like:
BEFORE INSERT ON users OR other_table OR another_one
I also found this post here, but the one answer there isn't helpful because I think it's reasonable for many tables to have the same SN field, or I'm misunderstanding the point.
Also, not Oracle 12c so no identity columns
Thanks in advance
I was going to just comment on the first post I mentioned but I can't comment without more reputation points :/
Creating a trigger referencing many tables in Oracle is not possible,
What you can do is to generate the triggers with a PL/SQL statement.
Below is an example on how could you achieve this
drop table tab_a;
drop table tab_b;
drop table tab_c;
drop sequence seq_tab_a_id;
drop sequence seq_tab_b_id;
drop sequence seq_tab_c_id;
--create test tables
create table tab_a (SN number, otherfield varchar2(30), date_field date);
create table tab_b (SN number, otherfield varchar2(30), date_field date);
create table tab_c (SN number, otherfield varchar2(30), date_field date);
-- this pl/sql block creates the sequences and the triggers
declare
my_seq_create_stmt varchar2(2000);
my_trigger_create_stmt varchar2(2000);
begin
for i in (select table_name
from user_tables
-- remember to change this where condition to filter
-- the tables that are relevant for you
where table_name in ('TAB_A', 'TAB_B', 'TAB_C') )loop <<TableLoop>>
my_seq_create_stmt := 'CREATE SEQUENCE '||'SEQ_'||i.table_name||'_ID '
||CHR(13)||' START WITH 1 INCREMENT BY 1 NOCYCLE ';
execute immediate my_seq_create_stmt;
my_trigger_create_stmt := 'CREATE OR REPLACE TRIGGER '||'TRG_'||i.Table_name||'_ID_BI '||' BEFORE INSERT ON '||i.table_name||' FOR EACH ROW '
||CHR(13)||'BEGIN '
||CHR(13)||' SELECT '||'SEQ_'||i.table_name||'_ID'||'.NEXTVAL '
||CHR(13)||' INTO :new.SN '
||CHR(13)||' FROM dual; '
||CHR(13)||'END; ';
execute immediate my_trigger_create_stmt;
end loop TableLoop;
end;
/
-- test the triggers and the sequences
insert into tab_a (otherfield, date_field) values ('test 1',sysdate);
insert into tab_a (otherfield, date_field) values ('test 2',sysdate);
commit;
Select * from tab_a;

ORA-00907: missing right parenthesis on create table query [duplicate]

It appears that there is no concept of AUTO_INCREMENT in Oracle, up until and including version 11g.
How can I create a column that behaves like auto increment in Oracle 11g?
There is no such thing as "auto_increment" or "identity" columns in Oracle as of Oracle 11g. However, you can model it easily with a sequence and a trigger:
Table definition:
CREATE TABLE departments (
ID NUMBER(10) NOT NULL,
DESCRIPTION VARCHAR2(50) NOT NULL);
ALTER TABLE departments ADD (
CONSTRAINT dept_pk PRIMARY KEY (ID));
CREATE SEQUENCE dept_seq START WITH 1;
Trigger definition:
CREATE OR REPLACE TRIGGER dept_bir
BEFORE INSERT ON departments
FOR EACH ROW
BEGIN
SELECT dept_seq.NEXTVAL
INTO :new.id
FROM dual;
END;
/
UPDATE:
IDENTITY column is now available on Oracle 12c:
create table t1 (
c1 NUMBER GENERATED by default on null as IDENTITY,
c2 VARCHAR2(10)
);
or specify starting and increment values, also preventing any insert into the identity column (GENERATED ALWAYS) (again, Oracle 12c+ only)
create table t1 (
c1 NUMBER GENERATED ALWAYS as IDENTITY(START with 1 INCREMENT by 1),
c2 VARCHAR2(10)
);
Alternatively, Oracle 12 also allows to use a sequence as a default value:
CREATE SEQUENCE dept_seq START WITH 1;
CREATE TABLE departments (
ID NUMBER(10) DEFAULT dept_seq.nextval NOT NULL,
DESCRIPTION VARCHAR2(50) NOT NULL);
ALTER TABLE departments ADD (
CONSTRAINT dept_pk PRIMARY KEY (ID));
SYS_GUID returns a GUID-- a globally unique ID. A SYS_GUID is a RAW(16). It does not generate an incrementing numeric value.
If you want to create an incrementing numeric key, you'll want to create a sequence.
CREATE SEQUENCE name_of_sequence
START WITH 1
INCREMENT BY 1
CACHE 100;
You would then either use that sequence in your INSERT statement
INSERT INTO name_of_table( primary_key_column, <<other columns>> )
VALUES( name_of_sequence.nextval, <<other values>> );
Or you can define a trigger that automatically populates the primary key value using the sequence
CREATE OR REPLACE TRIGGER trigger_name
BEFORE INSERT ON table_name
FOR EACH ROW
BEGIN
SELECT name_of_sequence.nextval
INTO :new.primary_key_column
FROM dual;
END;
If you are using Oracle 11.1 or later, you can simplify the trigger a bit
CREATE OR REPLACE TRIGGER trigger_name
BEFORE INSERT ON table_name
FOR EACH ROW
BEGIN
:new.primary_key_column := name_of_sequence.nextval;
END;
If you really want to use SYS_GUID
CREATE TABLE table_name (
primary_key_column raw(16) default sys_guid() primary key,
<<other columns>>
)
In Oracle 12c onward you could do something like,
CREATE TABLE MAPS
(
MAP_ID INTEGER GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) NOT NULL,
MAP_NAME VARCHAR(24) NOT NULL,
UNIQUE (MAP_ID, MAP_NAME)
);
And in Oracle (Pre 12c).
-- create table
CREATE TABLE MAPS
(
MAP_ID INTEGER NOT NULL ,
MAP_NAME VARCHAR(24) NOT NULL,
UNIQUE (MAP_ID, MAP_NAME)
);
-- create sequence
CREATE SEQUENCE MAPS_SEQ;
-- create tigger using the sequence
CREATE OR REPLACE TRIGGER MAPS_TRG
BEFORE INSERT ON MAPS
FOR EACH ROW
WHEN (new.MAP_ID IS NULL)
BEGIN
SELECT MAPS_SEQ.NEXTVAL
INTO :new.MAP_ID
FROM dual;
END;
/
Here are three flavors:
numeric. Simple increasing numeric value, e.g. 1,2,3,....
GUID. globally univeral identifier, as a RAW datatype.
GUID (string). Same as above, but as a string which might be easier to handle in some languages.
x is the identity column. Substitute FOO with your table name in each of the examples.
-- numerical identity, e.g. 1,2,3...
create table FOO (
x number primary key
);
create sequence FOO_seq;
create or replace trigger FOO_trg
before insert on FOO
for each row
begin
select FOO_seq.nextval into :new.x from dual;
end;
/
-- GUID identity, e.g. 7CFF0C304187716EE040488AA1F9749A
-- use the commented out lines if you prefer RAW over VARCHAR2.
create table FOO (
x varchar(32) primary key -- string version
-- x raw(32) primary key -- raw version
);
create or replace trigger FOO_trg
before insert on FOO
for each row
begin
select cast(sys_guid() as varchar2(32)) into :new.x from dual; -- string version
-- select sys_guid() into :new.x from dual; -- raw version
end;
/
update:
Oracle 12c introduces these two variants that don't depend on triggers:
create table mytable(id number default mysequence.nextval);
create table mytable(id number generated as identity);
The first one uses a sequence in the traditional way; the second manages the value internally.
Oracle Database 12c introduced Identity, an auto-incremental (system-generated) column.
In the previous database versions (until 11g), you usually implement an Identity by creating a Sequence and a Trigger.
From 12c onward, you can create your own Table and define the column that has to be generated as an Identity.
Assuming you mean a column like the SQL Server identity column?
In Oracle, you use a SEQUENCE to achieve the same functionality. I'll see if I can find a good link and post it here.
Update: looks like you found it yourself. Here is the link anyway:
http://www.techonthenet.com/oracle/sequences.php
Trigger and Sequence can be used when you want serialized number that anyone can easily read/remember/understand. But if you don't want to manage ID Column (like emp_id) by this way, and value of this column is not much considerable, you can use SYS_GUID() at Table Creation to get Auto Increment like this.
CREATE TABLE <table_name>
(emp_id RAW(16) DEFAULT SYS_GUID() PRIMARY KEY,
name VARCHAR2(30));
Now your emp_id column will accept "globally unique identifier value".
you can insert value in table by ignoring emp_id column like this.
INSERT INTO <table_name> (name) VALUES ('name value');
So, it will insert unique value to your emp_id Column.
Starting with Oracle 12c there is support for Identity columns in one of two ways:
Sequence + Table - In this solution you still create a sequence as you normally would, then you use the following DDL:
CREATE TABLE MyTable
(ID NUMBER DEFAULT MyTable_Seq.NEXTVAL,
...)
Table Only - In this solution no sequence is explicitly specified. You would use the following DDL:
CREATE TABLE MyTable (ID NUMBER GENERATED AS IDENTITY, ...)
If you use the first way it is backward compatible with the existing way of doing things. The second is a little more straightforward and is more inline with the rest of the RDMS systems out there.
it is called Identity Columns and it is available only from oracle Oracle 12c
CREATE TABLE identity_test_tab
(
id NUMBER GENERATED ALWAYS AS IDENTITY,
description VARCHAR2 (30)
);
example of insert into Identity Columns as below
INSERT INTO identity_test_tab (description) VALUES ('Just DESCRIPTION');
1 row created.
you can NOT do insert like below
INSERT INTO identity_test_tab (id, description) VALUES (NULL, 'ID=NULL and DESCRIPTION');
ERROR at line 1: ORA-32795: cannot insert into a generated always
identity column
INSERT INTO identity_test_tab (id, description) VALUES (999, 'ID=999 and DESCRIPTION');
ERROR at line 1: ORA-32795: cannot insert into a generated always
identity column
useful link
Here is complete solution w.r.t exception/error handling for auto increment, this solution is backward compatible and will work on 11g & 12c, specifically if application is in production.
Please replace 'TABLE_NAME' with your appropriate table name
--checking if table already exisits
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE TABLE_NAME';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
--creating table
CREATE TABLE TABLE_NAME (
ID NUMBER(10) PRIMARY KEY NOT NULL,
.
.
.
);
--checking if sequence already exists
BEGIN
EXECUTE IMMEDIATE 'DROP SEQUENCE TABLE_NAME_SEQ';
EXCEPTION WHEN OTHERS THEN NULL;
END;
--creating sequence
/
CREATE SEQUENCE TABLE_NAME_SEQ START WITH 1 INCREMENT BY 1 MINVALUE 1 NOMAXVALUE NOCYCLE CACHE 2;
--granting rights as per required user group
/
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE_NAME TO USER_GROUP;
-- creating trigger
/
CREATE OR REPLACE TRIGGER TABLE_NAME_TS BEFORE INSERT OR UPDATE ON TABLE_NAME FOR EACH ROW
BEGIN
-- auto increment column
SELECT TABLE_NAME_SEQ.NextVal INTO :New.ID FROM dual;
-- You can also put some other required default data as per need of your columns, for example
SELECT SYS_CONTEXT('USERENV', 'SESSIONID') INTO :New.SessionID FROM dual;
SELECT SYS_CONTEXT('USERENV','SERVER_HOST') INTO :New.HostName FROM dual;
SELECT SYS_CONTEXT('USERENV','OS_USER') INTO :New.LoginID FROM dual;
.
.
.
END;
/
Query to create auto increment in oracle. In below query incrmnt column value will be auto incremented wheneever a new row is inserted
CREATE TABLE table1(
id RAW(16) NOT NULL ENABLE,
incrmnt NUMBER(10,0) GENERATED ALWAYS AS IDENTITY
MINVALUE 1 MAXVALUE 999999999999999999999999999 INCREMENT BY 1 START WITH 1 NOORDER NOCYCLE NOT NULL ENABLE,
CONSTRAINT PK_table1 PRIMARY KEY (id) ENABLE);
This is how I did this on an existing table and column (named id):
UPDATE table SET id=ROWNUM;
DECLARE
maxval NUMBER;
BEGIN
SELECT MAX(id) INTO maxval FROM table;
EXECUTE IMMEDIATE 'DROP SEQUENCE table_seq';
EXECUTE IMMEDIATE 'CREATE SEQUENCE table_seq START WITH '|| TO_CHAR(TO_NUMBER(maxval)+1) ||' INCREMENT BY 1 NOMAXVALUE';
END;
CREATE TRIGGER table_trigger
BEFORE INSERT ON table
FOR EACH ROW
BEGIN
:new.id := table_seq.NEXTVAL;
END;
FUNCTION GETUNIQUEID_2 RETURN VARCHAR2
AS
v_curr_id NUMBER;
v_inc NUMBER;
v_next_val NUMBER;
pragma autonomous_transaction;
begin
CREATE SEQUENCE sequnce
START WITH YYMMDD0000000001
INCREMENT BY 1
NOCACHE
select sequence.nextval into v_curr_id from dual;
if(substr(v_curr_id,0,6)= to_char(sysdate,'yymmdd')) then
v_next_val := to_number(to_char(SYSDATE+1, 'yymmdd') || '0000000000');
v_inc := v_next_val - v_curr_id;
execute immediate ' alter sequence sequence increment by ' || v_inc ;
select sequence.nextval into v_curr_id from dual;
execute immediate ' alter sequence sequence increment by 1';
else
dbms_output.put_line('exception : file not found');
end if;
RETURN 'ID'||v_curr_id;
END;
FUNCTION UNIQUE2(
seq IN NUMBER
) RETURN VARCHAR2
AS
i NUMBER := seq;
s VARCHAR2(9);
r NUMBER(2,0);
BEGIN
WHILE i > 0 LOOP
r := MOD( i, 36 );
i := ( i - r ) / 36;
IF ( r < 10 ) THEN
s := TO_CHAR(r) || s;
ELSE
s := CHR( 55 + r ) || s;
END IF;
END LOOP;
RETURN 'ID'||LPAD( s, 14, '0' );
END;
Creating a Sequence:
CREATE SEQUENCE SEQ_CM_LC_FINAL_STATUS
MINVALUE 1 MAXVALUE 999999999999999999999999999
INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE;
Adding a Trigger
CREATE OR REPLACE TRIGGER CM_LC_FINAL_STATUS_TRIGGER
BEFORE INSERT
ON CM_LC_FINAL_STATUS
FOR EACH ROW
BEGIN
:NEW.LC_FINAL_STATUS_NO := SEQ_CM_LC_FINAL_STATUS.NEXTVAL;
END;
The first step is to create a SEQUENCE in your database, which is a data object that multiple users can access to automatically generate incremented values. As discussed in the documentation, a sequence in Oracle prevents duplicate values from being created simultaneously because multiple users are effectively forced to “take turns” before each sequential item is generated. –
Finally, we’ll create our SEQUENCE that will be utilized later to actually generate the unique, auto incremented value. –
While we have our table created and ready to go, our sequence is thus far just sitting there but never being put to use. This is where TRIGGERS come in. Similar to an event in modern programming languages, a TRIGGER in Oracle is a stored procedure that is executed when a particular event occurs. Typically a TRIGGER will be configured to fire when a table is updated or a record is deleted, providing a bit of cleanup when necessary. –
In our case, we want to execute our TRIGGER prior to INSERT into our CM_LC_FINAL_STATUS table, ensuring our SEQUENCE is incremented and that new value is passed onto our primary key column.
create trigger t1_trigger
before insert on AUDITLOGS
for each row
begin
select t1_seq.nextval into :new.id from dual;
end;
only I have to just change the table name (AUDITLOGS) with your table name and new.id with new.column_name
oracle has sequences AND identity columns in 12c
http://www.oracle-base.com/articles/12c/identity-columns-in-oracle-12cr1.php#identity-columns
I found this but not sure what rdb 7 is
http://www.oracle.com/technetwork/products/rdb/0307-identity-columns-128126.pdf
Maybe just try this simple script:
http://www.hlavaj.sk/ai.php
Result is:
CREATE SEQUENCE TABLE_PK_SEQ;
CREATE OR REPLACE TRIGGER TR_SEQ_TABLE BEFORE INSERT ON TABLE FOR EACH ROW
BEGIN
SELECT TABLE_PK_SEQ.NEXTVAL
INTO :new.PK
FROM dual;
END;

Resources