I have a PL/SQL script where it runs a bunch of commands, and calls commit at the very end.
select count(*) into countCol from USER_TAB_COLUMNS where TABLE_NAME = 'EVAPP_CARLETON_RESULT' and COLUMN_NAME = 'AMTFIN' and DATA_SCALE is null;
IF (countCol <> 0) then
execute immediate 'alter table EVAPP_CARLETON_RESULT add AMTFIN_TMP NUMBER(10,2)' ;
execute immediate 'update EVAPP_CARLETON_RESULT set AMTFIN_TMP = AMTFIN' ;
execute immediate 'alter table EVAPP_CARLETON_RESULT drop column AMTFIN' ;
execute immediate 'alter table EVAPP_CARLETON_RESULT rename column AMTFIN_TMP to AMTFIN' ;
DBMS_OUTPUT.put_line('This column EVAPP_CARLETON_RESULT.AMTFIN has been modified to the required precision');
END IF;
logger('68');
evaluate.commitScript;
There are 68 such blocks prior to this but evaluate.commitScript is called at the very end.
But, logger is called after every such block, and it has a commit statement inside of it.
create or replace
PROCEDURE LOGGER
(MESSAGE1 IN VARCHAR2 ) AS
pragma autonomous_transaction;
BEGIN
insert into message_log (datetime, message) values (sysdate, message1);
commit;
END LOGGER;
The commit commits all changes simultaneously. Not just the changes made by the procedure. Is there anyway, we can call commit selectively?
It looks like you already have the solution with this line in the LOGGER proc:
pragma autonomous_transaction
That statement sets a separate transaction within the procedure. The COMMIT issued there will not commit any transactions outside of that procedure.
Also, the DDL in the EXECUTE IMMEDIATE statements are implicitly committed.
The commit in the LOGGER procedure will only commit the changes made by that call of the LOGGER procedure.
If the snippet you posted is representative of the 68 blocks, the problem is that DDL statements (like ALTER TABLE) implicitly issue two commits-- one before the statement is executed and one after the execution if the execution was successful. That means that DDL cannot be done transactionally and the evaluate.commitScript call is neither committing nor rolling back any changes.
Depending on the Oracle version, edition, and configuration, you may be able to use something like Oracle flashback database to create a restore point before running your script and the flashing back to that restore point if your script fails (though that would also roll back the changes made by your LOGGER procedure).
Related
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.
I recently started working on a number of large Oracle PL/SQL stored procedures with Toad for Oracle. Number of these procedures updates and inserts stuff into tables. My question is, is there a way to "safely" execute PL/SQL procedures without permanently modifying any of the tables ? Also, how do I safely modify and execute stored procedures for experimentation without actually making changes to the database ?
Doesn't matter if you have Toad or SQ*Plus or anything really - it's all about the code.
First - does your program have any commits or rollbacks IN the stored procedures?
Second - does your program do any DDL work: create a table? That will do an implicit COMMIT. Mind you, if your program calls another program and THAT program has a COMMIT or DDL - you're COMMITTED as its' all in one session.
Third - when you go to execute your stored procedure, does your anonymous block have a COMMIT or ROLLBACK there?
Your tool comes into play for the third bit. Inspect the code behind the 'execute' button.
In SQL Developer (similar to Toad in this regard)...
In this case my SP has a commit in the code - so barring an exception before that line...it's a permanent change.
In the generated anonymous block, there's a ROLLBACK, but it's commented out. When you hit the execute button in your GUI, look at the code there. Change it if necessary.
You can create a copy of your database, then play there. Other thing is, you can create a copy of the procedures/functions, packages and tables involve and play with it.
Let's you have this procedure,
CREATE PROCEDURE proc1
IS
BEGIN
INSERT INTO table1
(col1, col2)
VALUES
('actual data', 'hello');
UPDATE table2
SET col1 = 'actual'
WHERE col2 = 1;
COMMIT;
END;
You will create new procedure with same logic inside it.
CREATE PROCEDURE proc1_test
IS
BEGIN
INSERT INTO table1_test
(col1, col2)
VALUES ('test', 'hello');
UPDATE table2_test
SET col1 = 'test2'
WHERE col2 = 1;
COMMIT;
END;
/
Doing this, it will let you compare your actual data to your test data.
You can put rollback at the end of the procedure and comment any Commits/DDL statements. Also you need to be careful for the Pragma statements if any.
I need to delete one or more row from list of tables stored in a table, and commit only if all deletion succeed.
So I wrote something like this (as part of a bigger procedure):
BEGIN
SAVEPOINT sp;
FOR cur_table IN (SELECT * FROM TABLE_OF_TABLES)
LOOP
EXECUTE IMMEDIATE 'DELETE FROM ' || cur_table.TABNAME || ' WHERE ID = :id_bind'
USING id;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK TO SAVEPOINT sp;
END;
I know this couldn't work, because of the "execute immediate".
So, what is the correct way to do that?
Dynamic SQL (Execute Immediate) doesn't commit the transaction. You have to commit explicitly. Your code is fine but it doesn't record/log the errors in case if they occur.
Log the errors in the exception handler section.
Honestly, this sounds like a bad idea. Since you have the tables stored in the database, why not just list them out in your procedure? Surely, you're not adding tables that often that this procedure would need to be updated often?
As pointed out in the comments, this will work fine, since EXECUTE IMMEDIATE does not automatically commit.
Don't forget to add a RAISE at the end of your exception block, or you'll never know that an error happened.
I have 3 tables in oracle DB. I am writing one procedure to delete some rows in all the 3 tables based on some conditions.
I have used all three delete statements one by one in the procedure. While executing the mentioned stored procedure, is there any auto-commit happening in the at the time of execution?
Otherwise, Should I need to manually code the commit at the end?
There is no auto-commit on the database level, but the API that you use could potentially have auto-commit functionality. From Tom Kyte.
That said, I would like to add:
Unless you are doing an autonomous transaction, you should stay away from committing directly in the procedure: From Tom Kyte.
Excerpt:
I wish PLSQL didn't support commit/rollback. I firmly believe
transaction control MUST be done at the topmost, invoker level. That
is the only way you can take these N stored procedures and tie them
together in a transaction.
In addition, it should also be noted that for DDL (doesn't sound like you are doing any DDL in your procedure, based on your question, but just listing this as a potential gotcha), Oracle adds an implicit commit before and after the DDL.
There's no autocommit, but it's possible to set commit command into stored procedure.
Example #1: no commit
create procedure my_proc as
begin
insert into t1(col1) values(1);
end;
when you execute the procedure you need call commit
begin
my_proc;
commit;
end;
Example #2: commit
create procedure my_proc as
begin
insert into t1(col1) values(1);
commit;
end;
When you execute the procedure you don't nee call commit because procedure does this
begin
my_proc;
end;
There is no autocommit with in the scope of stored procedure. However if you are using SQL Plus or SQL Developer, depending on the settings autocommit is possible.
You should handle commit and rollback as part of the stored procedure code.
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.