PLSQL - FORALL in immediate execute - oracle

I want to use execute immediate for the forall syntax as i want to use the variable 'schemasname' instead of 'dcbhmoh1'.
Thanks
DECLARE
schemasname varchar2(25) := 'dcbhmoh1';
datet char(6) := '102024';
TYPE anames IS table of dcbhmoh1.F01131M%ROWTYPE;
ar anames;
BEGIN
EXECUTE IMMEDIATE 'SELECT * FROM ' || schemasname || '.F01131M WHERE ZMDTI >= ' || datet BULK COLLECT INTO ar;
dbms_output.put_line(ar.count);
IF ar.Count > 0 THEN
EXECUTE IMMEDIATE 'TRUNCATE TABLE ' || schemasname || '.F01131M';
FORALL i in ar.first .. ar.last INSERT INTO dcbhmoh1.F01131M VALUES ar(i);
END IF;
END;

You could try to define schema for your work session with
EXECUTE IMMEDIATE 'Alter session set current_schema='|| schemasname ;
Like this:
DECLARE
schemasname varchar2(25) := 'dcbhmoh1';
datet char(6) := '102024';
TYPE anames IS table of dcbhmoh1.F01131M%ROWTYPE;
ar anames;
BEGIN
-- set schema
EXECUTE IMMEDIATE 'Alter session set current_schema='|| schemasname ;
-- do your work
EXECUTE IMMEDIATE 'SELECT * FROM F01131M WHERE ZMDTI >= ' || datet BULK COLLECT INTO ar;
dbms_output.put_line(ar.count);
IF ar.Count > 0 THEN
EXECUTE IMMEDIATE 'TRUNCATE TABLE F01131M';
FORALL i in ar.first .. ar.last INSERT INTO F01131M VALUES ar(i);
END IF;
END;
(not sure about the complete syntax of the FORALL though).

Related

how to provide multiple bind variable for execute immediate inside pl/sql block

