Oracle Trigger to write all Insert/Updates to File - sql

I want to place a trigger on a table which writes all inserted / updated Data to an additional log file for processing with an external tool.
Is the a way to accomplish that?

You need to create triggers that execute after the table row has been altered and write to a log file using the UTL_FILE package.
The UTL_FILE package info can be found here:
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/u_file.htm
And Trigger documentation can be found here:
http://docs.oracle.com/cd/B10501_01/appdev.920/a96590/adg13trg.htm
There is a similar answer to what you are looking for here:
http://bytes.com/topic/oracle/answers/762007-trigger-output-text-file
More info on writing to a file using PL/SQL here:
http://www.devshed.com/c/a/Oracle/Writing-to-Text-Files-in-Oracle-PLSQL/
Hope it helps...

I would avoid writing out to the file system at DML time, but would pull out the data in a batch process each night (or whatever frequency).
From your OP, its not clear if you need the "new" data after the update, or the "old" data before the update. If you just want the latest data, why not just add a modified_date field (date or timestamp type) and update that via a trigger.
create table test
(
id number,
val varchar2(100),
modified_date date default sysdate not null
)
;
CREATE OR REPLACE TRIGGER TR_TEST_BU
BEFORE UPDATE
ON TEST REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
begin
:new.modified_date := sysdate;
end;
insert into test(id, val) values (1, 'Insert 1');
commit;
select * from test;
exec dbms_lock.sleep(5);
update test set val = 'Update 1' where id = 1;
commit;
select * from test;
If you need the old data before the update (or you care about deleted data), then you'll modify the trigger to write old or deleted values to a history table, then extract the data from there.
Also note that adding a trigger to a table will slow down associated DML activity. Some shops wish to avoid this by replacing the triggers with business logic rules ("all apps must update modifed_date" edict), which usually leads to inconsistent data (or worse) from what I've seen.

Yes, here you have an example of the update part.
You simply need to do a similar one to the insert part.

Related

How to know all the actions taken on postgres SQL table?

I am looking for a way to know all the events occurred on a specific table without enabling postgres logs.
Just want to know weather the sequence of addition/deletion/ Modification.
Thanks
For audit trail in postgres you have to write an function and call it in a trigger, please have a look at wiki.postgresql.org/wiki/Audit_trigger , You will have to write a function stating that if an update or delete or insert is happening on a table it will trigger an action updating a audit table capturing required information such as ip address, query, old data, new data, timestamp of the action that has occured etc..
You can create triggers (see postgres docs) to observe all changes, e.g.:
CREATE TRIGGER insert_trigger AFTER INSERT ON my_table
EXECUTE PROCEDURE insert_function();
CREATE TRIGGER update_trigger AFTER UPDATE ON my_table
EXECUTE PROCEDURE update_function();
CREATE TRIGGER delete_trigger AFTER DELETE ON my_table
EXECUTE PROCEDURE delete_function();
or do this with just one function:
CREATE TRIGGER universal_trigger AFTER INSERT OR UPDATE OR DELETE ON my_table
EXECUTE PROCEDURE universal_function();

Why doesn't a PL/SQL block create a temporary table?

