PL SQL- Using Dynamic SQL to Generate Delete Statements - oracle

I want to create a stored procedure using PL SQL that allows me to find all tables that contain a specific column, and then delete records from those tables that have a specific value in that column.
For example, I want to find all tables that have the column "year" and then delete all records from all of those tables that have the year "2012"(this year will be a parameter that will be entered upon execution)
My attempt at this has been to create a stored procedure, use a cursor to get all of the tables that have this column of "year" and then loop through that cursor using Dynamic SQL which will generate my Delete Statements that I can execute.
CREATE OR REPLACE PROCEDURE year_deletion
(
p_year NUMBER --Input of the year for records to be deleted
)
IS
CURSOR c1 --Cursor that will find all tables that have the YEAR column
IS
SELECT owner, table_name
FROM all_tab_columns
WHERE column_name = 'YEAR'
AND owner = 'GTP';
BEGIN
FOR i IN c1 LOOP --Loop through all of the tables that the cursor found, generate a SQL statement for each table that will delete all of the records that have the year of p_year
EXECUTE IMMEDIATE ('SELECT * FROM' ||i.table_name|| 'WHERE YEAR = '||p_year||';');
END LOOP;
END;
Disclaimer: I am using a Select * From instead of a DELETE * From for testing purposes, I will change this when it this procedure executes correctly.
So far this stored procedure compiles correctly, but gives me an error during execution that a FROM keyword was expected but not found. Is this the best method to use for my purpose?

Is must be like this:
EXECUTE IMMEDIATE 'DELETE FROM ' ||i.table_name|| ' WHERE YEAR = :theYear' USING p_year;
Note the space after FROM and before WHERE.
You cannot simply replace DELETE by SELECT ... for testing because for SELECT you must have an INTO clause.
Your entire procedure can be like this
CREATE OR REPLACE PROCEDURE year_deletion(p_year IN NUMBER) IS
CURSOR c1 IS
SELECT owner, table_name
FROM all_tab_columns
WHERE column_name = 'YEAR'
AND owner = 'GTP';
res NUMBER;
BEGIN
FOR i IN c1 LOOP
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM ' ||i.table_name|| ' WHERE YEAR = :theYear' INTO res USING p_year;
DBMS_OUTPUT.PUT_LINE (res ||' rows will be deleted from table '||i.table_name );
EXECUTE IMMEDIATE
'DELETE FROM ' ||i.table_name|| ' WHERE YEAR = :theYear' USING p_year;
END LOOP;
END;

Hello you can try the below code. It will surely help you out.
CREATE OR REPLACE PROCEDURE year_deletion(
p_year IN NUMBER --Input of the year for records to be deleted
)
IS
BEGIN
FOR i IN (SELECT owner,
table_name
FROM all_tab_columns
WHERE column_name = 'YEAR'
AND owner = 'GTP')
LOOP --Loop through all of the tables that the cursor found, generate a SQL statement for each table that will delete all of the records that have the year of p_year
EXECUTE IMMEDIATE 'DELETE FROM ' ||i.table_name|| ' WHERE YEAR = '||p_year;
END LOOP;
END;

Related

Oracle Stored Procedure to insert values from query, if values do not exist in target

