How to declare %rowtype dynamically? - sql

Below is the sample code in which I have stored all the table names in one table (table_config) and trying to insert one record of every table into its temporary table and trying to get the particular rowid for further need.
So I need every table rowtype to make this work, something dynamic. Could you please help me with this?
DECLARE
l_row table_name%ROWTYPE;
l_rowid ROWID;
l_table_name all_tab_partitions.table_name%TYPE;
l_temp_table_name all_tab_partitions.table_name%TYPE;
BEGIN
FOR tab IN
(select table_name from
Table_config)
LOOP
l_table_name:= tab.table_name;
l_temp_table_name:= 'TEMP_'||l_table_name;
SELECT * INTO l_row
FROM l_table_name
WHERE ROWNUM=1;
INSERT INTO l_temp_table_name VALUES l_row
RETURNING ROWID INTO l_rowid;
COMMIT;
END LOOP;
END;
Thank you,
Pradeep

Without coding the complete answer for you.
Why don't you do something like this?
FOR tab IN
(select table_name from
Table_config)
EXECUTE_IMMEDIATE(
'declare
l_row '||table_name||'%ROWTYPE;
begin
INSERT INTO '||l_temp_table_name
SELECT * FROM '||l_table_name||' WHERE ROWNUM=1;
end;');
EXECUTE_IMMEDIATE ('SELECT ROWID FROM '||l_table_name)
INTO l_rowid;
END LOOP;
it assumes target table is empty to begin with with only one record inserted during the process.

You can't do that as already mentioned in the comment by OldProgrammer above.
You'll have to use Dynamic SQL to achieve what you're trying to achieve.
DECLARE
temp_table VARCHAR2(255);
source_table VARCHAR2(255);
sql_stmt VARCHAR2(255);
CURSOR c1 IS
SELECT table_name FROM user_Tables;
BEGIN
FOR c1_Rec IN c1 LOOP
temp_table := 'TEMP_'||c1_rec.table_name;
source_table := c1_rec.table_name;
sql_stmt := 'INSERT INTO '||temp_table||' SELECT * FROM '||source_table||' WHERE rownum = 1';
EXECUTE IMMEDIATE sql_stmt;
END LOOP;
END;
/

Below is solution. What do you need this rowids for? I would be much simpler without it, as you cannot use returning with insert as select
DECLARE
l_rowid ROWID;
l_table_name all_tab_partitions.table_name%TYPE;
l_temp_table_name all_tab_partitions.table_name%TYPE;
v_sql1 varchar2(4000);
v_sql2 varchar2(4000);
BEGIN
FOR tab IN (select table_name from Table_config) LOOP
l_table_name:= tab.table_name;
l_temp_table_name:= 'TEMP_'||l_table_name;
v_sql1 := 'select rowid from ' || l_table_name || ' where rownum =1 for update';
v_sql2 := 'insert into ' || l_temp_table_name || ' select * from ' || l_table_name || ' where rownum = 1';
execute immediate v_sql1 into l_rowid;
execute immediate v_sql2;
commit;
END LOOP;
END;
/

You should investigate EXECUTE IMMEDIATE INTO. I think this would be an excellent way to get the ROWID when combined with some dynamic SQL examples from above. Here's an example:
DECLARE
DYN_SQL VARCHAR(4000) := 'SELECT 1 FROM DUAL';
INTO_VAR NUMBER(1);
BEGIN
EXECUTE IMMEDIATE DYN_SQL INTO INTO_VAR;
DBMS_OUTPUT.PUT_LINE(INTO_VAR);
END;

Thank you guys for your response. Actually I was trying to implement partition exchange on interval partitioned tables. I achieved it by using Dynamic Sql now. Initially I was trying to implement it by using rowid which is ok when I hard coded for one table, but when I thought of configuring it and using it for multiple tables I got stuck at that %ROWTYPE.
In the below code I have hard coded table name in few places which can be modified as dynamic but the problem is how to get the %ROWTYPE for the every table we pass.
DECLARE
l_table_name table_config.table_name%TYPE;
l_query_temp VARCHAR2(1000);
l_part_table_name all_tab_partitions.table_name%TYPE;
l_part_name all_tab_partitions.partition_name%TYPE;
l_temp_table_name all_tab_partitions.table_name%TYPE;
l_row test_archival%ROWTYPE;
l_rowid ROWID;
l_arch_table_name all_tab_partitions.table_name%TYPE;
l_arch_part_name VARCHAR2(30);
l_query_arch VARCHAR2(1000);
l_query_source VARCHAR2(1000);
BEGIN
<<outer_loop>>
FOR tab IN
(SELECT table_name FROM
table_config)
LOOP
l_table_name:= tab.table_name;
<<inner_loop>>
FOR part IN
(SELECT table_name, partition_position, partition_name FROM
(SELECT table_name, partition_position, partition_name,
DENSE_RANK() OVER (PARTITION BY table_name ORDER BY partition_position DESC) AS RANK
FROM all_tab_partitions
WHERE table_name=l_table_name
) WHERE RANK NOT IN(1, 2) ORDER BY partition_position)
LOOP
l_part_table_name:= part.table_name;
l_part_name:= part.partition_name;
l_temp_table_name := 'TEMP_'||l_part_table_name;
l_arch_table_name := 'ARCH_'||l_part_table_name;
l_query_temp := 'ALTER TABLE '
|| l_part_table_name
|| ' EXCHANGE PARTITION '
|| l_part_name
|| ' WITH TABLE '
|| l_temp_table_name
||' INCLUDING INDEXES WITHOUT VALIDATION';
EXECUTE IMMEDIATE l_query_temp;
COMMIT;
SELECT * INTO l_row FROM temp_test_archival WHERE ROWNUM = 1;
INSERT INTO arch_test_archival VALUES l_row RETURNING ROWID INTO l_rowid;
COMMIT;
SELECT subobject_name
INTO l_arch_part_name FROM user_objects
WHERE data_object_id = dbms_rowid.rowid_object(l_rowid);
DELETE from arch_test_archival where rowid=l_rowid;
COMMIT;
l_query_arch := 'ALTER TABLE '
||'ARCH_TEST_ARCHIVAL'
||' EXCHANGE PARTITION '
||l_arch_part_name
||' WITH TABLE '
||'TEMP_TEST_ARCHIVAL'
||' INCLUDING INDEXES WITHOUT VALIDATION';
EXECUTE IMMEDIATE l_query_arch;
END LOOP;
END LOOP;
END;
/

Related

Dynamic sql with for loop PL/SQL

The following query needs to convert to dynamic SQL without hard code cursor SQL,
using l_query, I do not know the l_query it will come as a parameter.
Inside the loop, I need to execute another insert query ( l_insert_query) that also comes as a parameter.
Your counsel would be much appreciated
DECLARE
CURSOR cust
IS
SELECT *
FROM customer
WHERE id < 500;
BEGIN
l_query := 'SELECT * FROM customer WHERE id < 5';
l_insert_query :=
'insert into data ( name, mobile) values ( cust.name,cust.mobile)';
FOR r_cust IN cust
LOOP
EXECUTE IMMEDIATE l_insert_query;
END LOOP;
END;
You could do this with a dynamic PL/SQL block:
declare
l_query varchar2(100) := 'SELECT * FROM customer WHERE id < 5';
l_insert varchar2(100) := 'insert into data ( name, mobile) values ( cust.name,cust.mobile)';
l_plsql varchar2(4000);
begin
l_plsql := '
begin
for cust in (' || l_query || ') loop
' || l_insert || ';
end loop;
end;
';
dbms_output.put_line(l_plsql);
execute immediate l_plsql;
end;
/
The l_plsql statement ends up as a generated PL/SQL block using the cursor query and insert statement:
begin
for cust in (SELECT * FROM customer WHERE id < 5) loop
insert into data ( name, mobile) values ( cust.name,cust.mobile);
end loop;
end;
db<>fiddle
But that you can do this doesn't mean you should. This is vulnerable to SQL injection, and doesn't seem like a very safe, sensible or efficient way to handle data manipulation in your system.

Oracle procedure to delete records in all tables of an owner

I am trying to create an Oracle procedure to delete records from multiple tables of an owner based upon a distinct count condition:
Firstly I am trying to obtain the tables for which I want to delete those records with this query:
SELECT * FROM ALL_TABLES WHERE OWNER = 'Lorik' AND TABLE_NAME LIKE 'UT_%';
This results in a total of 300 tables, now all of those tables have a column named: DATE_INC
I am trying to delete records from all of the tables if this COUNT(DISTINCT DATE_INC) > 5.
Assuming that one of those 300 tables is named UT_NAMES:
SELECT COUNT(DISTINCT DATE_INC) FROM Lorik.UT_NAMES;
So if the count exceeds 5, then I want to delete the records with the minimum date:
DELETE MIN(DATE_INC) FROM Lorik.UT_NAMES;
Can someone please link these steps together so I can loop through each table of that owner and obtain the distinct date count and delete records based upon the above cited condition.
Thanks in advance!
You can use 'EXECUTE IMMEDIATE' in PL/SQL to accomplish your goal:
DECLARE
strTable VARCHAR2(32767);
nCount NUMBER;
BEGIN
FOR aRow IN (SELECT *
FROM ALL_TABLES
WHERE OWNER = 'Lorik' AND
TABLE_NAME LIKE 'UT_%')
LOOP
strTable := aRow.OWNER || '.' || aRow.TABLE_NAME;
EXECUTE IMMEDIATE 'SELECT COUNT(DISTINCT DATE_INC) FROM ' || strTable
INTO nCount;
IF nCount > 5 THEN
EXECUTE IMMEDIATE 'DELETE FROM ' || strTable ||
' WHERE DATE_INC = (SELECT MIN(DATE_INC) ' ||
'FROM ' || strTable || ')';
END IF;
END LOOP;
END;
Not tested on animals - you'll be first! :-)
As pointed out by #Andrew this seems to be very basic processes.
Declare some variables.
Open a cursor.
Use dynamic sql to count
Use dynamic sql to delete
Add some log information
_
declare
v_cnt number;
v_sql varchar2(1000);
begin
for cur in (SELECT * FROM ALL_TABLES WHERE OWNER = 'HR' AND TABLE_NAME LIKE 'E%')
loop
v_sql := 'select count(distinct department_id||''date_inc'') from '||cur.owner||'. '||cur.table_name;
execute immediate v_sql into v_cnt;
dbms_output.put_line (cur.table_name || ': ' || v_cnt);
if v_cnt > 5 then
v_sql := 'delete from '||cur.owner||'. '||cur.table_name || ' where date_inc = (select min (date_inc) from ' ||cur.owner||'. '||cur.table_name || ')';
dbms_output.put_line (v_sql);
-- execute immediate v_sql;
end if;
end loop;
rollback;
-- commit;
end;
A little bit simpler dynamic SQL for deleting rows, as it uses a local variable:
declare
l_cnt number; -- counter variable
l_min_date date; -- MIN(date_inc)
begin
for cur_t in (select table_name from all_tables
where owner = 'Lorik' -- are you sure it is not uppercase, LORIK?
and table_name like 'UT%' -- underline is wildcard character so you don't need it
)
loop
execute immediate 'select count(distinct date_inc), min(date_inc) from ' ||
cur_t.table_name into l_cnt, l_min_date;
if l_cnt > 5 then
execute immediate 'delete from ' || cur_t.table_name ||
' where date_inc = ' || l_min_date_inc;
end if;
end loop;
end;
/

ORACLE SQL CURSOR / FOR LOOP

I need to verify converted data, distinct values and records counts. I would like to write statements so that I can enter a table name, then retrieve it's columns and use them in a query to get its distinct values (the actual values, not just a count of how many distinct) and their count.
I think I need to a CURSOR or CURSOR FOR LOOP and create something like this:
declare
cursor field_name
is
select COLUMN_NAME
from user_tab_cols
where table_name='TABLE1'
c_field_name field_name%ROWTYPE;
BEGIN
OPEN field_name
loop
fetch field_name INTO c_field_name;
exit when field_name%NOTFOUND;
end loop;
CLOSE field_name;
end;
Then run a query using that above in something like
select field_name, count(*)
from table1
group by field_name
Do I need to create 2 loop statements? I've not yet created one and can't quite get the context to get my results so far.
BEGIN
FOR myrow in (select field_name, count(*) as "count" from table1 group by field_name)
loop
dbms_output.put_line(myrow.field_name);
dbms_output.put_line(myrow.count);
end loop;
end;
Considering you will be giving the table name as parameter below code will print all the values of all the columns one by one along with the count of the values
create or replace PROCEDURE PR_PREP(
P_TABLE_NAME IN VARCHAR2)
IS
CURSOR CUR_COLUMNS (PA_TABLE_NAME VARCHAR2)
IS
SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = PA_TABLE_NAME;
COL_NAMES CUR_COLUMNS%ROWTYPE;
TYPE TYP_RECORD
IS
RECORD
(
FIELD_NAME VARCHAR2(255),
CNT INT);
TYPE TYP_OP_TABLE
IS
TABLE OF TYP_RECORD;
OP_TABLE TYP_OP_TABLE;
I INT;
V_SQL VARCHAR2(2000);
BEGIN
FOR COL_NAMES IN CUR_COLUMNS(P_TABLE_NAME)
LOOP
V_SQL := 'SELECT ' || COL_NAMES.COLUMN_NAME || ' AS FIELD_NAME ,
COUNT(*) AS CNT FROM ' ||
P_TABLE_NAME || ' GROUP BY ' || COL_NAMES.COLUMN_NAME ;
-- DBMS_OUTPUT.PUT_LINE (V_SQL);
EXECUTE IMMEDIATE V_SQL BULK COLLECT INTO OP_TABLE;
dbms_output.put_line('columna name = ' ||COL_NAMES.COLUMN_NAME);
FOR I IN OP_TABLE.FIRST .. OP_TABLE.LAST
LOOP
DBMS_OUTPUT.PUT_LINE('FIELD VALUE '||OP_TABLE(I).FIELD_NAME || ' COUNT = ' || OP_TABLE(I).CNT);
END LOOP;
DBMS_OUTPUT.PUT_LINE('ONE FILED ENDED , NEXT STARTED');
END LOOP;
END;

query all tables in Oracle

I have this plsql code that will give me all tables in the database with name CUSTOMERS. Now I am struck how to insert another loop in to this. I want to get the output from this code and pass it on to next loop where I want to query something like, select count(*) from Schema.customers; for each schema.
DECLARE
--c_id customers.id%type;
c_name all_tables.table_name%type;
c_tabs all_tables.owner%type;
CURSOR c_tables is
SELECT table_name, owner FROM all_tables where table_name='CUSTOMERS';
BEGIN
OPEN c_tables;
LOOP
FETCH c_tables into c_name, c_tabs;
dbms_output.put_line(c_tabs || '.' || c_name );
EXIT WHEN c_tables%notfound;
END LOOP;
CLOSE c_tables;
END;
/
------- Sample output of my code: ------------
UMICH2.CUSTOMERS
TRINITYDC.CUSTOMERS
BUFFALO.CUSTOMERS
SNOW.CUSTOMERS
PULASKITECH.CUSTOMERS
RARITANVAL.CUSTOMERS
STMARYSCA.CUSTOMERS
You can get the same result in a single SQL statement
SELECT table_name
,to_number
(extractvalue
(xmltype
(dbms_xmlgen.getxml
('SELECT count(*) c FROM ' || owner || '.' || table_name)
)
,'/ROWSET/ROW/C'
)
) Count
FROM all_tables
WHERE table_name = 'CUSTOMERS'
This way is one possibility that you can do that.
DECLARE
--c_id customers.id%type;
c_name all_tables.table_name%TYPE;
c_tabs all_tables.owner%TYPE;
v_value PLS_INTEGER;
CURSOR c_tables IS
SELECT table_name
FROM all_tables
WHERE table_name = 'CUSTOMERS';
CURSOR c_owner IS
SELECT DISTINCT owner
FROM all_tables
WHERE table_name = 'CUSTOMERS';
BEGIN
OPEN c_tables;
LOOP
FETCH c_tables INTO c_name;
OPEN c_owner;
LOOP
FETCH c_owner INTO c_tabs;
BEGIN
EXECUTE IMMEDIATE 'SELECT COUNT(1) FROM ' || c_tabs || '.' || c_name INTO v_value;
EXCEPTION
WHEN other THEN
NULL;
END;
DBMS_OUTPUT.put_line ( v_value );
EXIT WHEN c_owner%NOTFOUND;
END LOOP;
CLOSE c_owner;
DBMS_OUTPUT.put_line ( c_tabs || '.' || c_name );
EXIT WHEN c_tables%NOTFOUND;
END LOOP;
CLOSE c_tables;
END;
Any question just let me know.
Thanks.

Cursor and table cannot be found

I have a procedure that will select MAX from some tables, but for some reason it is not able to find these tables. Could anybody help me?
declare
varible1 varchar2 (255);
temp varchar2 (255);
last_val number(9,0);
cursor c1 is Select distinct table_name from user_tab_cols order by table_name;
begin
FOR asd in c1
LOOP
temp := asd.table_name;
varible1 := '"'||temp||'"';
select max("id") into last_val from varible1 ;
END LOOP;
end;
For example, the first table name is acceptance_form and for select I need to use "acceptance_form".
Code after edit:
declare
varible1 varchar2 (255);
temp varchar2 (255);
last_val number(9,0);
cursor c1 is Select distinct table_name from user_tab_cols where column_name = 'id';
begin
FOR asd in c1
LOOP
temp := asd.table_name;
execute immediate 'select NVL(max('||'id'||'),0) from "'||varible1||'"' into last_val;
END LOOP;
end;
Can't cuz db is Case sensitive Oracle express 10g tables and rows was created like this
CREATE TABLE "ADMINMME"."acceptance_form"
(
"group_id" NUMBER(9, 0),
"id" NUMBER(4, 0) DEFAULT '0' NOT NULL ,
"is_deleted" NUMBER(4, 0),
"name" NVARCHAR2(30) NOT NULL
);
Can u tell me how to handle exception sequence dosn't exist for this;
Nevermind exception was in wrong block :)
declare
temp varchar2 (255);
last_val number(9,0);
cursor c1 is Select distinct table_name from user_tab_cols where column_name = 'id';
begin
FOR asd in c1
LOOP
temp := asd.table_name;
execute immediate 'select NVL(max("id"),0)+1 from "'||temp||'"' into last_val;
begin
EXECUTE IMMEDIATE 'drop sequence "seq_'|| temp||'"';
EXECUTE IMMEDIATE 'create SEQUENCE "seq_'|| temp ||'" MINVALUE '||last_val||'MAXVALUE 999999999999999999999999999 INCREMENT BY 1 NOCACHE';
EXECUTE IMMEDIATE 'select '||temp||'.nextval from dual';
EXECUTE IMMEDIATE 'ALTER SEQUENCE "seq_'||temp||'" INCREMENT BY 1';
exception when others then
null;
end;
END LOOP;
end;
Dynamic sql doesn't work in that way.
declare
varible1 varchar2 (255);
temp varchar2 (255);
last_val number(9,0);
cursor c1 is Select distinct table_name from user_tab_cols order by table_name;
begin
FOR asd in c1
LOOP
temp := asd.table_name;
begin
execute immediate 'select max(id) from '||temp into last_val;
dbms_output.put_line('max(id) for table: ' ||temp||' = '||last_val);
exception when others then
dbms_output.put_line('Failed to get max(id) for table: ' ||temp);
end;
END LOOP;
end;
You can't use a variable for the table name.
What you can do is creating the complete sql statement as a string and use execute immediate
Here are some examples how to do that: http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/dynamic.htm#CHDGJEGD