I have a table which contains the metadata of the table. The task is to periodically delete a specific set of tables, provided the information for where condition and how many days the data is retained are present.If a user needs to delete a data on daily basis, he simply enter his table name in audit log. The procedure will do the rest. The example is shown below.
Table structure:
CREATE TABLE delete_tbl_list (
id NUMBER NOT NULL,
table_name VARCHAR2(100) NOT NULL,
column_name VARCHAR2(100) NOT NULL,
day_retented NUMBER NOT NULL,
where_clause VARCHAR2(2000)
);
the day_retended is the number which will tell on how many days the data can hold.
select * from delete_tbl_list
ID TABLE_NAME COLUMN_NAME DAY_RETENTED WHERE_CLAUSE
---------- -----------------------------------------------
1 audit_log log_TS 60
So if i need to delete a table taking log_ts(timestamp) as column with 60days period as retention. The table in query needs to do
delete * from audit_log where log_ts<systimestamp -60
Now i need to do it using bulk delete and more dynamic and hence i wrote the procedure below,
create procedure dynamic_mass_delete as
TYPE tbl_rec_rowid IS TABLE OF ROWID INDEX BY PLS_INTEGER;
lv_del_exec_rec tbl_rec_rowid;
v_limit PLS_INTEGER := 10000;
m_date date:=sysdate;
total_records_deleted number:=0;
l_where delete_tbl_list.where_clause%type;
l_sql varchar2(2000);
TYPE ref_cur_type IS REF CURSOR;
delete_content ref_cur_type;
BEGIN
for i in (select table_name,COLUMN_NAME,DAY_RETENTED,WHERE_CLAUSE from delete_tbl_list) loop
DBMS_OUTPUT.PUT_LINE('tablename..'||i.table_name);
l_where:='';
m_date:=m_date-i.day_retented;
if i.where_clause is not null then
l_where:=' and '||i.where_clause;
end if;
OPEN delete_content FOR 'SELECT rowid from ' || i.table_name ||' where '|| i.COLUMN_NAME || ' <= to_timestamp('''||m_date||''')'||l_where;
LOOP
total_records_deleted := 0;
FETCH delete_content BULK COLLECT INTO lv_del_exec_rec LIMIT v_limit;
FORALL j IN lv_del_exec_rec.first..lv_del_exec_rec.last
execute immediate 'DELETE FROM :1 where rowid=:2 'using i.table_name,lv_del_exec_rec(j);
total_records_deleted := total_records_deleted + SQL%rowcount;
DBMS_OUTPUT.PUT_LINE('Delete count..'||total_records_deleted);
EXIT WHEN delete_content%notfound;
END LOOP;
CLOSE delete_content;
end loop;
EXCEPTION
when others then
DBMS_OUTPUT.PUT_LINE('Error-->'||SQLERRM);
END;
/
Now i getting error in the delete query stating invalid table name, i was not able to write dbms_output inside for all statment. Is it possible to use multiple bind variable inside a pl/sql procedure.
The error which i get is ,
Error-->ORA-00942: table or view does not exist
PL/SQL procedure successfully completed.
The table very much exists, but it is throwing error, i was not able to print inside the forall block too.
Switch to
execute immediate 'DELETE FROM ' || i.table_name ||' where rowid = ' || lv_del_exec_rec(j);
You can simplify your code as follows:
BEGIN
FOR TBL_DETAILS IN (
SELECT
TABLE_NAME,
COLUMN_NAME,
DAY_RETENTED,
WHERE_CLAUSE
FROM
DELETE_TBL_LIST
) LOOP
DBMS_OUTPUT.PUT_LINE('tablename..' || TBL_DETAILS.TABLE_NAME);
EXECUTE IMMEDIATE 'DELETE FROM '
|| TBL_DETAILS.TABLE_NAME
|| ' WHERE '
|| TBL_DETAILS.COLUMN_NAME
|| ' < SYSTIMESTAMP - '
|| TBL_DETAILS.DAY_RETENTED
|| CASE
WHEN TBL_DETAILS.WHERE_CLAUSE IS NOT NULL THEN ' AND ' || TBL_DETAILS.WHERE_CLAUSE
END;
DBMS_OUTPUT.PUT_LINE('Delete count..' || SQL%ROWCOUNT);
END LOOP;
END;
/
Hope, This will help you in creating simpler code.
Cheers!!

execute immediate using cursor

I've number of tables that i want to drop some columns and add some another columns again. (oracle database)
All of tables are empty.
does it work??
DECLARE
CURSOR cursor_name
IS
SELECT TABLE_NAME
FROM SYS.ALL_TABLES
WHERE OWNER = 'username';
TN NVARCHAR2 (30);
TABLE_COUNT NUMBER (3);
TCDROP NVARCHAR2 (1000);
TCADD NVARCHAR2 (1000);
BEGIN
SELECT COUNT (1)
INTO TABLE_COUNT
FROM SYS.ALL_TABLES
WHERE OWNER = 'username';
OPEN cursor_name;
FOR i IN 1 .. TABLE_COUNT
LOOP
FETCH cursor_name INTO TN;
TCDROP := 'ALTER TABLE ' || TN || ' DROP (*columns list*);';
EXECUTE IMMEDIATE TCDROP;
TCADD :=
'ALTER TABLE ' || TN || ' ADD (*columns and datatype list*);';
EXECUTE IMMEDIATE TCADD;
EXIT WHEN cursor_name%NOTFOUND;
END LOOP;
CLOSE cursor_name;
END;
/
Yes, this will certainly work. I'd recommend to put in the owner/schema of the tables, though. Besides, it is unusual or unsafe to query the number of tables and the loop through the list. I'd put it in a simple for loop:
DECLARE
stmt VARCHAR2(1000);
BEGIN
FOR t IN (SELECT table_name FROM all_tables WHERE owner='XYZ') LOOP
stmt := 'ALTER TABLE '||owner||'.'||table_name||' DROP (*columns list*);';
DBMS_OUTPUT.PUT_LINE(stmt);
EXECUTE IMMEDIATE (stmt);
stmt:= 'ALTER TABLE '||owner||'.'||table_name||' ADD (*columns and datatype list*);';
DBMS_OUTPUT.PUT_LINE(stmt);
EXECUTE IMMEDIATE (stmt);
END LOOP;
END;
/

execute immediate alter sequence not working

I'm stuck on this pretty simple script. It isn't working like I expect it to.
declare
st VARCHAR(1024);
begin
for x in (SELECT sequence_name FROM USER_SEQUENCES) loop
st := 'ALTER SEQUENCE ' || x.sequence_name || ' INCREMENT BY 1000';
execute immediate st;
st := 'select ' || x.sequence_name || '.nextval from dual';
execute immediate st;
st := 'ALTER SEQUENCE ' || x.sequence_name || ' INCREMENT BY 1';
execute immediate st;
end loop;
end;
/
When I run this it doesn't appear to work at all - all of my sequences just stay as they are, and they have not been incremented by a thousand by the dynamic statements. If I check nextval before and after the anonymous block, the difference is only 1, not 1001.
If I replace execute immediate with dbms_output.put_line and execute the generated commands manually the sequences are altered as I want.
What am I missing?
Both alter sequence statements are working, it's the increment in between that isn't happening. The nextval call in your loop is not being evaluated because the select statement isn't sending its output anywhere. From the documentation, a note that happens to refer to exactly what you are doing:
Note:
If dynamic_sql_statement is a SELECT statement, and you omit both into_clause and bulk_collect_into_clause, then execute_immediate_statement never executes.
For example, this statement never increments the sequence:
EXECUTE IMMEDIATE 'SELECT S.NEXTVAL FROM DUAL'
So you need to select that value into something:
declare
st VARCHAR(1024);
val number;
begin
for x in (SELECT sequence_name FROM USER_SEQUENCES) loop
st := 'ALTER SEQUENCE ' || x.sequence_name || ' INCREMENT BY 1000';
execute immediate st;
st := 'select ' || x.sequence_name || '.nextval from dual';
execute immediate st into val;
st := 'ALTER SEQUENCE ' || x.sequence_name || ' INCREMENT BY 1';
execute immediate st;
end loop;
end;
/
I've added a val variable, and an into val clause on the second execute immediate.
To demonstrate that it works now:
create sequence s42;
Sequence s42 created.
declare
st VARCHAR(1024);
n number;
begin
for x in (SELECT sequence_name FROM USER_SEQUENCES) loop
st := 'ALTER SEQUENCE ' || x.sequence_name || ' INCREMENT BY 1000';
execute immediate st;
st := 'select ' || x.sequence_name || '.nextval from dual';
execute immediate st into n;
st := 'ALTER SEQUENCE ' || x.sequence_name || ' INCREMENT BY 1';
execute immediate st;
end loop;
end;
/
anonymous block completed
select s42.nextval from dual;
NEXTVAL
----------
1001
Without the into clause, this came back with 1 rather than 1001, which is what you are seeing.
The restart start with syntax in 12c can simplify the steps:
create sequence test_sequence;
declare
st VARCHAR(1024);
begin
for x in (SELECT sequence_name, last_number FROM USER_SEQUENCES) loop
st := 'ALTER SEQUENCE ' || x.sequence_name
|| ' RESTART START WITH ' || to_char(x.last_number+1000);
execute immediate st;
end loop;
end;
/
select test_sequence.nextval from dual;
NEXTVAL
-------
1001

PL/SQL Execute immediate exception handling inside for loop

In the below PL/SQL code, TABLE_ONE holds table name tname , column name cname and rowid rid. The For loop fetches records from TABLE_ONE and updates column cname in table tname for the record with row id rid. But if the record to be updated in tname is locked then the for loop gets stuck and no further records from TABLE_ONE are processed. Ideally, the script to ignore the records for which update failed and proceed further. Please advise what could be the issue.
BEGIN
FOR c IN (SELECT * FROM TABLE_ONE a )
LOOP
DECLARE
TNAME varchar2(30);
CNAME varchar2(30);
RID ROWID;
X number;
updt_stmt varchar2(300);
BEGIN
BEGIN
TNAME := c.TNAME;
CNAME := c.CNAME;
RID := c.RID;
DBMS_OUTPUT.PUT_LINE( TNAME || '=>' || CNAME);
updt_stmt := 'UPDATE ' || TNAME || ' SET ' || CNAME || ' = ''123'' WHERE ROWID like ''%' || RID || '%''';
EXECUTE IMMEDIATE updt_stmt;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERROR');
END;
END;
END LOOP;
END;
Untested, but I think you could first try to lock the record with FOR UPDATE and by specifying NOWAIT cause a failure if some other transaction was active. You could then catch this exception and skip processing. Here is an untested example:
DECLARE
x ROWID;
resource_busy EXCEPTION;
PRAGMA EXCEPTION_INIT(resource_busy,
-00054);
BEGIN
FOR c IN (SELECT *
FROM table_one a)
LOOP
DECLARE
tname VARCHAR2(30);
cname VARCHAR2(30);
rid ROWID;
x NUMBER;
updt_stmt VARCHAR2(300);
BEGIN
BEGIN
tname := c.tname;
cname := c.cname;
rid := c.rid;
dbms_output.put_line(tname || '=>' || cname);
BEGIN
EXECUTE IMMEDIATE 'SELECT rowid FROM ' || tname ||
' WHERE rowid = :x FOR UPDATE NOWAIT'
INTO x
USING rid;
EXCEPTION
WHEN resource_busy THEN
dbms_output.put_line('Record locked; try again later.');
CONTINUE;
END;
updt_stmt := 'UPDATE ' || tname || ' SET ' || cname ||
' = ''123'' WHERE ROWID like ''%' || rid || '%''';
EXECUTE IMMEDIATE updt_stmt;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('ERROR');
END;
END;
END LOOP;
END;

