PL/SQL "WHERE CURRENT OF" vs "ROWID" - oracle

Why Oracle made "where current of" syntax when you can use "rowid"? Example:
BEGIN
FOR rec IN (SELECT t.column1, t.rowid rid FROM test_table) LOOP
UPDATE test_table tb SET column1 = some_function(rec.column1) WHERE tb.rowid = rec.rid;
END LOOP;
COMMIT;
END;
DECLARE
CURSOR cur IS SELECT t.column1 FROM test_table;
param1 test_table.column1%TYPE;
BEGIN
LOOP
FETCH cur INTO param1;
UPDATE test_table tb SET tb.column1 = some_function(param1) WHERE CURRENT OF cur;
EXIT WHEN cur%NOTFOUND;
END LOOP;
COMMIT;
END;

Where Current Of is used to identify LAST FETCHED ROW in cursor. It's more safe, because You have 100% confidence, that f.e. You updating LAST FETCHED ROW from curosr. With Rowids there's danger, because it's really easy to mess up something.

Using WHERE CURRENT OF clause without having FOR UPDATE clause mentioned in your SELECT statement could be risky. Reason behind that is when you are not applying FOR UPDATE clause then you are not exclusively placing Row level lock to those rows which you are intending to update in the following UPDATE DML. And hence it opens an opportunity for outside world of violating data consistency i.e. some other user from different session may be looking to UPDATE same rows of your targeted table.
Also, in you learn more about WHERE CURRENT OF clause you will notice that during this clause Oracle internally makes use of ROWID's only to reach/identify the rows which needs to be updated.
Hope it helps !! Happy Programming

Related

How to make a cursor pick table data change?

I have the following cursor in a procedure :
procedure Run is
Cur Cursor is select * from table where condition;
R Cur%rowtype;
Open Cur;
loop
fetch Cur into R;
exit when Cur%notfound;
-- Run some time consuming operations here
something...
end loop;
Close Cur;
end;
This cursor is run a scheduled job.
Assume when running this cursor there are 100 rows that satisfy the where condition.
If, while the procedure is running, I have a new rows inserted in the table that satisfies the same where condition, Is there any way that cursor picks also these new row please ?
Thanks.
Cheers,
No.
The set of rows the cursor will return is determined at the time the cursor is opened. At that point, Oracle knows the current SCN (system change number) and will return the data as it existed at that point in time.
Depending on the nature of the problem, you could write a loop that just keeps asking for a single row that meets the criteria (assuming your time-consuming operation updates some data so that you know what needs to be processed). Something like
loop
begin
select some_id
into l_some_id
from your_table
where needs_processing = 'Y'
order by some_id
fetch first row only;
exception
when no_data_found
then
l_some_id := null;
end;
exit when l_some_id is null;
some_slow_operation( l_some_id );
end loop;
assuming that some_slow_operation changes the needs_processing flag to N. And assuming that you are using the default read committed transaction isolation level.
You can have commit inside loop so that select query fetches latest records from table in every iteration.
No, a cursor can't do that. The transactions are consistent and your cursor is a snapshot of the data you've extracted.
If you want consistent results you could either:
Lock the table so that there will be no changes,
Use other mechanism e.g. move the logic to a trigger, which will execute on each new piece of data that satisfies your conditions (and bring overhead too so very situational)

WITH Hold Option in Oracle

I am learning Oracle.
CURSOR A FOR TABLE1
LOOP
DELETE B TABLE RECORD BASED ON CURSOR A
END LOOP
I am reading a Cursor from Table A, and based on the result i am deleting 2 more table entries. Please let me know in this case i need to provide FOR UPDATE OF while declaring Oracle or Its not needed. In DB2 i know we need to give with hold option.
How to get the current user details in the query in oracle. In sql server if we give USER then the details will come.
This link may be useful while learning Oracle: http://www.oracle.com/pls/db112/portal.all_books
Open this link, click on PL/ at the top of the page, then open PL/SQL Language Reference,
and in chapter 13 PL/SQL Language elements find DELETE Statement extension:
http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/delete_statement.htm#LNPLS1187
Basically, to delete a record fetched from a cursor, a cursor must use FOR UPDATE clause, and a delete command must have WHERE CUFFENT OF <cursor-variable> clause.An example:
CURSOR c1 IS
SELECT employee_id, job_id, salary
FROM employees FOR UPDATE;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO my_emp_id, my_job_id, my_sal;
EXIT WHEN c1%NOTFOUND;
IF my_job_id = 'SA_REP' THEN
DELETE employees
WHERE CURRENT OF c1;
END IF;
END LOOP;
END;

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.

use of FOR UPDATE statement

