Oracle Transactions PLSQL - oracle

I am working on PLSQL based Procedures in Oracle 11g & 12c.
I want to keep logs of table name and row count when I issue commit command in one of my procedure/function.
This is for audit logs.
Can you please suggest how do I accomplish this?

Your PL/SQL code will need to keep track of its activity and log it. There is no way to ask Oracle "how many rows are you committing right now and to which tables?"
So, e.g.,
DECLARE
l_row_count NUMBER;
BEGIN
UPDATE table_1 SET column_a = 'whatever' WHERE column_b = 'some condition';
l_row_count := SQL%ROWCOUNT;
INSERT INTO my_audit ( action, cnt ) VALUES ('Updated table_1', l_row_count);
-- Notice the audit is part of the transaction; if I don't commit the UPDATE,
-- I won't commit the log of the update.
-- ... do other similar updates / inserts / deleted, using SQL%ROWCOUNT to
-- to determine the number of rows affected and log each one ...
COMMIT;
END;
Again, it is not practical to do a bunch of DML statements (inserts, updates, deletes) and then ask Oracle after the fact "what I have done so far in this transaction?" You need to record it as you go.

Related

Audit-table-DEPT Trigger

What would happen when this database trigger is fired?
Command (as user SYS):
SQL> CREATE OR REPLACE TRIGGER audit-table-DEPT AFTER
INSERT OR UPDATE OR DELETE ON DEPT FOR EACH ROW
declare
audit_data DEPT$audit%ROWTYPE;
begin
if inserting then audit_data.change_type := 'I';
elsif updating then audit_data.change_type :='U';
else audit_data.change_type := 'D';
end if;
audit_data.changed_by := user;
audit_data.changed_time := sysdate;
case audit_data.change_type
when 'I' then
audit_data.DEPTNO := :new.DEPTNO;
audit_data.DNAME := :new.DNAME;
audit_data.LOC := :new.LOC;
else
audit_data.DEPTNO := :old.DEPTNO;
audit_data.DNAME := :old.DNAME;
audit_data.LOC := :old.LOC;
end case;
insert into DEPT$audit values audit_data;
end;
/
how this can affect normal database operations?
What will happen if you run this command? Nothing, The trigger won't compile as you have given it an invalid object name. (Replace those dashes with underscores.)
After that, you have a trigger which inserts an audit record for DML activity on the DEPT table. When inserting you get an AUDIT_DEPT record with the values of the inserted DEPT record. When deleting you get an AUDIT_DEPT record with the values of the deleted DEPT record. When updating you get a somewhat useless AUDIT_DEPT record which tells you a DEPT record was updated but doesn't identify which one.
how this can affect normal database operations?
It won't cause anything to fail. However, you are executing additional insert statements every time you execute DML on the DEPT table. You probably won't notice the impact on single-row statements, but you might notice a slower response time if you insert, update or delete a large number of DEPT records. You will need to bench mark it.
One last observation:
Command (as user SYS):
Uh oh.
The better interpretation of this statement is that the trigger won't compile, because the SYS schema doesn't have a DEPT table. Connect as the user which owns the DEPT table, then run the CREATE TRIGGER statement.
The worrying option is that the trigger compiles because you have put a DEPT table in its schema. This is bad practice. The SYS schema is maintained by Oracle to run the database internal software. Changing the SYS schema unless authorised by Oracle could corrupt your database and invalidate any support contract you have. What you should do is use SYS (or SYSTEM) to create a user to host your application's objects, then connect as that user to build tables, triggers and whatever else you need.

locks when updating a table with a trigger

I have a table (T1) with a trigger after update. This trigger looks for some values and then it updates other table (T2)
CREATE OR REPLACE TRIGGER t1_AIR AFTER UPDATE ON t1 FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
v_dF1 DATE;
v_dF2 DATE;
v_nDays NUMBER;
v_cReg VARCHAR2(50);
BEGIN
IF :NEW.BN_BE_ESTADO not in ('PEN', 'ERR', 'BAJ') THEN
v_nDays := F_GET_PARAM(NULL, NULL, 'X_DAYS');
PCK_AUX.PR_GET_F1(:NEW.F_A, v_dF1, v_cReg);
PCK_AUX.PR_GET_F2(:NEW.F_A, v_dF2);
UPDATE T2
SET F_F1 = TRUNC(v_dF1),
F_F2 = TRUNC(v_dF2),
F_F_END = PCK_AUX.FU_GET_F_END(v_dF2+1, v_nDays),
F_N_REG = v_cReg
WHERE F_ID = :NEW.F_B;
END IF;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
But when I update T1 it generates locks in both tables (t1 and t2)... does anyone know why??
Thanks to all
When you update a table, row-level(TX) lock and table lock in row-share(RM) and row exclusive mode(RX) are acquired.
Row-level lock is there so other sessions couldn't modify row(s) being updated. Table locks in different modes are there to protect your table structure from modifications while DML statement (update in your case) in progress.
So yes, in your situation rows that are being updated will be locked in both table 1 and table 2 plus table lock will be held in row share and row exclusive mode.
A bit about your trigger...
Why autonomous transaction pragma is there? Unless you are using that trigger for logging for example, situation when you have to commit whether main transaction succeeds or not, you really do not need this pragma. As #William Robertson absolutely correctly pointed out if use autonomous transaction you have to commit this autonomous transaction otherwise ORA-06519 will be raised.
Reconsider usage of when others then null statement - would be better not to hide but re-raise any exceptions that might be generated.

How to update a table using an AFTER INSERT trigger in that same table?