I would like to create and populate temporary table with data to process it inside loop statement like this:
DECLARE
cnt NUMBER;
BEGIN
SELECT COUNT(tname) INTO cnt from tab where tname = 'MY_TEMP';
IF (cnt > 0) THEN
EXECUTE IMMEDIATE 'DROP TABLE MY_TEMP';
END IF;
EXECUTE IMMEDIATE 'CREATE GLOBAL TEMPORARY TABLE MY_TEMP (G NVARCHAR2(128), F NVARCHAR2(128), V NVARCHAR2(128)) ON COMMIT DELETE ROWS';
INSERT INTO MY_TEMP VALUES (N'G-value1', N'F-value1', N'V-value1');
INSERT INTO MY_TEMP VALUES (N'G-value2', N'F-value2', N'V-value2');
...
FOR record IN (SELECT G,F,V FROM MY_TEMP)
LOOP
... Do something sophisticated with record.G, record.F, record.V
END LOOP;
COMMIT;
END;
When I run this script inside PL-SQL Developer it tells me for the very first INSERT that MY_TEMP table or view doesn't exist even though my EXECUTE IMMEDIATE 'CREATE GLOBAL TEMPORARY TABLE ... ' statement seems to be executed without errors. I checked there is no MY_TEMP table inside tables list after script execution
When I run EXECUTE IMMEDIATE 'CREATE GLOBAL TEMPORARY TABLE ... ' alone it runs ok and MY_TEMP table is really created. After this the whole scripts runs ok.
How do I use this script without manually precreating MY_TEMP table ?
How do I use this script without manually precreating MY_TEMP table ?
You can't. Unless of course you run everything after the creation of the temporary table using EXECUTE IMMEDIATE. But I cannot for a second recommend that approach.
The point is not that your script fails to run, but that it fails to compile. Oracle won't start running your block if it can't compile it first. At the point Oracle tries to compile your PL/SQL block, the table doesn't exist. You have a compilation error, not a runtime error.
I suspect that you are more familiar with temporary tables in SQL Server and are trying to use temporary tables in Oracle in the same way. If this is the case, then you will need to know that there are differences between temporary tables in Oracle and in SQL Server.
Firstly, there's no such thing as a local temporary table (i.e. a table visible to only one connected user) in Oracle. Oracle does have global temporary tables, but only the data in a global temporary table is temporary. The table itself is permanent, so once it has been created it will only be dropped if you explicitly drop it. Compare this with SQL Server temporary tables, which are dropped once all relevant users disconnect.
I really don't think you need to be creating the temporary table in your block. It should be sufficient to create it once beforehand.
Why do want to drop and create the temp table? Simply create it and use it.
The only way around for your problem is to make the whole INSERT INTO temp_table statements into EXECUTE IMMEDIATE in this way you can BYPASS the TABLE check during COMPILE Time first.
But this way in my opinion is not good at all. There are some questions in my mind which has be answred before answering this question.
1) Why Temp Table is created evertime and Dropped.
We have option in GTT to keep or Remove Data after one Oracle Session.
2) Is this script a one time job ? If Yes then we can go for once GTT creation and the rest script will work fine.
The probloem is not wityh your first insert. It is with the compile of your block. The table does not exist, but you are referencing it. Try creating it beforhand so it is there such as it will be once the bloick finishes. Now the code is likely to compile as the reference to the table exists when you run it.
However, then you'll get into trouble with the drop as your code has a share lock on the table so you are not allowed to drop it.
You either have to make your selects dynamic, or make sure the table is created and dropped outrside the execution of your block.
Creating temporary table in Oracle is not best practice, instead use PIVOT

How to get previous data before commit?

Is there a way to get the previous values ​​that were in a specific table before the current changes and before we commit changes, and without store data in backup table.
Maybe that way it will be more clear:
stage 1: change data in table X without commit
stage 2: get data before stage 1 without store it in backup folder
stage 3: commit stage 1 changes.
Depending on how your transaction is designed you could place logic in a trigger. Triggers have access to old and new values. See Oracle Documentation for more information (:old and :new).
CREATE OR REPLACE TRIGGER test_trigger
BEFORE DELETE OR UPDATE ON test_table
FOR EACH ROW
BEGIN
INSERT INTO backup_table (col1, col2) VALUES (:old.col1, :old.col2);
END;
The RETURNING clause on an UPDATE should be able to do this for you. Unfortunately, I don't know the Oracle syntax for referring to the previous value (in PostgreSQL this would be OLD.column_name) but the docs make it sound as if your needs are supported.

Can dynamic SQL be called from a trigger in Oracle?