Oracle stored procedure: I want a stored procedure that inserts table and column names into another table (target: add_definitions). I want to be able to run this procedure at any time to pick up new tables/columns that may have been added to the database. Also, I only want to add items that belong to a specific owner (Oracle term - aka "PUBUSER"), and try to avoid "system" tables. The solution must be a stored procedure.
Current stored proc that does not work
create or replace procedure test_adding as
sqlcmd varchar2(2000);
begin
sqlcmd := 'insert into add_definitions (table_name, column_name) ' ||
'select aa.table_name, aa.column_name ' ||
'from all_tab_columns aa ' ||
'where (instr(aa.table_name, ''$'') = 0 and aa.owner = ''PUBUSER'') and ' ||
'(not exists (select item_id from add_definitions cc where cc.table_name = aa.table_name and cc.column_name = aa.column_name))';
execute immediate sqlcmd;
end test_adding;
I checked the above by first manually populating the add_definitions table (with all the proper values). Then I deleted 2 of the rows. Then when I run the above, those 2 rows are not added back. I checked the SELECT part of the above INSERT and it does return the 2 items (table_name and column_name) that I deleted.
Note: I simplified the code, I need to add other values during the insert which get calculated from other code snippets (I plan on doing the "USING" part of the EXECUTE IMMEDIATE... to substitute those values into the sql command). But I cannot get this very basic step to work first. So please do not offer suggestions to "not do" a stored procedure, etc, etc. After 30 hours on this, I'm about to shoot myself.
Update: As per one of the comments, I tried to get this to run without EXECUTE IMMEDIATE - just did a direct "INSERT INTO... SELECT ..." It still failed to recover the deleted values (I retested the above steps over again, several times, using the direct INSERT approach). This is quite frustrating as one of the other responses says it works fine for them....??? (I'm using Oracle 11g Express Edition - but I sure hope that would not make a difference).
There seems no problem with your code, except not including and not populating item_id column in the insert statement :
SQL> create table add_definitions (item_id int, table_name varchar2(100), column_name varchar2(100));
SQL> create sequence seq_test start with 1;
SQL> create or replace procedure test_adding as
sqlcmd varchar2(2000);
begin
sqlcmd := 'insert into add_definitions (item_id,table_name, column_name) ' ||
'select seq_test.nextval,aa.table_name, aa.column_name ' ||
'from all_tab_columns aa ' ||
'where (instr(aa.table_name, ''$'') = 0 and aa.owner = ''PUBUSER'') and ' ||
'(not exists (select item_id from add_definitions cc where cc.table_name = aa.table_name and cc.column_name = aa.column_name))';
--dbms_output.put_line(sqlcmd);
execute immediate sqlcmd;
end test_adding;
SQL> exec test_adding; -- Assume 100 rows inserted
SQL> select count(1) from add_definitions;
COUNT(1)
----------
100
SQL> delete add_definitions where item_id in (99,100);
SQL> select count(1) from add_definitions;
COUNT(1)
----------
98
SQL> exec test_adding;
SQL> select count(1) from add_definitions;
COUNT(1)
----------
100
SQL> commit;

"ORA-01007: variable not in select list" when no rows are returned by EXECUTE IMMEDIATE

I have a procedure which receives as parameter a where clause (i.e. where col1 = 1). I am using this clause to search in some tables using an EXECUTE IMMEDIATE statement and the result to be inserted into a nested table, and than be displayed.
The procedure works fine if any data is found but in case no data is found, then the above error is thrown.
Can someone explain what cause this error, please?
Here is the procedure:
create or replace procedure prc_checks(pi_where varchar2) as
cursor c_tables is
select object_name,
case object_name
when 'XP_IMPORT_MW' THEN 99999999
when 'XP_IMPORT_MW_ARCH' THEN 99999998
else TO_NUMBER(SUBSTR(object_name, -8, 8))
end to_order
from dba_objects
where object_type = 'TABLE'
and object_name IN ('XP_IMPORT_MW', 'XP_IMPORT_MW_ARCH')
or REGEXP_LIKE (object_name, 'XP_IMPORT_MW_ARCH_201(5|6|7)[0-9]{4}') order by 2 desc;
type t_result is table of xp_import_mw%rowtype;
v_result t_result;
v_sql varchar2(300);
BEGIN
for i in c_tables
loop
v_sql := 'select * from ' || i.object_name || ' ' || pi_where;
execute immediate v_sql bulk collect into v_result;
if v_result.count > 0
then
for j in v_result.first .. v_result.last
loop
dbms_output.put_line(v_result(j).art_nr);
end loop;
dbms_output.put_line('... the required information was found on table name ' || upper(i.object_name));
exit;
end if;
end loop;
END prc_checks;
You'll get this is one of the tables being found by the cursor has fewer columns than xp_import_mw. For example:
create table xp_import_mw (col1 number, art_nr number, dummy number);
create table xp_import_mw_arch_20160102 (col1 number, art_nr number, dummy number);
create table xp_import_mw_arch_20160101 (col1 number, art_nr number);
insert into xp_import_mw_arch_20160101 values (1, 42);
So the main xp_import_mw table has three columns but no matching data. One of the old archive tables has one fewer columns.
I added a dbms_output.put_line(v_sql) to the procedure to see which table it fails against, then ran it:
set serveroutput on
exec prc_checks('where col1 = 1');
which got output:
select * from XP_IMPORT_MW where col1 = 1
select * from XP_IMPORT_MW_ARCH_20160102 where col1 = 1
select * from XP_IMPORT_MW_ARCH_20160101 where col1 = 1
Error starting at line : 49 in command -
BEGIN prc_checks('where col1 = 1'); END;
Error report -
ORA-01007: variable not in select list
ORA-06512: at "MY_SCHEMA.PRC_CHECKS", line 25
ORA-06512: at line 1
01007. 00000 - "variable not in select list"
*Cause:
*Action:
So the problem isn't that there is no data found; the problem is that there is matching data in a table which has the wrong structure.
You could construct the select list based on the xp_import_mw table's structure, instead of using *; that won't stop it failing, but would at least give you a slightly more helpful error message - in this case ORA-00904: "DUMMY": invalid identifier instead of ORA-01007.
You could do a quick and crude check for discrepancies with something like:
select table_name, count(column_id) as column_count,
listagg(column_name, ',') within group (order by column_id) as columns
from dba_tab_columns
where table_name IN ('XP_IMPORT_MW', 'XP_IMPORT_MW_ARCH')
or REGEXP_LIKE (table_name, 'XP_IMPORT_MW_ARCH_201(5|6|7)[0-9]{4}')
group by table_name
having count(column_id) != (
select count(column_id) from dba_tab_columns where table_name = 'XP_IMPORT_MW'
);
... although if you're using dba_* or all_* view you should really be including the owner, here and in your procedure.

Create table if it does not exist, and enter one row after creating

I need to create a table if it does not exist, and when it is created add a single row to it.
I'm new to oracle and PL/SQL so I basically need an equivalent of the following T-SQL:
IF OBJECT_ID('my_table', 'U') IS NULL
BEGIN
CREATE TABLE my_table(id numeric(38,0), date datetime)
INSERT INTO my_table
VALUES (NULL, 0)
END
if you want to check table creation
DECLARE count NUMBER;
BEGIN
count := 0;
SELECT COUNT(1) INTO count from user_tables WHERE table_name= 'MY_TABLE';
IF COL_COUNT = 0 THEN
EXECUTE IMMEDIATE 'create table ....';
END IF;
END;
/
A checking for DML .please note you have to sepcify your pk columns and values.
DECLARE count NUMBER;
BEGIN
count := 0;
SELECT COUNT(1) INTO count from MY_TABLE WHERE id= 0 and name='Something';
IF COL_COUNT = 0 THEN
EXECUTE IMMEDIATE 'insert into MY_TABLE (id,name) values(0,''something'') ';
END IF;
END;
/
also note I recomand to specify columns when you insert into a table
Another approach is to use exception logic. I changed field names and types according to Oracle rules
declare
eAlreadyExists exception;
pragma exception_init(eAlreadyExists, -00955);
begin
execute immediate 'CREATE TABLE my_table(id number, dateof date)';
execute immediate 'INSERT INTO my_table VALUES (NULL, sysdate)';
exception when eAlreadyExists then
null;
end;
but may be it is not a good idea to create tables dynamically
In my opinion, you should not be creating objects on the fly. You should think about your design before implementing it.
Anyway, if you really want to do it this way, then you need to do it programmatically in PL/SQL (ab)using EXECUTE IMMEDIATE.
However, I would prefer the CTAS i.e. create table as select if you want to create a table ta once with a single row. For example,
SQL> CREATE TABLE t AS SELECT 1 id, SYSDATE dt FROM DUAL;
Table created.
SQL> SELECT * FROM t;
ID DT
---------- ---------
1 29-MAY-15
SQL>
The table is created permanently.
If you are looking for a temporary table, which you could use to store session specific data , then look at creating Global temporary table.
From documentation,
Use the CREATE GLOBAL TEMPORARY TABLE statement to create a temporary
table. The ON COMMIT clause indicates if the data in the table is
transaction-specific (the default) or session-specific
You can use NOT EXISTS with select statement:
IF NOT EXISTS(SELECT 1 FROM my_table) THEN
CREATE TABLE my_table(id NUMBER, date date);
COMMIT;
INSERT INTO my_table(id, date) values (NULL, O);
COMMIT;
END IF;
UPDATE
According to the comment, I cannot use Exist directly in PL/SQL. So this is another way to do it:
begin
select case
when exists(select 1
from my_table)
then 1
else 0
end into l_exists
from dual;
if (l_exists = 1)
then
-- anything
else
EXECUTE IMMEDIATE 'CREATE TABLE my_table(id NUMBER, date date)';
EXECUTE IMMEDIATE 'INSERT INTO my_table(id, date) values (NULL, O)';
end if;
end;

For loop with Table name in Stored Procedures

I am working on Oracle stored procedures.
My requirement is below
IF variable1 := 'true"
THEN
tableName=abr
ELSE
tableName=mvr
END IF;
FOR i IN (select unique(row1) as sc from tableName t where t.row2 = 'name') LOOP
BEGIN
-- required Logic
END
END LOOP;
But here I am not able to pass the table name in tableName parameter. How to do it?
You'll need to use Execute Immediate - it's designed for operations that aren't known until run time.
For normal operations, Oracle must know the tables and columns at compile time. You can't do SELECT * FROM tableName because it has no idea what tableName is and therefore it can't be compiled correctly.
Instead, you can do EXECUTE IMMEDIATE 'SELECT * FROM ' || tableName;
You can select your results INTO a variable, loop the result set, or BULK COLLECT into a structure and then iterate that.
For a simple select into, you can do this:
EXECUTE IMMEDIATE 'SELECT COL1, COL2 FROM ' || tableName INTO V_COL1, V_COL2
V_COL1 & V_COL2 are just local variables, tableName is a string representing your table name, and COL2 and COL2 are columns in the table you're selecting from. You can use the likes of ALL_TAB_COLUMNS to get the structure of a table dynamically.
Here is an example from Oracle docs:
CREATE OR REPLACE PROCEDURE query_invoice(
month VARCHAR2,
year VARCHAR2) IS
TYPE cur_typ IS REF CURSOR;
c cur_typ;
query_str VARCHAR2(200);
inv_num NUMBER;
inv_cust VARCHAR2(20);
inv_amt NUMBER;
BEGIN
query_str := 'SELECT num, cust, amt FROM inv_' || month ||'_'|| year
|| ' WHERE invnum = :id';
OPEN c FOR query_str USING inv_num;
LOOP
FETCH c INTO inv_num, inv_cust, inv_amt;
EXIT WHEN c%NOTFOUND;
-- process row here
END LOOP;
CLOSE c;
END;
/
http://docs.oracle.com/cd/B12037_01/appdev.101/b10795/adfns_dy.htm
You are going to have to build a for loop for each table then use your logic to determine which loop you will execute.

pl/sql query to insert multiple rows using select statement

I want to insert multiple rows into a table.
The query would be:
insert into temp(table_name,run_date,table_count)
select 'TABLE_A',sysdate,count(*) from A;
insert into temp(table_name,run_date,table_count)
select 'TABLE_B',sysdate,count(*) from B;
insert into temp(table_name,run_date,table_count)
select 'TABLE_C',sysdate,count(*) from C;
How do I write this in a loop using pl/sql?
Thanks,
Anju
For a variable list of tables, here's a script that reads the Oracle system table ALL_TABLES for a specified owner and inserts the counts into a temp table.
DECLARE
-- Define a cursor to get the list of tables you want counts for.
cursor c1 is
select table_name
from all_tables
where owner = 'YOUR_OWNER_HERE';
-- Dynamically created select.
stmt varchar2(200);
BEGIN
-- The cursor for loop implicitly opens and closes the cursor.
for table_rec in c1
loop
-- dynamically build the insert statement.
stmt := 'insert into temp(table_name,run_date,table_count) ';
stmt := stmt || 'select ''' || table_rec.table_name || ''','''|| sysdate||''','|| 'count(*) from ' || table_rec.table_name;
-- Execute the insert statement.
execute immediate(stmt);
end loop;
END;
commit;

Resources