Reset auto increment sequence pl-sql

How can i reset auto increment primary key ?
I have a doc_id_seq and a doc_pk_trg trigger like that:
CREATE SEQUENCE doc_id_seq START WITH 1;
CREATE OR REPLACE TRIGGER doc_pk_trg
BEFORE INSERT ON TFIDF FOR EACH ROW
BEGIN
IF :NEW.doc_id IS NULL THEN
SELECT doc_id_seq.NEXTVAL INTO :NEW.doc_id FROM DUAL;
END IF;
END;
/
I want to learn reset the sequence . How can I do this ?
You could use the ALTER SEQUENCE syntax.
Tom Kyte explains how to do exactly this here:
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1119633817597
Simply drop and then recreate the sequence.
This is how I would reset it
CREATE OR REPLACE PROCEDURE reset_sequence (
p_sequence_name IN VARCHAR2,
p_new_value IN NUMBER
)
AS
l_current_value NUMBER;
v_sequence_exists NUMBER;
BEGIN
SELECT 1
INTO v_sequence_exists
FROM user_sequences
WHERE sequence_name = p_sequence_name;
EXECUTE IMMEDIATE 'SELECT ' || p_sequence_name || '.nextval FROM dual'
INTO l_current_value;
/*Not possible to increment by 0 !*/
IF (p_new_value - l_current_value - 1) != 0
THEN
EXECUTE IMMEDIATE 'ALTER SEQUENCE '
|| p_sequence_name
|| ' INCREMENT BY '
|| (p_new_value - l_current_value - 1)
|| ' MINVALUE 0';
END IF;
EXECUTE IMMEDIATE 'SELECT ' || p_sequence_name || '.nextval FROM dual'
INTO l_current_value;
EXECUTE IMMEDIATE 'ALTER SEQUENCE ' || p_sequence_name || ' INCREMENT BY 1 ';
EXCEPTION
WHEN NO_DATA_FOUND
THEN
raise_application_error (-20001, 'Sequence does not exist');
END;
This has the added benefit over drop & recreate of not invalidating any dependant schema objects.
try this
alter table "table_name"
modify "id" generated always as identity restart start with 1;

Resources