How to drop and recreate Indexes inside PLSQL Stored Procedure - oracle

In PLSQL stored procedure, I would like to drop indexes and truncate the table
before inserting data into the table. Then I want to recreate the indexes afterwards . What is the best way of achieving this?
I need something similar like this
Begin:
Truncate Table
Drop index1
Drop index2
loop
--- other code
Insert data
commit;
end loop;
Create index1
Create index2
End;

We can't run DDL directly in PL/SQL.
The best approach is to use the Oracle built-in DBMS_UTILITY.EXEC_DDL_STATEMENT(). It's covered in the docs Find out more. But basically:
begin
DBMS_UTILITY.EXEC_DDL_STATEMENT('drop index index1')
DBMS_UTILITY.EXEC_DDL_STATEMENT('drop index index2')
DBMS_UTILITY.EXEC_DDL_STATEMENT('truncate table your_table')
....
Note that the commands are strings not SQL.
If you're inserting enough data to make it worthwhile dropping and re-creating the indexes then you probably want set operations rather than doing it in a loop. Set operations work on chunks of data rather then individual rows. Quite what approach is appropriate depends on how much data you have.
"I want to insert data from staging table and then joining with other table"
So the most efficient route is likely to be
insert into your_table
select /*+ append */ ....
from staging_table
join other_table
on ...
However, depending on your definition of "huge" you may need to use a PL/SQL bulk operation, with a FORALL insertion. That would require reading from your staging table in chunks, so it would entail a loop. Find out more

You can use dynamic SQL , specifically, execute immediate, based on your requirements:
Begin:
execute immediate 'truncate table statement';
execute immediate 'DROP index statement';
loop
--- other code
Insert data
commit;
end loop;
execute immediate 'Create index1 statement';
execute immediate 'Create index2 statement';
End;

Related

Mutating table exception is not occurring

I have created a trigger and was expecting a mutating table error in below case but didn't get one through normal insert but getting an error while inserting using a query. I am not sure which concept I am missing here.
drop table temp;
create table temp (id number,name varchar2(500),is_active number);
create or replace trigger temp_trg before insert on temp
for each row
declare
v_count number;
begin
select count(1) into v_count from temp;
update temp set is_active=0 where is_active=1 and id=:new.id;
end;
/
select * from temp;
insert into temp values (1,'xyz',1);
insert into temp values (1,'xyz',1);
insert into temp select 1,'xyz',1 from dual;
getting an error while inserting using a query.
Mutating table occurs when we query the table which owns the trigger. Specifically it happens when Oracle can't guarantee the outcome of the query. Now when you insert a single row into the table Oracle can predict the outcome of the query when the FOR EACH ROW trigger fires, because it's a single row.
But with an INSERT FROM query Oracle is confused: should the count be the final figure including all the rows selected by the query or just a rolling count? The answer seems straightforward in this case but it's easy to imagine other queries where the answer is not clear cut. Rather than evaluate each query on its predictability Oracle enforces a plain fiat and hurls ORA-04091 for all query driven inserts.
The restriction on Mutating Tables applies to all triggers that use FOR EACH ROW clause except when either of the following is true:
the trigger fires on BEFORE event, i.e. the data wasn't actually changed
it is known that only one row will be affected - INSERT ... VALUES is the only DML that meets this condition

Oracle how to delete from a table except few partitions data

I have a big table with lot of data partitioned into multiple partitions. I want to keep a few partitions as they are but delete the rest of the data from the table. I tried searching for a similar question and couldn't find it in stackoverflow. What is the best way to write a query in Oracle to achieve the same?
It is easy to delete data from a specific partition: this statement clears down all the data for February 2012:
delete from t23 partition (feb2012);
A quicker method is to truncate the partition:
alter table t23 truncate partition feb2012;
There are two potential snags here:
Oracle won't let us truncate partitions if we have foreign keys referencing the table.
The operation invalidates any partitioned Indexes so we need to rebuild them afterwards.
Also, it's DDL, so no rollback.
If we never again want to store data for that month we can drop the partition:
alter table t23 drop partition feb2012;
The problem arises when we want to zap multiple partitions and we don't fancy all that typing. We cannot parameterise the partition name, because it's an object name not a variable (no quotes). So leave only dynamic SQL.
As you want to remove most of the data but retain the partition structure truncating the partitions is the best option. Remember to invalidate any integrity constraints (and to reinstate them afterwards).
declare
stmt varchar2(32767);
begin
for lrec in ( select partition_name
from user_tab_partitions
where table_name = 'T23'
and partition_name like '%2012'
)
loop
stmt := 'alter table t23 truncate partition '
|| lrec.partition_name
;
dbms_output.put_line(stmt);
execute immediate stmt;
end loop;
end;
/
You should definitely run the loop first with execute immediate call commented out, so you can see which partitions your WHERE clause is selecting. Obviously you have a back-up and can recover data you didn't mean to remove. But the quickest way to undertake a restore is not to need one.
Afterwards run this query to see which partitions you should rebuild:
select ip.index_name, ip.partition_name, ip.status
from user_indexes i
join user_ind_partitions ip
on ip.index_name = i.index_name
where i.table_name = 'T23'
and ip.status = 'UNUSABLE';
You can automate the rebuild statements in a similar fashion.
" I am thinking of copying the data of partitions I need into a temp
table and truncate the original table and copy back the data from temp
table to original table. "
That's another way of doing things. With exchange partition it might be quite quick. It might also be slower. It also depends on things like foreign keys and indexes, and the ratio of zapped partitions to retained ones. If performance is important and/or you need to undertake this operation regularly then you should to benchmark the various options and see what works best for you.
You must very be careful in drop partition from a partition table. Partition table usually used for big data tables and if (and only if) you have a global index on the table, drop partition make your global index invalid and you should rebuild your global index in a big table, this is disaster.
For minimum side effect for queries on the table in this scenario, I first delete records in the partition and make it empty partition, then with
ALTER TABLE table_name DROP PARTITION partition_name UPDATE GLOBAL INDEXES;
drop empty partition without make my global index invalid.

