Dynamically create tables based on the total counts of another table - oracle

I want to be able to create tables based on the total rows in another table.
Let's say I've a table A, and the count is 500K, and so I wanna be able to create 5 tables each of 100K dynamically inside a procedure in oracle.
table A will have counts changing each time, but I would still want to be able to create tables with 100K max, for instance, tomorrow the table A has 550K, I want to be able to create 6 tables with 5 tables having 100k and last one with 50k.
SET SERVEROUTPUT ON
DECLARE
ttl_tables NUMBER;
var_loop NUMBER := 0;
BEGIN
SELECT CAST(COUNT(*)/(500000) AS INT) INTO ttl_tables FROM
px_extract_checks;
DBMS_OUTPUT.PUT_LINE(ttl_tables);
LOOP
var_loop := var_loop + 1;
EXECUTE IMMEDIATE (' || CREATE TABLE ' || 'table' || var_loop || ' ' ||
(Card_Number) || ');
EXIT WHEN var_loop = ttl_tables;
END LOOP;
END;
Something like above.

I have created a small block to help you out using dynamic queries.
You need to use actual column names and little bit changes in the following code as per your requirement:
See inline comments for more information on steps.
BEGIN
-- BEFORE CREATING THE TABLES, NEED TO DROP THE TABLES
FOR T IN (
SELECT
TABLE_NAME
FROM
USER_TABLES
WHERE
TABLE_NAME LIKE 'MY_TABLES_DYNAMIC%'
) LOOP
EXECUTE IMMEDIATE 'DROP TABLE ' || T.TABLE_NAME;
END LOOP;
-- CREATING THE TABLES USING TABLE_A
FOR I IN (
SELECT
CEIL(COUNT(1) / 100000) AS CNT -- DIVIDED THE COUNT OF ROWS BY 100K
FROM
TABLE_A
) LOOP
-- USING CTAS
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLES_DYNAMIC_'
|| I.CNT
|| ' AS '
|| ' SELECT COL1, COL2, ... FROM'
|| ' (SELECT A.COL1, A.COL2, ...., ROW_NUMBER() OVER (ORDER BY A.PRIMARY_KEY_OF_TABLE_A) RN '
|| ' FROM TABLE_A A)'
|| ' WHERE RN BETWEEN '
|| ( ( I.CNT - 1 ) * 100000 ) + 1
|| ' AND '
|| ( I.CNT * 100000 );
END LOOP;
END;
Cheers!!

Hope this below code will help you for your requirement, you can test it by changing the column names,table names and counts accordingly:
declare
tname_count number:=0;
t_name varchar2(500);
begin
select count(1) into t_count from A;
for k in 1.. t_count
loop
if mod(k,100000)=0 then
tname_count:=tname_count+1;
t_name:='TAB_'||tname_count;
execute immediate 'create table '|| t_name||' (id varchar2(50))';
end if;
end loop;
if floor(t_count/100000)<>0 then
tname_count:=tname_count+1;
t_name:='TAB_'||tname_count;
execute immediate 'create table '|| t_name||' (id varchar2(50))';
end if;
end;

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!!

Automatically generate sequences and triggers for all tables in Oracle

In my schema, I've migrated about 250 tables from SQL Server to Oracle. The thing is, no sequences or triggers have been created for any of these tables.
Is there an easy way to generate all the table sequences and triggers rather than manually doing this for every table?
An example of a sequence I need would be:
CREATE SEQUENCE "SYSTEM"."SEC_USERS_ID_SEQ"
MINVALUE 0 MAXVALUE 999999999999999999999999
INCREMENT BY 1
START WITH 23
CACHE 20
NOORDER NOCYCLE NOPARTITION;
And the trigger:
create or replace TRIGGER SEC_USERS_TRIG
before INSERT
ON "SYSTEM"."SEC_USERS"
FOR EACH row
BEGIN
IF inserting THEN
IF :NEW."ID" IS NULL THEN
SELECT SEC_USERS_ID_SEQ.nextval INTO :NEW."ID" FROM dual;
END IF;
END IF;
END;
We can generate scripts using the Oracle data dictionary views (the equivalent of MSSQL INFORMATION_SCHEMA). Find out more.
This example generates CREATE SEQUENCE statements. I have followed your example and accepted the default values, which don't need to be coded. The sequence name is derived from table name concatenated with column name and suffixed with "_SEQ". Watch out for Oracle's thirty character limit on object names!
This loop dynamically queries the table to get the current maximum value of the Primary Key column, which is used to derive the STARTS WITH clause.
declare
curr_mx number;
begin
for lrec in ( select ucc.table_name
, ucc.column_name
from user_constraints uc
join user_cons_columns ucc
on ucc.table_name = uc.table_name
and ucc.constraint_name = uc.constraint_name
join user_tab_columns utc
on utc.table_name = ucc.table_name
and utc.column_name = ucc.column_name
where uc.constraint_type = 'P' -- primary key
and utc.data_type = 'NUMBER' -- only numeric columns
)
loop
execute immediate 'select max ('|| lrec.column_name ||') from ' ||lrec.table_name
into curr_mx;
if curr_mx is null then
curr_mx := 0;
end if;
dbms_output.put_line('CREATE SEQUENCE "'|| user || '"."'
|| lrec.table_name ||'_'|| lrec.column_name || '_SEQ" '
||' START WITH ' || to_char( curr_mx + 1 ) ||';'
);
end loop;
end;
/
This code uses DBMS_OUTPUT, so you can spool it to a file for later use. If you're using an IDE like SQL Developer you may need to enable DBMS_OUTPUT. Follow the guidance in this StackOverflow answer.
If you can guarantee that all your tables have a primary key which is a numeric column called ID then you can simplify the select statement. Contrariwise, if some of your primary keys are compound constraints you will need to handle that.
Obviously I plumped for generating sequences because they're simpler. Writing the more complex trigger implementation is left as an exercise for the reader :)
thanks for the script. I altered it a little bit and did the trigger implementation. Feel free to use it.
declare
curr_mx number;
counter number;
seq_name varchar2 (30);
trigger_name varchar2 (30);
begin
for lrec in ( select ucc.table_name
, ucc.column_name
from user_constraints uc
join user_cons_columns ucc
on ucc.table_name = uc.table_name
and ucc.constraint_name = uc.constraint_name
join user_tab_columns utc
on utc.table_name = ucc.table_name
and utc.column_name = ucc.column_name
where uc.constraint_type = 'P' -- primary key
and utc.data_type = 'NUMBER' -- only numeric columns
)
loop
execute immediate 'select (max ('|| lrec.column_name ||')+1) from ' ||lrec.table_name
into curr_mx;
IF curr_mx is null THEN
curr_mx := 0;
END IF;
IF counter is null THEN
counter := 0;
END IF;
/* check length of sequence name, 30 is max */
IF length(lrec.table_name ||'_'|| lrec.column_name || '_SEQ') > 30 THEN
IF length(lrec.column_name || '_SEQ') > 30 THEN
seq_name := counter || '_PKA_SEQ';
ELSE
seq_name := lrec.column_name || '_SEQ';
END IF;
ELSE
seq_name := lrec.table_name ||'_'|| lrec.column_name || '_SEQ';
END IF;
/* check length of trigger name, 30 is max */
IF length(lrec.table_name || '_PKA_T') > 30 THEN
trigger_name := counter || '_PKA_T';
ELSE
trigger_name := lrec.table_name || '_PKA_T';
END IF;
counter := counter +1;
dbms_output.put_line(
'CREATE SEQUENCE "' || seq_name || '"'
||' START WITH ' || to_char( curr_mx + 1 ) ||';'
);
dbms_output.put_line('/');
dbms_output.put_line(
'CREATE OR REPLACE TRIGGER "' || trigger_name || '"'
|| ' BEFORE INSERT ON "' || lrec.table_name || '"'
|| ' FOR EACH ROW '
|| ' BEGIN '
|| ' :new."' || lrec.column_name || '" := "' || seq_name || '".nextval;'
|| ' END;'
);
dbms_output.put_line('/');
end loop;
end;
I also checked if the names of the sequences and triggers are longer than 30 characters because oracle won´t accept these.
EDIT:
Had to put '/' after each line so you can execute all statements at one run.

Find a Value in Table Using SQL Developer

I inherited a large database and nobody seems to know which table/column a particular data set is coming from. I've spent a lot of time going through table by table in Oracle's SQL Developer, but I can't find it. Is there a way in SQLDeveloper to search the entire table for a single value. Something like:
select table_name from all_tab_columns where column_value='desired value';
The db has around 1K+ tables each with lots of columns so manually combing through this isn't working.
You can use the following script to search for a value in all columns of your schema. The execution time for the script will depend on the number of tables in your schema and the number of rows in each of your table.
Replace 'abc' with the value which you intend to search. Also, right now the script will search all VARCHAR2 columns. You can also insert the table names and counts into a table instead of doing a DBMS_OUTPUT.PUT_LINE.
DECLARE
CURSOR cur_tables
IS
SELECT table_name,
column_name
FROM user_tab_columns
WHERE data_type = 'VARCHAR2';
v_sql VARCHAR2(4000);
v_value VARCHAR2(50);
v_count NUMBER;
BEGIN
v_value := 'abc';
FOR c_tables IN cur_tables LOOP
v_sql := 'SELECT count(1) FROM ' || c_tables.table_name || ' WHERE ' || c_tables.column_name || ' = :val' ;
EXECUTE IMMEDIATE v_sql INTO v_count USING v_value;
IF v_count > 0 THEN
DBMS_OUTPUT.PUT_LINE('Table Name ' || c_tables.table_name || ' Column Name ' || c_tables.column_name || ' Row Count ' || v_count);
END IF;
END LOOP;
END;

Execute For Each Table in PLSQL

I want to the the number of records in all tables that match a specific name criteria. Here is the SQL I built
Declare SQLStatement VARCHAR (8000) :='';
BEGIN
SELECT 'SELECT COUNT (*) FROM ' || Table_Name || ';'
INTO SQLStatement
FROM All_Tables
WHERE 1=1
AND UPPER (Table_Name) LIKE UPPER ('MSRS%');
IF SQLStatement <> '' THEN
EXECUTE IMMEDIATE SQLStatement;
END IF;
END;
/
But I get the following error:
Error at line 1
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 3
Script Terminated on line 1.
How do I modify this so that it runs for all matching tables?
Update:
Based on an answer received, I tried the following but I do not get anything in the DBMS_OUTPUT
declare
cnt number;
begin
for r in (select table_name from all_tables) loop
dbms_output.put_line('select count(*) from CDR.' || r.table_name);
end loop;
end;
/
declare
cnt number;
begin
for r in (select owner, table_name from all_tables
where upper(table_name) like ('%MSRS%')) loop
execute immediate 'select count(*) from "'
|| r.owner || '"."'
|| r.table_name || '"'
into cnt;
dbms_output.put_line(r.owner || '.' || r.table_name || ': ' || cnt);
end loop;
end;
/
If you're selecting from all_tables you cannot count on having been given the grants necessary to select from the table name. You should therefore check for the ORA-00942: table or view does not exist error thrown.
As to the cause for your error: You get this error because the select statement returns a result set with more than one row (one for each table) and you cannot assign such a result set to a varchar2.
By the way, make sure you enable dbms_output with SET SERVEROUT ON before executing this block.

How to trim all columns in all rows in all tables of type string?

In Oracle 10g, is there a way to do the following in PL/SQL?
for each table in database
for each row in table
for each column in row
if column is of type 'varchar2'
column = trim(column)
Thanks!
Of course, doing large-scale dynamic updates is potentially dangerous and time-consuming. But here's how you can generate the commands you want. This is for a single schema, and will just build the commands and output them. You could copy them into a script and review them before running. Or, you could change dbms_output.put_line( ... ) to EXECUTE IMMEDIATE ... to have this script execute all the statements as they are generated.
SET SERVEROUTPUT ON
BEGIN
FOR c IN
(SELECT t.table_name, c.column_name
FROM user_tables t, user_tab_columns c
WHERE c.table_name = t.table_name
AND data_type='VARCHAR2')
LOOP
dbms_output.put_line(
'UPDATE '||c.table_name||
' SET '||c.column_name||' = TRIM('||c.column_name||') WHERE '||
c.column_name||' <> TRIM('||c.column_name||') OR ('||
c.column_name||' IS NOT NULL AND TRIM('||c.column_name||') IS NULL)'
);
END LOOP;
END;
Presumably you want to do this for every column in a schema, not in the database. Trying to do this to the dictionary tables would be a bad idea...
declare
v_schema varchar2(30) := 'YOUR_SCHEMA_NAME';
cursor cur_tables (p_schema_name varchar2) is
select owner, table_name, column_name
from all_tables at,
inner join all_tab_columns atc
on at.owner = atc.owner
and at.table_name = atc.table_name
where atc.data_type = 'VARCHAR2'
and at.owner = p_schema;
begin
for r_table in cur_tables loop
execute immediate 'update ' || r.owner || '.' || r.table_name
|| ' set ' || r.column_name || ' = trim(' || r.column_name ||');';
end loop;
end;
This will only work for fields that are VARCHAR2s in the first place. If your database contains CHAR fields, then you're out of luck, because CHAR fields are always padded to their maximum length.

Resources