I have a dozen tables of whom I want to keep the history of the changes. For every one I created a second table with the ending _HISTO and added fields modtime, action, user.
At the moment before I insert, modify or delete a record in this tables I call ( from my delphi app ) a oracle procedure that copies the actual values to the histo table and then do the operation.
My procedure generates a dynamic sql via DBA_TAB_COLUMNS and then executes the generated ( insert into tablename_histo ( fields s ) select fields, sysdate, 'acition', userid from table_name
I was told that I can not call this procedure from a trigger because it has to select the table the trigger is triggered on. Is this true ? Is it possible to implement what I need ?
Assuming you want to maintain history using triggers (rather than any of the other methods of tracking history data in Oracle-- Workspace Manager, Total Recall, Streams, Fine_Grained Auditing etc.), you can use dynamic SQL in the trigger. But the dynamic SQL is subject to the same rules that static SQL is subject to. And even static SQL in a row-level trigger cannot in general query the table that the trigger is defined on without generating a mutating table exception.
Rather than calling dynamic SQL from your trigger, however, you can potentially write some dynamic SQL that generates the trigger in the first place using the same data dictionary tables. The triggers themselves would statically refer to :new.column_name and :old.column_name. Of course, you would have to either edit the trigger or re-run the procedure that dynamically creates the trigger when a new column gets added. Since you, presumably, need to add the column to both the main table and the history table, however, this generally isn't too big of a deal.
Oracle does not allow a trigger to execute a SELECT against the table on which the trigger is defined. If you try it you'll get the dreaded "mutating table" error (ORA-04091), and while there are ways to get around that error they add a lot of complexity for little value. If you really want to build a dynamic query every time your table is updated (IMO this is a bad idea from the standpoint of performance - I find that metadata queries are often slow, but YMMV) it should end up looking something like
strAction := CASE
WHEN INSERTING THEN 'INSERT'
WHEN UPDATING THEN 'UPDATE'
WHEN DELETING THEN 'DELETE'
END;
INSERT INTO TABLENAME_HISTO
(ACTIVITY_DATE, ACTION, MTC_USER,
old_field1, new_field1, old_field2, new_field2)
VALUES
(SYSDATE, strAction, USERID,
:OLD.field1, :NEW.field1, :OLD.field2, :NEW.field2)
Share and enjoy.

PL/SQL embedded insert into table that may not exist

I much prefer using this 'embedded' style inserts in a pl/sql block (opposed to the execute immediate style dynamic sql - where you have to delimit quotes etc).
-- a contrived example
PROCEDURE CreateReport( customer IN VARCHAR2, reportdate IN DATE )
BEGIN
-- drop table, create table with explicit column list
CreateReportTableForCustomer;
INSERT INTO TEMP_TABLE
VALUES ( customer, reportdate );
END;
/
The problem here is that oracle checks if 'temp_table' exists and that it has the correct number of colunms and throws a compile error if it doesn't exist.
So I was wondering if theres any way round that?! Essentially I want to use a placeholder for the table name to trick oracle into not checking if the table exists.
EDIT:
I should have mentioned that a user is able to execute any 'report' (as above). A mechanism that will execute an arbitrary query but always write to the temp_table ( in the user's schema). Thus each time the report proc is run it drops the temp_table and recreates it with, most probably, a different column list.
You could use a dynamic SQL statement to insert into the maybe-existent temp_table, and then catch and handle the exception that occurs when the table doesn't exist.
Example:
execute immediate 'INSERT INTO '||TEMP_TABLE_NAME||' VALUES ( :customer, :reportdate )' using customer, reportdate;
Note that having the table name vary in a dynamic SQL statement is not very good, so if you ensure the table names stay the same, that would be best.
Maybe you should be using a global temporary table (GTT). These are permanent table structures that hold temporary data for an Oracle session. Many different sessions can insert data into the same GTT, and each will only be able to see their own data. The data is automatically deleted either on COMMIT or when the session ends, according to the GTT's definition.
You create the GTT (once only) like this:
create globabal temporary table my_gtt
(customer number, report_date date)
on commit delete/preserve* rows;
* delete as applicable
Then your programs can just use it like any other table - the only difference being it always begins empty for your session.
Using GTTs are much preferable to dropping/recreating tables on the fly - if your application needs a different structure for each report, I strongly suggest you work out all the different structures that each report needs, and create separate GTTs as needed by each, instead of creating ordinary tables at runtime.
That said, if this is just not feasible (and I've seen good examples when it's not, e.g. in a system that supports a wide range of ad-hoc requests from users), you'll have to go with the EXECUTE IMMEDIATE approach.