I seem to be having some issues around creating a stored procedure and I simply cannot see where the issue lies. I am relatively new to Oracle.
I have a table of unknown length. What I have done is I created a stored procedure that will create a table to the width of the row count of the table where I am getting my values from. i.e. if the table has 10 values, the code will create a new table 10 columns wide, etc.
I have managed to get the initial code working, but now I am trying to add some additional logic without success.
My logic that I am trying to accomplish is as such:
IF TABLE DOES NOT EXIST - CREATE IT
IF TABLE EXSITS - SIMPLY INSERT INTO IT
I have not created the code for the INSERT part yet as I cannot get the first part to work. Everything was working fine until I added the count and IF statement.
CREATE OR REPLACE PROCEDURE "MDWPROD"."WORKFLOW_VAR_PIVOT" IS
v_sql varchar2(32767);
-- for the first run ofthe procedure, we need to create the table
DECLARE var_count INT;
SELECT
COUNT(*)
INTO
var_count
FROM
all_tables
WHERE
OWNER = 'MDWPROD'
AND TABLE_NAME = 'RBI_PROCESSVARIABLE_WK';
-- if var_count = 0 then the table does not exists, create it, otherwise proceed with other logic
IF var_count = 0 THEN
-- cursor to find out the maximum number of projected columns required
CURSOR cur_proj_test IS
SELECT DISTINCT
ID,
VARIABLE_REPORT_LBL
FROM
MDWPROD.RBI_VARIABLETYPE_DM
ORDER BY
ID;
-- We now loop through the cursor, and build of the SQL string to CREATE and POPULATE the table
BEGIN
v_sql := 'CREATE TABLE MDWPROD.RBI_PROCESSVARIABLE_WK AS SELECT VAR.PROCESS_ID';
FOR i IN cur_proj_test
LOOP
-- dynamically add to the projection for the query
v_sql := v_sql || ',MAX(CASE VT.VARIABLE_REPORT_LBL WHEN ''' || i.VARIABLE_REPORT_LBL || ''' THEN VAR.VALUE ELSE '''' END) AS "' || i.VARIABLE_REPORT_LBL || '"';
END LOOP;
v_sql := v_sql || ' FROM MDWPROD.RBI_VARIABLE_DM VAR INNER JOIN MDWPROD.RBI_VARIABLETYPE_DM VT ON VAR.VARIABLE_TYPE_ID = VT.ID WHERE VAR.CURRENT_IND = ''Y'' GROUP BY VAR.PROCESS_ID order by VAR.PROCESS_ID';
-- Create table and populate it with all the relevant variable values
EXECUTE IMMEDIATE v_sql;
END;
END IF;
END;
Any assistance would be greatly appreciated.
Original working proc:
CREATE OR REPLACE PROCEDURE WORKFLOW_VAR_PIVOT IS
v_sql varchar2(32767);
-- cursor to find out the maximum number of projected columns required
CURSOR cur_proj_test IS
SELECT DISTINCT
ID,
VARIABLE_REPORT_LBL
FROM
MDWPROD.RBI_VARIABLETYPE_DM
ORDER BY
ID;
-- We now loop through the cursor, and build of the SQL string to CREATE and POPULATE the table
BEGIN
v_sql := 'CREATE TABLE MDWPROD.RBI_PROCESSVARIABLE AS SELECT VAR.PROCESS_ID';
FOR i IN cur_proj_test
LOOP
-- dynamically add to the projection for the query
v_sql := v_sql || ',MAX(CASE VT.VARIABLE_REPORT_LBL WHEN ''' || i.VARIABLE_REPORT_LBL || ''' THEN VAR.VALUE ELSE '''' END) AS "' || i.VARIABLE_REPORT_LBL || '"';
END LOOP;
v_sql := v_sql || ' FROM MDWPROD.RBI_VARIABLE_DM VAR INNER JOIN MDWPROD.RBI_VARIABLETYPE_DM VT ON VAR.VARIABLE_TYPE_ID = VT.ID WHERE VAR.CURRENT_IND = ''Y'' GROUP BY VAR.PROCESS_ID order by VAR.PROCESS_ID';
-- un comment this line to print out the entire SQL statement
-- dbms_output.put_line('Dynamic SQL Statement:-' || chr(10) || v_sql || chr(10) || chr(10));
-- DROP TABLE before recreating it
EXECUTE IMMEDIATE 'DROP TABLE MDWPROD.RBI_PROCESSVARIABLE';
-- Create table and populate it with all the relevant variable values
EXECUTE IMMEDIATE v_sql;
END;
There may be other problems (you never stated your exact error), but I can immediately see that the begin keyword is in the wrong place. Your declarations should be at the top, before the begin keyword, and your execution statements should come after.
I moved things around a little. This should get you closer to your goal:
CREATE OR REPLACE PROCEDURE "MDWPROD"."WORKFLOW_VAR_PIVOT" IS
v_sql varchar2(32767);
-- for the first run ofthe procedure, we need to create the table
DECLARE var_count INT;
-- cursor to find out the maximum number of projected columns required
CURSOR cur_proj_test IS
SELECT DISTINCT
ID,
VARIABLE_REPORT_LBL
FROM
MDWPROD.RBI_VARIABLETYPE_DM
ORDER BY
ID;
BEGIN
SELECT
COUNT(*)
INTO
var_count
FROM
all_tables
WHERE
OWNER = 'MDWPROD'
AND TABLE_NAME = 'RBI_PROCESSVARIABLE_WK';
-- if var_count = 0 then the table does not exists, create it, otherwise proceed with other logic
IF var_count = 0 THEN
-- We now loop through the cursor, and build of the SQL string to CREATE and POPULATE the table
v_sql := 'CREATE TABLE MDWPROD.RBI_PROCESSVARIABLE_WK AS SELECT VAR.PROCESS_ID';
FOR i IN cur_proj_test
LOOP
-- dynamically add to the projection for the query
v_sql := v_sql || ',MAX(CASE VT.VARIABLE_REPORT_LBL WHEN ''' || i.VARIABLE_REPORT_LBL || ''' THEN VAR.VALUE ELSE '''' END) AS "' || i.VARIABLE_REPORT_LBL || '"';
END LOOP;
v_sql := v_sql || ' FROM MDWPROD.RBI_VARIABLE_DM VAR INNER JOIN MDWPROD.RBI_VARIABLETYPE_DM VT ON VAR.VARIABLE_TYPE_ID = VT.ID WHERE VAR.CURRENT_IND = ''Y'' GROUP BY VAR.PROCESS_ID order by VAR.PROCESS_ID';
-- Create table and populate it with all the relevant variable values
EXECUTE IMMEDIATE v_sql;
END IF;
END;
Related
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.
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;
/
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;
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;
/
I want to get the distinct dates in a column called "YMDH" from each table in a schema where that column exists. I figured that I needed to use nested cursors (something I've not done before) and came up with the following code:
CREATE OR REPLACE PROCEDURE DistinctDates AS
sql_statement1 varchar2(200);
sql_statement2 varchar2(200);
results varchar2(15);
ColumnExist integer;
BEGIN
for cursor_rec in (SELECT * FROM user_objects WHERE object_type='TABLE'
AND object_name NOT LIKE 'TM%') loop
sql_statement1 := 'select count (*) from user_tab_columns where table_name=' || '''' || cursor_rec.object_name || '''' || ' and column_name=' || '''' ||'YMDH' || '''';
execute immediate sql_statement1 into ColumnExist;
if ColumnExist = 1 then
for inner_cursor_rec in (select distinct(ymdh) from cursor_rec.object_name) loop
null;
end loop;
end if;
end loop;
END DistinctDates;
SQL Developer is complaining about the select statement for the inner cursor. The error message is:
Error(18,32): PL/SQL: SQL Statement ignored
Error(18,70): PL/SQL: ORA-00942: table or view does not exist
So it's not recognizing the reference to the outer cursor. How do I pass the table name (which is the cursor_rec.object_name) to the inner cursor?
You have used dynamic SQL where it is not needed, and have not used it where it is needed!
The check to see if the table has a column called 'YMDH' can be incorporated into the first query, giving this code:
CREATE OR REPLACE PROCEDURE DistinctDates AS
sql_statement varchar2(200);
rc sys_refcursor;
ymdh_value ????; -- Appropriate data type
BEGIN
for cursor_rec in (SELECT t.table_name
FROM user_tables t
JOIN user_tab_columns c ON c.table_name = t.table_name
WHERE t.table_name NOT LIKE 'TM%'
AND c.column_name='YMDH')
loop
sql_statement := 'select distinct(ymdh) from ' || cursor_rec.table_name;
open rc for sql_statement;
loop
fetch rc into ymdh_value;
exit when rc%notfound;
null;
end loop;
close rc;
end loop;
END DistinctDates;