I have 2 triggers on a table A one is BEFORE INSERT and one AFTER INSERT :
CREATE OR REPLACE TRIGGER DEMO_TRG
AFTER INSERT
ON A
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
declare
PRAGMA AUTONOMOUS_TRANSACTION;
begin
UPDATE A
SET STATUS = 'DONE'
WHERE id_id =:new.p_id;
commit;
END;
/
Why the update command in the after insert doesn't work (the status is not set to DONE)? Did I missed something?
Most likely the issue is due to the AUTONOMOUS_TRANSACTION - when you do that, you're effectively switching to a different transaction, which won't know about your uncommitted transaction (ie. your insert), so there is nothing for it to update.
What you should do is amend the column (:new.status := 'DONE') in your before trigger. Or, even better, avoid triggers and have the logic in a stored procedure and don't allow anyone to directly insert into the table, although I appreciate this may be a big switch if you have lots of applications inserting directly into tables etc

How to create a table and insert in the same statement

I'm new to Oracle and I'm struggling with this:
DECLARE
cnt NUMBER;
BEGIN
SELECT COUNT(*) INTO cnt FROM all_tables WHERE table_name like 'Newtable';
IF(cnt=0) THEN
EXECUTE IMMEDIATE 'CREATE TABLE Newtable ....etc';
END IF;
COMMIT;
SELECT COUNT(*) INTO cnt FROM Newtable where id='something'
IF (cnt=0) THEN
EXECUTE IMMEDIATE 'INSERT INTO Newtable ....etc';
END IF;
END;
This keeps crashing and gives me the "PL/SQL: ORA-00942:table or view does not exist" on the insert-line. How can I avoid this? Or what am I doing wrong? I want these two statements (in reality it's a lot more of course) in a single transaction.
It isn't the insert that is the problem, it's the select two lines before. You have three statements within the block, not two. You're selecting from the same new table that doesn't exist yet. You've avoided that in the insert by making that dynamic, but you need to do the same for the select:
EXECUTE IMMEDIATE q'[SELECT COUNT(*) FROM Newtable where id='something']'
INTO cnt;
SQL Fiddle.
Creating a table at runtime seems wrong though. You said 'for safety issues the table can only exist if it's filled with the correct dataset', which doesn't entirely make sense to me - even if this block is creating and populating it in one go, anything that relies on it will fail or be invalidated until this runs. If this is part of the schema creation then making it dynamic doesn't seem to add much. You also said you wanted both to happen in one transaction, but the DDL will do an implicit commit, you can't roll back DDL, and your manual commit will start a new transaction for the insert(s) anyway. Perhaps you mean the inserts shouldn't happen if the table creation fails - but they would fail anyway, whether they're in the same block or not. It seems a bit odd, anyway.
Also, using all_tables for the check could still cause this to behave oddly. If that table exists in another schema, you create will be skipped, but you select and insert might still fail as they might not be able to see, or won't look for, the other schema version. Using user_tables or adding an owner check might be a bit safer.
Try the following approach, i.e. create and insert are in two different blocks
DECLARE
cnt NUMBER;
BEGIN
SELECT COUNT (*)
INTO cnt
FROM all_tables
WHERE table_name LIKE 'Newtable';
IF (cnt = 0)
THEN
EXECUTE IMMEDIATE 'CREATE TABLE Newtable(c1 varchar2(256))';
END IF;
END;
DECLARE
cnt2 NUMBER;
BEGIN
SELECT COUNT (*)
INTO cnt2
FROM newtable
WHERE c1 = 'jack';
IF (cnt2 = 0)
THEN
EXECUTE IMMEDIATE 'INSERT INTO Newtable values(''jill'')';
END IF;
END;
Oracle handles the execution of a block in two steps:
First it parses the block and compiles it in an internal representation (so called "P code")
It then runs the P code (it may be interpreted or compiled to machine code, depending on your architecture and Oracle version)
For compiling the code, Oracle must know the names (and the schema!) of the referenced tables. Your table doesn't exist yet, hence there is no schema and the code does not compile.
To your intention to create the tables in one big transaction: This will not work. Oracle always implicitly commits the current transaction before and after a DDL statement (create table, alter table, truncate table(!) etc.). So after each create table, Oracle will commit the current transaction and starts a new one.

Alternative for 'ddl_lock_timeout' in oracle 10g

In oracle 11g it's allowed to set session and system parameter, which's called ddl_lock_timeout. It's very useful when you need to execute some statement and resources are highly used (in order to avoid ORA-00054 exception).
But the case is that there's no such a parameter in 10g.
Of course, I'm able to use such a cosntruction as:
DECLARE START_DATE DATE := SYSDATE;
BEGIN
LOOP
IF SYSDATE>START_DATE+30/60/60/24 THEN
EXIT;
END IF;
BEGIN
<some statement>
EXIT;
EXCEPTION WHEN OTHERS THEN
IF sqlcode != -54 THEN
RAISE;
END IF;
END;
END LOOP;
END;
And by using it, I will try to execute the statement for 30 seconds in a cycle, but the thing here is that the statement is executed many many times and could cause some troubles (i'm not sure but I feel it somehow), but using ddl_lock_timeout the statement is executed only once and then waits for resources which is much fluffier.
Any ideas?
This is an explanation of why LOCK TABLE does not necessarily work.
LOCK TABLE - trick may not be working in all situations.
Session 1: Session 2:
create table table1(a number);
insert into table1 values(1);
commit;
update table1 set a = 2;
lock table table1 in exclusive mode;
<waits...>
commit;
"Table(s) Locked."
update table1 set a = 3; <-- notice session 1 goes in queue now.
DDL fails with "resource busy with NOWAIT".
Reason is DDL first commit the previous transaction
before executing the DDL. And when it commits
session 1 gets the lock as it was already in queue.

Resources