I am using PL/SQL (Oracle 11g) to update the EMPLOYEES table salary column.
I have used two separate scripts to do the same thing i.e update the salary of employees.
One script uses FOR UPDATE OF statement where as another script doesn't uses it. In both cases I found that oracle holds the row level locks until we execute the ROLLBACK or COMMIT commands.
Then what is the difference in between two scripts?
Which one is better to use?
Here are the two scripts I am talking about:
-- Script 1: Uses FOR UPDATE OF
declare
cursor cur_emp
is
select employee_id,department_id from employees where department_id = 90 for update of salary;
begin
for rec in cur_emp
loop
update Employees
set salary = salary*10
where current of cur_emp;
end loop;
end;
--Script 2: Does the same thing like script 1 but FOR UPDATE OF is not used here
declare
cursor cur_emp
is
select employee_id,department_id from employees where department_id = 90;
begin
for rec in cur_emp
loop
update Employees
set salary = salary*10
where Employee_ID = rec.employee_id;
end loop;
end;
I found that Oracle acquired the row level locks on both cases. So, what is the benefit of using FOR UPDATE OF and Which is the better way of coding?
When you specify FOR UPDATE, the row is locked at the point that you SELECT the data. Without the FOR UPDATE, the row is locked at the point you UPDATE the row. In the second script, another session could potentially lock the row between the time that the SELECT was executed and the point that you tried to UPDATE it.
If you are dealing with a SELECT statement that returns relatively few rows and a tight inner loop, it is unlikely that there will be an appreciable difference between the two. Adding a FOR UPDATE on the SELECT also gives you the opportunity to add a timeout clause if you don't want your script to block indefinitely if some other session happens to have one of the row you're trying to update locked.

Oracle pl sql 10g - move set of rows from a table to a history table with same structure

PL SQL moves older versions of data from a transaction table to a history table of same structure and archive for a certain period -
for each record
insert into tab_hist (select older_versions of current row);
delete from tab (select older_versions of current row);
END
ps: earlier we were not archiving(no insert) - but after adding the insert it has doubled the run time - so can we accomplish insert and delete with a single select statement? as there is large data to be processed and across multiple table
This is a batch operation, right? In which case you should avoid Row By Row and use set processing. SQL is all about The Joy Of Sets.
Oracle has fantastic bulk SQL processing capabilities. The pseudo code you paosted would look something like this:
declare
cursor c_oldrecs is
select * from your_table
where criterion between some_date and some_other_date;
type rec_nt is table of your_table%rowtype;
oldrecs_coll rec_nt;
begin
open c_oldrecs;
loop
fetch c_oldrecs into oldrecs_coll limit 1000;
exit when oldrecs_coll.count() = 0;
forall i in oldrecs_coll.first() oldrecs_coll.last()
insert into your_table_hist
values oldrecs_coll(i);
forall i in oldrecs_coll.first() oldrecs_coll.last()
delete from your_table
where pk_col = oldrecs_coll(i).pk_col;
end loop;
end;
/
This bulk processing is faster because it sends one thousand statements to the database at a time, instead of switching between PL/SQL and SQL one thousand times. The LIMIT 1000 clause is there to prevent a really huge selection blowing the PGA. This safeguard may not be necessary in your case, or perhaps you can work with a higher value.
I think your current implementation is wrong. It is better to keep only the current version in the live table, and to keep all the historical versions in a separate table from the off. Use triggers to maintain the history as part of every transaction.
It may be that the slowness you are seeing is due to the logic that selects which rows are to be moved. If so, you might get better results by doing the select once to get the rowids into a nested table in memory, then doing the insert and the delete based on that list; or alternatively, driving your loop with a query that selects the rows to be moved.
You might instead consider creating a trigger on insert that will move the existing rows that "match" the row being inserted. This will slow down the inserts somewhat, but would mean you don't need any process to move the old rows in bulk.
If you are on Enterprise edition with the partitioning option, look at partition exchange.
As simple as this
CREATE BACKUP_TAB AS SELECT * FROM TAB
If you are deleting a lot of rows you will be hitting your undo tablespace and a delete which deletes say 100k rows can cause performance issues. You are better of deleting by batch say 5k rows at a time and committing.
BEGIN
-- Where condition on insert and delete must be the same
loop
INSERT INTO BACKUP_TAB SELECT * FROM TAB WHERE 1=1 and rownum < 5000; --Your condition here
exit when SQL%rowcount < 4999;
commit;
end loop;
loop
DELETE FROM TAB
where 1=1--Your condition here
and rownum < 5000;
exit when SQL%rowcount < 4999;
commit;
end loop;
commit;
END;

Resources