Using PL/SQL associative arrays

create or replace aArr is TABLE of varchar2 index by binary_integer;
create or replace bArr is TABLE of varchar2 index by binary_integer;
create or replace prc(oname aArr, iname bArr) as
begin
--Now i have two arrays
-- i want to insert or update into table using these two arrays
-- How can i do that with out using the loops.
-- is there any bulk insert or update.
end
Now I have two arrays. I want to insert or update into table using these two arrays. How can I do that with out using the loops? Is there any bulk insert or update?
If you have PL/SQL associative arrays, you can use bulk processing to insert the data into underlying database tables using FORALL.
The oracle documantation is here:
http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/forall_statement.htm
The syntax is similar to:
FORALL x IN INDICES OF <associative_array_name>
-- DML (INSERT or UPDATE etc)
It's a bit of a generic answer but you have asked a very generic question.
Hope this helps...

How do I UPDATE a large table in oracle pl/sql in batches to avoid running out of undospace?

I have a very large table (5mm records). I'm trying to obfuscate the table's VARCHAR2 columns with random alphanumerics for every record on the table. My procedure executes successfully on smaller datasets, but it will eventually be used on a remote db whose settings I can't control, so I'd like to EXECUTE the UPDATE statement in batches to avoid running out of undospace.
Is there some kind of option I can enable, or a standard way to do the update in chunks?
I'll add that there won't be any distinguishing features of the records that haven't been obfuscated so my one thought of using rownum in a loop won't work (I think).
If you are going to update every row in a table, you are better off doing a Create Table As Select, then drop/truncate the original table and re-append with the new data. If you've got the partitioning option, you can create your new table as a table with a single partition and simply swap it with EXCHANGE PARTITION.
Inserts require a LOT less undo and a direct path insert with nologging (/+APPEND/ hint) won't generate much redo either.
With either mechanism, there would probably sill be 'forensic' evidence of the old values (eg preserved in undo or in "available" space allocated to the table due to row movement).
The following is untested, but should work:
declare
l_fetchsize number := 10000;
cursor cur_getrows is
select rowid, random_function(my_column)
from my_table;
type rowid_tbl_type is table of urowid;
type my_column_tbl_type is table of my_table.my_column%type;
rowid_tbl rowid_tbl_type;
my_column_tbl my_column_tbl_type;
begin
open cur_getrows;
loop
fetch cur_getrows bulk collect
into rowid_tbl, my_column_tbl
limit l_fetchsize;
exit when rowid_tbl.count = 0;
forall i in rowid_tbl.first..rowid_tbl.last
update my_table
set my_column = my_column_tbl(i)
where rowid = rowid_tbl(i);
commit;
end loop;
close cur_getrows;
end;
/
This isn't optimally efficient -- a single update would be -- but it'll do smaller, user-tunable batches, using ROWID.
I do this by mapping the primary key to an integer (mod n), and then perform the update for each x, where 0 <= x < n.
For example, maybe you are unlucky and the primary key is a string. You can hash it with your favorite hash function, and break it into three partitions:
UPDATE myTable SET a=doMyUpdate(a) WHERE MOD(ORA_HASH(ID), 3)=0
UPDATE myTable SET a=doMyUpdate(a) WHERE MOD(ORA_HASH(ID), 3)=1
UPDATE myTable SET a=doMyUpdate(a) WHERE MOD(ORA_HASH(ID), 3)=2
You may have more partitions, and may want to put this into a loop (with some commits).
If I had to update millions of records I would probably opt to NOT update.
I would more likely create a temp table and then insert data from old table since insert doesnt take up a lot of redo space and takes less undo.
CREATE TABLE new_table as select <do the update "here"> from old_table;
index new_table
grant on new table
add constraints on new_table
etc on new_table
drop table old_table
rename new_table to old_table;
you can do that using parallel query, with nologging on most operations generating very
little redo and no undo at all -- in a fraction of the time it would take to update the
data.

Stored Procedure for copying data from one table to another

I have pairs of tables in the format TABLE and TABLE_TWIN now
TABLE is the main table with lots of data
TABLE_TWIN is a table with the exact same fields with a little data (different data)
Now I would like to copy all rows from TABLE_TWIN to TABLE using a stored procedure. I have many such tables and could like the stored procedure to take the table name(s) as parameter(s) so that I can use the same procedure for each table pair. I do not want to write long INSERT statements because these tables have around 50 attributes each.
I am not good with PL/SQL so I need some help here.
Thanks!
SQL is not so long... But if you prefer a procedure, here it is:
create or replace procedure table_copy(
p_tab_from varchar2,
p_tab_to varchar2)
is
begin
execute immediate 'insert into '||p_tab_to||' (select * from '||p_tab_from||')';
end;
insert into table_twin (select * from table)
should do it

Resources